Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions app/models/tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,25 @@ class Tag < ApplicationRecord
validate :synonym_unique
validates :name, uniqueness: { scope: [:tag_set_id], case_sensitive: false }

# Fuzzy-searches tags by name, excerpt, and synonym name
# @param term [String] search term
# @return [ActiveRecord::Relation<Tag>]
def self.search(term)
stripped = term.strip
value = "%#{sanitize_sql_like(term.strip)}%"

# Query to search on tags, the name is used for sorting.
q1 = where('tags.name LIKE ?', "%#{sanitize_sql_like(stripped)}%")
.or(where('tags.excerpt LIKE ?', "%#{sanitize_sql_like(stripped)}%"))
.select(Arel.sql('name AS sortname, tags.*'))
q1 = where('tags.name LIKE ?', value)
.or(where('tags.excerpt LIKE ?', value))
.select('tags.*')

# Query to search on synonyms, the synonym name is used for sorting.
# The order clause here actually applies to the union of q1 and q2 (so not just q2).
q2 = joins(:tag_synonyms)
.where('tag_synonyms.name LIKE ?', "%#{sanitize_sql_like(stripped)}%")
.select(Arel.sql('tag_synonyms.name AS sortname, tags.*'))
.order(Arel.sql(sanitize_sql_array(['sortname LIKE ? DESC, sortname', "#{sanitize_sql_like(stripped)}%"])))
.where('tag_synonyms.name LIKE ?', value)
.select('tags.*')

# Select from the union of the above queries, select only the tag columns such that we can distinct them
from(Arel.sql("((#{q1.to_sql}) UNION (#{q2.to_sql})) tags"))
.order(Arel.sql(sanitize_sql_array(['name LIKE ? DESC, name', value])))
.select(Tag.column_names.map { |c| "tags.#{c}" })
.distinct
end
Expand Down
14 changes: 14 additions & 0 deletions test/models/tag_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,18 @@ class TagTest < ActiveSupport::TestCase
test 'is community related' do
assert_community_related(Tag)
end

test 'search should correctly order tags' do
term = 'us'

tags = Tag.search(term).to_a

name_match_sorted = tags.select { |t| t.name.include?(term) }.sort { |a, b| a.name <=> b.name }
excerpt_match_sorted = tags.select { |t| t.excerpt&.include?(term) }.sort { |a, b| a.name <=> b.name }
sorted_tags = name_match_sorted + excerpt_match_sorted

sorted_tags.each_with_index do |tag, idx|
assert_equal tag.name, tags[idx].name
end
end
end