public
Created

Class that handles the PostgreSQL full text search for railscasts.com

  • Download Gist
episode_search.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
class EpisodeSearch
attr_reader :ability, :options
delegate :sanitize, to: Episode
 
def initialize(ability, options = {})
@ability = ability
@options = options.dup
end
 
def tag
@tag ||= Tag.find_by_id(options[:tag_id]) if options[:tag_id].present?
end
 
def episodes(paginate = true)
episodes = (tag ? tag.episodes : Episode).accessible_by(ability).where(type_conditions)
episodes = episodes.paginate(page: page, per_page: per_page) if paginate
if options[:search]
episodes = episodes.where(search_conditions(options[:search])).order(order_clause)
else
episodes = episodes.recent
end
episodes
end
 
def similar_episodes(episode)
episodes(false).limit(5).where(search_conditions(episode.name, "OR")).where("episodes.id != ?", episode.id)
end
 
def type_conditions
case options[:type]
when "free" then {pro: false, revised: false}
when "pro" then {pro: true, revised: false}
when "revised" then {revised: true}
else {}
end
end
 
def search_conditions(query, join = "AND")
query.split(/\s+/).map do |word|
'(' + word_search_conditions(word).join(' OR ') + ')'
end.join(" #{join} ")
end
 
def word_search_conditions(word)
%w[name description notes].map do |col|
"to_tsvector('english', episodes.#{col}) @@ plainto_tsquery(#{sanitize(word)})"
end + ["episodes.position::varchar = #{sanitize(word)}"]
end
 
def order_clause
(search_order_clauses + extra_order_clauses).join(" + ") + " desc"
end
 
def search_order_clauses
{
name: 5,
description: 3,
notes: 1
}.map do |col, weight|
"(ts_rank(to_tsvector('english', episodes.#{col}), " +
"plainto_tsquery(#{sanitize(options[:search])})) * #{weight})"
end
end
 
def extra_order_clauses
[
"((date(episodes.published_at) - date '2007-01-01') * 0.0003)",
"(episodes.comments_count * 0.001)",
"(CASE episodes.revised WHEN TRUE THEN 0.02 ELSE 0 END)",
]
end
 
def per_page
if options[:per_page]
options[:per_page].to_i
else
case options[:view]
when "list" then 40
when "grid" then 24
else 10
end
end
end
 
def page
options[:page].to_i > 0 ? options[:page].to_i : 1
end
 
def applied_filters
filters = []
filters << [options[:search], options.merge(search: nil, page: nil)] if options[:search].present?
filters << ["#{options[:type].titleize} Episodes", options.merge(type: nil, page: nil)] if options[:type].present?
filters << [tag.display_name, options.merge(tag_id: nil, page: nil)] if tag
filters
end
end

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.