class ScopedSearch::Definition

The ScopedSearch definition class defines on what fields should be search in the model in what cases

A definition can be created by calling the scoped_search method on an ActiveRecord-based class, so you should not create an instance of this class yourself.

Constants

INTEGER_REGXP
NUMERICAL_REGXP
UUID_REGXP

Attributes

default_order[RW]
klass[R]
profile[RW]

Public Class Methods

new(klass) click to toggle source

Initializes a ScopedSearch definition instance. This method will also setup a database adapter and create the :search_for named scope if it does not yet exist.

    # File lib/scoped_search/definition.rb
197 def initialize(klass)
198   @klass                 = klass
199   @fields                = {}
200   @unique_fields         = []
201   @profile_fields        = {:default => {}}
202   @profile_unique_fields = {:default => []}
203 
204   register_named_scope! unless klass.respond_to?(:search_for)
205   register_complete_for! unless klass.respond_to?(:complete_for)
206 end

Public Instance Methods

default_fields() click to toggle source

Returns a list of fields that should be searched on by default.

Every field will show up in this method's result, except for fields for which the only_explicit parameter is set to true.

    # File lib/scoped_search/definition.rb
303 def default_fields
304   unique_fields.reject { |field| field.only_explicit }
305 end
default_fields_for(value, operator = nil) click to toggle source

Returns a list of appropriate fields to search in given a search keyword and operator.

    # File lib/scoped_search/definition.rb
266 def default_fields_for(value, operator = nil)
267 
268   column_types  = [:virtual]
269   column_types += [:string, :text]                if [nil, :like, :unlike, :ne, :eq].include?(operator)
270   column_types += [:double, :float, :decimal]     if value =~ NUMERICAL_REGXP
271   column_types += [:integer]                      if value =~ INTEGER_REGXP
272   column_types += [:uuid]                         if value =~ UUID_REGXP
273   column_types += [:datetime, :date, :timestamp]  if (parse_temporal(value))
274 
275   default_fields.select { |field| !field.set? && column_types.include?(field.type) }
276 end
define(*args) click to toggle source

Defines a new search field for this search definition.

    # File lib/scoped_search/definition.rb
308 def define(*args)
309   Field.new(self, *args)
310 end
define_field(name, field) click to toggle source
    # File lib/scoped_search/definition.rb
214 def define_field(name, field)
215   @profile ||= :default
216   @profile_fields[@profile] ||= {}
217   @profile_fields[@profile][name.to_sym] ||= field
218   @profile_unique_fields[@profile] ||= []
219   @profile_unique_fields[@profile] = (@profile_unique_fields[@profile] + [field]).uniq
220   field
221 end
field_by_name(name) click to toggle source

this method return definitions::field object from string

    # File lib/scoped_search/definition.rb
236 def field_by_name(name)
237   field = fields[name.to_sym] unless name.blank?
238   if field.nil?
239     dotted = name.to_s.split('.')[0]
240     field = fields[dotted.to_sym] unless dotted.blank?
241     if field && field.key_relation.nil?
242       return nil
243     end
244   end
245   field
246 end
fields() click to toggle source
    # File lib/scoped_search/definition.rb
223 def fields
224   @profile ||= :default
225   @profile_fields[@profile] ||= {}
226   super_definition ? super_definition.fields.merge(@profile_fields[@profile]) : @profile_fields[@profile]
227 end
operator_by_field_name(name) click to toggle source

this method is used by the syntax auto completer to suggest operators.

    # File lib/scoped_search/definition.rb
249 def operator_by_field_name(name)
250   field = field_by_name(name)
251   return [] if field.nil?
252   return field.operators                                          if field.operators
253   return ['=', '!=', '>', '<', '<=', '>=', '~', '!~', '^', '!^']  if field.virtual?
254   return ['=', '!=']                                              if field.set? || field.uuid?
255   return ['=', '>', '<', '<=', '>=', '!=', '^', '!^']             if field.numerical?
256   return ['=', '!=', '~', '!~', '^', '!^']                        if field.textual?
257   return ['=', '>', '<']                                          if field.temporal?
258   raise ScopedSearch::QueryNotSupported, "Unsupported type '#{field.type.inspect}')' for field '#{name}'. This can be a result of a search definition problem."
259 end
parse_temporal(value) click to toggle source

