Posting this here if someone else is looking for a way to query a model by tags and order by the number of matches. This solution also allows for the usage of any "equality" operator like the %
from pg_trgm.
query = <<-SQL
SELECT users.*, COUNT(DISTINCT taggings.id) AS ct
FROM users
INNER JOIN taggings ON taggings.taggable_type = 'User'
AND taggings.context = 'skills'
AND taggings.taggable_id = users.id
AND taggings.tag_id IN
(SELECT tags.id FROM tags
WHERE (LOWER(tags.name) % 'ruby'
OR LOWER(tags.name) % 'java'
OR LOWER(tags.name) % 'sa-c'
OR LOWER(tags.name) % 'c--'
OR LOWER(tags.name) % 'gnu e'
OR LOWER(tags.name) % 'lite-c'
))
GROUP BY users.id
ORDER BY ct DESC;
SQL
User.find_by_sql(query)
Note that the code above will only work if you have pg_trgm enabled. You can also simply replace %
with ILIKE
.
EDIT: With ActiveRecord and eager loading:
This could be in a scope or class method and can be chained with other ActiveRecord methods.
ActiveRecord::Base.connection
.execute('SET pg_trgm.similarity_threshold = 0.5')
matches = skills.map do
'LOWER(tags.name) % ?'
end.join(' OR ')
select('users.*, COUNT(DISTINCT taggings.id) AS ct')
.joins(sanitize_sql_array(["INNER JOIN taggings
ON taggings.taggable_type = 'User'
AND taggings.context = 'skills'
AND taggings.taggable_id = users.id
AND taggings.tag_id IN
(SELECT tags.id FROM tags WHERE (#{matches}))", *skills]))
.group('users.id')
.order('ct DESC')
.includes(:skills)
Override skill_list
from acts-as-taggable-on in the model:
def skill_list
skills.collect(&:name)
end
and proceed normally.