otwarchive-symphonyarchive/app/models/search/work_search_form.rb
2026-03-11 22:22:11 +00:00

228 lines
5.9 KiB
Ruby

class WorkSearchForm
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
ATTRIBUTES = [
:query,
:title,
:creators,
:collected,
:faceted,
:revised_at,
:language_id,
:complete,
:crossover,
:single_chapter,
:word_count,
:hits,
:kudos_count,
:bookmarks_count,
:comments_count,
:pseud_ids,
:collection_ids,
:tag,
:excluded_tag_names,
:excluded_tag_ids,
:other_tag_names,
:filter_ids,
:fandom_names,
:fandom_ids,
:rating_ids,
:category_ids,
:archive_warning_ids,
:character_names,
:character_ids,
:relationship_names,
:relationship_ids,
:freeform_names,
:freeform_ids,
:date_from,
:date_to,
:words_from,
:words_to,
:sort_column,
:sort_direction,
:page
].freeze
attr_accessor :options
ATTRIBUTES.each do |filterable|
define_method(filterable) { options[filterable] }
end
def initialize(opts={})
@options = opts
process_options
@searcher = WorkQuery.new(@options)
end
def process_options
@options.delete_if { |k, v| v == "0" || v.blank? }
standardize_creator_queries
standardize_language_ids
set_sorting
clean_up_angle_brackets
rename_warning_field
end
# Make the creator/creators change backwards compatible
def standardize_creator_queries
return unless @options[:query].present?
@options[:query] = @options[:query].gsub('creator:', 'creators:')
end
def standardize_language_ids
# Maintain backward compatibility for old work searches/filters:
# - Using language IDs in the "Language" dropdown
if @options[:language_id].present? && @options[:language_id].to_i != 0
language = Language.find_by(id: options[:language_id])
options[:language_id] = language.short if language.present?
end
# - Using language IDs in "Any field" (search) or "Search within results" (filters)
if @options[:query].present?
@options[:query] = @options[:query].gsub(/\blanguage_id\s*:\s*(\d+)/) do
lang = Language.find_by(id: Regexp.last_match[1])
lang = Language.default if lang.blank?
"language_id: " + lang.short
end
end
end
def set_sorting
@options[:sort_column] ||= default_sort_column
@options[:sort_direction] ||= default_sort_direction
end
def clean_up_angle_brackets
[:word_count, :hits, :kudos_count, :comments_count, :bookmarks_count, :revised_at, :query].each do |countable|
next unless @options[countable].present?
str = @options[countable]
@options[countable] = str.gsub("&gt;", ">").gsub("&lt;", "<")
end
end
def rename_warning_field
if @options[:warning_ids].present?
@options[:archive_warning_ids] = @options.delete(:warning_ids)
end
end
def persisted?
false
end
def summary
summary = []
if @options[:query].present?
summary << @options[:query].gsub('creators:', 'creator:')
end
if @options[:title].present?
summary << "Title: #{@options[:title]}"
end
if @options[:creators].present?
summary << "Creator: #{@options[:creators]}"
end
tags = @searcher.included_tag_names
all_tag_ids = @searcher.filter_ids
unless all_tag_ids.empty?
tags << Tag.where(id: all_tag_ids).pluck(:name).join(", ")
end
unless tags.empty?
summary << "Tags: #{tags.uniq.join(", ")}"
end
if complete.to_s == "T"
summary << "Complete"
elsif complete.to_s == "F"
summary << "Incomplete"
end
if crossover.to_s == "T"
summary << "Only Crossovers"
elsif crossover.to_s == "F"
summary << "No Crossovers"
end
if %w(1 true).include?(self.single_chapter.to_s)
summary << "Single Chapter"
end
if @options[:language_id].present?
language = Language.find_by(short: @options[:language_id])
if language.present?
summary << "Language: <span lang=#{language.short}>#{language.name}</span>"
end
end
[:word_count, :hits, :kudos_count, :comments_count, :bookmarks_count, :revised_at].each do |countable|
if @options[countable].present?
summary << "#{countable.to_s.humanize.downcase}: #{@options[countable]}"
end
end
if @options[:sort_column].present?
# Use pretty name if available, otherwise fall back to plain column name
pretty_sort_name = name_for_sort_column(@options[:sort_column])
direction = if @options[:sort_direction].present?
@options[:sort_direction] == "asc" ? " ascending" : " descending"
else
""
end
summary << ("sort by: #{pretty_sort_name&.downcase || @options[:sort_column]}" + direction)
end
summary.join(" ")
end
def search_results
@searcher.search_results
end
###############
# SORTING
###############
SORT_OPTIONS = [
["Best Match", "_score"],
%w[Creator authors_to_sort_on],
%w[Title title_to_sort_on],
["Date Posted", "created_at"],
["Date Updated", "revised_at"],
["Word Count", "word_count"],
%w[Hits hits],
%w[Kudos kudos_count],
%w[Comments comments_count],
%w[Bookmarks bookmarks_count]
].freeze
def sort_columns
options[:sort_column] || default_sort_column
end
def sort_direction
options[:sort_direction] || default_sort_direction
end
def sort_options
options[:faceted] || options[:collected] ? SORT_OPTIONS[1..-1] : SORT_OPTIONS
end
def sort_values
sort_options.map{ |option| option.last }
end
# extract the pretty name
def name_for_sort_column(sort_column)
Hash[SORT_OPTIONS.map { |v| [v[1], v[0]] }][sort_column]
end
def default_sort_column
options[:faceted] || options[:collected] ? 'revised_at' : '_score'
end
def default_sort_direction
if %w[authors_to_sort_on title_to_sort_on].include?(sort_column)
'asc'
else
'desc'
end
end
end