Try to parse a string as a datetime. Supported formats are Today, Yesterday, Sunday, '1 day ago', '2 hours ago', '3 months ago', '4 weeks from now', 'Jan 23, 2004' And many more formats that are documented in Ruby DateTime API Doc. In case Time responds to zone, we know this is Rails environment and we can use Time.zone.parse. The benefit is that the current timezone is respected and does not have to be specified explicitly. That way even relative dates work as expected.

    # File lib/scoped_search/definition.rb
283 def parse_temporal(value)
284   return Date.current if value =~ /\btoday\b/i
285   return 1.day.ago.to_date if value =~ /\byesterday\b/i
286   return 1.day.from_now.to_date if value =~ /\btomorrow\b/i
287   return (eval($1.strip.gsub(/\s+/,'.').downcase)).to_datetime if value =~ /\A\s*(\d+\s+\b(?:hours?|minutes?)\b\s+\bago)\b\s*\z/i
288   return (eval($1.strip.gsub(/\s+/,'.').downcase)).to_date     if value =~ /\A\s*(\d+\s+\b(?:days?|weeks?|months?|years?)\b\s+\bago)\b\s*\z/i
289   return (eval($1.strip.gsub(/from\s+now/i,'from_now').gsub(/\s+/,'.').downcase)).to_datetime if value =~ /\A\s*(\d+\s+\b(?:hours?|minutes?)\b\s+\bfrom\s+now)\b\s*\z/i
290   return (eval($1.strip.gsub(/from\s+now/i,'from_now').gsub(/\s+/,'.').downcase)).to_date     if value =~ /\A\s*(\d+\s+\b(?:days?|weeks?|months?|years?)\b\s+\bfrom\s+now)\b\s*\z/i
291   if Time.respond_to?(:zone) && !Time.zone.nil?
292     parsed = Time.zone.parse(value) rescue nil
293     parsed && parsed.to_datetime
294   else
295     DateTime.parse(value, true) rescue nil
296   end
297 end
reflection_by_name(klass, name) click to toggle source

Returns a reflection for a given klass and name

    # File lib/scoped_search/definition.rb
313 def reflection_by_name(klass, name)
314   return if name.nil?
315   klass.reflections[name.to_sym] || klass.reflections[name.to_s]
316 end
super_definition() click to toggle source
    # File lib/scoped_search/definition.rb
210 def super_definition
211   klass.superclass.try(:scoped_search_definition)
212 end
unique_fields() click to toggle source
    # File lib/scoped_search/definition.rb
229 def unique_fields
230   @profile ||= :default
231   @profile_unique_fields[@profile] ||= []
232   super_definition ? (super_definition.unique_fields + @profile_unique_fields[@profile]).uniq : @profile_unique_fields[@profile]
233 end

Protected Instance Methods

register_complete_for!() click to toggle source

Registers the complete_for method within the class that is used for searching.

    # File lib/scoped_search/definition.rb
340 def register_complete_for! # :nodoc
341   @klass.extend(ScopedSearch::AutoCompleteClassMethods)
342 end
register_named_scope!() click to toggle source

Registers the search_for named scope within the class that is used for searching.

    # File lib/scoped_search/definition.rb
321 def register_named_scope! # :nodoc
322   @klass.define_singleton_method(:search_for) do |query = '', options = {}|
323     # klass may be different to @klass if the scope is called on a subclass
324     klass = self
325     definition = klass.scoped_search_definition
326 
327     search_scope = klass.all
328     find_options = ScopedSearch::QueryBuilder.build_query(definition, query || '', options)
329     search_scope = search_scope.where(find_options[:conditions])        if find_options[:conditions]
330     search_scope = search_scope.includes(find_options[:include])        if find_options[:include]
331     search_scope = search_scope.joins(find_options[:joins])             if find_options[:joins]
332     search_scope = search_scope.reorder(Arel.sql(find_options[:order])) if find_options[:order]
333     search_scope = search_scope.references(find_options[:include])      if find_options[:include]
334 
335     search_scope
336   end
337 end