Skip to content

Instantly share code, notes, and snippets.

@ryanb
Created February 18, 2013 00:44
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save ryanb/4974414 to your computer and use it in GitHub Desktop.
Save ryanb/4974414 to your computer and use it in GitHub Desktop.
Class that handles the PostgreSQL full text search for railscasts.com
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment