Skip to content

Instantly share code, notes, and snippets.

@joost
Created November 28, 2012 12:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joost/4160895 to your computer and use it in GitHub Desktop.
Save joost/4160895 to your computer and use it in GitHub Desktop.
Convert a params Hash to a Sunspot search.
# SearchHash converts a Hash with search options to a Sunspot search.
# Example:
# s = SearchHash.new(Model, :q => 'some string to match in all text fields')
# s.search # The Sunspot search
# s.search.results # The actual results
#
# s = SearchHash.new(Model, :some_field__gt => 12, :text_field_name => 'some query')
#
# Pagination:
# s = SearchHash.new(Model, {:some_field__gt => 12, :text_field_name => 'some query', :page => 2, :per_page => 100}, {:per_page_max => 200, :per_page_default => 25})
#
# Ordering:
# s = SearchHash.new(Model, {:some_field__gt => 12, :text_field_name => 'some query', :order => 'some_field__asc'}, {:order_direction_default => :desc})
#
class SearchHash
class SearchOption
PREDICATE_TO_SUNSPOT = {
:gt => :greater_than,
:lt => :less_than,
:gte => :greater_than,
:lte => :less_than,
:in => :any_of,
:eq => :equal_to,
# New
:all_in => :all_of,
:between => :between
}
attr_accessor :klass, :field_name, :predicate, :value
def sunspot_field
return @sunspot_field if @sunspot_field
@sunspot_field = Sunspot::Setup.for(self.klass).text_field_factories.detect {|f| f.name == field_name}
@sunspot_field ||= Sunspot::Setup.for(self.klass).field_factories.detect {|f| f.name == field_name}
raise StandardError, "Can't find Sunspot field #{field_name.inspect} for #{klass}." if @sunspot_field.nil?
@sunspot_field
end
# https://github.com/ernie/ransack/wiki/Basic-Searching
# https://github.com/sunspot/sunspot/wiki/Scoping-by-attribute-fields
# PREDICATES = %w(gt lt gte lte eq cont in)
def sunspot_predicate
@sunspot_predicate ||= PREDICATE_TO_SUNSPOT[predicate] || raise(StandardError, "No Sunspot predicate found named #{predicate.inspect}!")
end
def is_text_field?
sunspot_field.build.is_a?(Sunspot::FulltextField)
end
def is_attribute_field?
sunspot_field.build.is_a?(Sunspot::AttributeField)
end
def initialize(klass, field_name, predicate, value)
@klass = klass
@field_name = field_name.to_sym
@predicate = predicate.to_sym if predicate.present?
@value = value
sunspot_field # This raises Error early
end
def add_to_sunspot_search(search)
if self.is_attribute_field?
if self.predicate
if predicate =~ /^(lte|gte)$/ # Sunspot's does not have a gte. We fix this here.
search.any_of do
with(self.field_name, value)
with(self.field_name).send(sunspot_predicate, value)
end
else
search.with(self.field_name).send(sunspot_predicate, value)
end
else
search.with(self.field_name, value)
end
elsif self.is_text_field?
search.fulltext(value, :fields => [field_name])
end
end
end
attr_accessor :q, :order
attr_writer :page, :per_page
def search
@klass.solr_search do |search|
search.fulltext(q) if q.present?
@search_options.each {|search_option| search_option.add_to_sunspot_search(search)}
search.order_by(order_field, order_direction) if has_order?
search.paginate :page => page, :per_page => per_page
end
end
# Ordering
alias_method :s=, :order= # s is used by Ransack gem
alias_method :s, :order
def has_order?
!order_field.blank?
end
def order_field
split_order[0]
end
def order_direction
split_order[1] || @order_direction_default
end
# Pagination
def page
[@page.to_i, 1].max
end
def per_page
return @per_page_default if @per_page.nil? || @per_page.zero?
[@per_page, @per_page_max].min
end
alias_method :per=, :per_page= # per is used by Kaminari gem
alias_method :per, :per_page
private
def split_order
return [] if @order.blank?
@order.split('__')
end
def initialize(klass, attributes = {}, options = {})
@search_options = []
@klass = klass
@per_page_max = options[:per_page_max] || 100
@per_page_default = options[:per_page_default] || 10
@order_default = options[:order_direction_default] || :asc
attributes.each do |attribute, value|
self.send("#{attribute}=", value)
end
end
def method_missing(m, *args, &block)
if m =~ /(.*)=$/ # setter
field_name, predicate = $1.to_s.split('__')
@search_options << SearchOption.new(@klass, field_name, predicate, *args)
else
super
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment