Class: ScopedSearch::Definition

Inherits:
Object
  • Object
show all
Defined in:
lib/scoped_search/definition.rb

Overview

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.

Defined Under Namespace

Classes: Field

Constant Summary collapse

NUMERICAL_REGXP =
/^\-?\d+(\.\d+)?$/
INTEGER_REGXP =
/^\-?\d+$/
UUID_REGXP =
/^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(klass) ⇒ Definition

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.



197
198
199
200
201
202
203
204
205
206
# File 'lib/scoped_search/definition.rb', line 197

def initialize(klass)
  @klass                 = klass
  @fields                = {}
  @unique_fields         = []
  @profile_fields        = {:default => {}}
  @profile_unique_fields = {:default => []}

  register_named_scope! unless klass.respond_to?(:search_for)
  register_complete_for! unless klass.respond_to?(:complete_for)
end

Instance Attribute Details

#default_orderObject

Returns the value of attribute default_order.



208
209
210
# File 'lib/scoped_search/definition.rb', line 208

def default_order
  @default_order
end

#klassObject (readonly)

Returns the value of attribute klass.



192
193
194
# File 'lib/scoped_search/definition.rb', line 192

def klass
  @klass
end

#profileObject

Returns the value of attribute profile.



208
209
210
# File 'lib/scoped_search/definition.rb', line 208

def profile
  @profile
end

Instance Method Details

#date_with_timezone(datetime) ⇒ Object

This should get called only when the relative specifiers (1 day ago and so on), this code path is only viable when Rails are available



340
341
342
343
344
# File 'lib/scoped_search/definition.rb', line 340

def date_with_timezone(datetime)
  date = datetime.to_date
  return datetime unless date.respond_to?(:in_time_zone)
  date.in_time_zone.to_datetime
end

#default_fieldsObject

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.



303
304
305
# File 'lib/scoped_search/definition.rb', line 303

def default_fields
  unique_fields.reject { |field| field.only_explicit }
end

#default_fields_for(value, operator = nil) ⇒ Object

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



266
267
268
269
270
271
272
273
274
275
276
# File 'lib/scoped_search/definition.rb', line 266

def default_fields_for(value, operator = nil)

  column_types  = [:virtual]
  column_types += [:string, :text, :citext]       if [nil, :like, :unlike, :ne, :eq].include?(operator)
  column_types += [:double, :float, :decimal]     if value =~ NUMERICAL_REGXP
  column_types += [:integer]                      if value =~ INTEGER_REGXP
  column_types += [:uuid]                         if value =~ UUID_REGXP
  column_types += [:datetime, :date, :timestamp]  if (parse_temporal(value))

  default_fields.select { |field| !field.set? && column_types.include?(field.type) }
end

#define(*args, **kwargs) ⇒ Object

Defines a new search field for this search definition.



308
309
310
# File 'lib/scoped_search/definition.rb', line 308

def define(*args, **kwargs)
  Field.new(self, *args, **kwargs)
end

#define_field(name, field) ⇒ Object



214
215
216
217
218
219
220
221
# File 'lib/scoped_search/definition.rb', line 214

def define_field(name, field)
  @profile ||= :default
  @profile_fields[@profile] ||= {}
  @profile_fields[@profile][name.to_sym] ||= field
  @profile_unique_fields[@profile] ||= []
  @profile_unique_fields[@profile] = (@profile_unique_fields[@profile] + [field]).uniq
  field
end

#field_by_name(name) ⇒ Object

this method return definitions::field object from string



236
237
238
239
240
241
242
243
244
245
246
# File 'lib/scoped_search/definition.rb', line 236

def field_by_name(name)
  field = fields[name.to_sym] unless name.blank?
  if field.nil?
    dotted = name.to_s.split('.')[0]
    field = fields[dotted.to_sym] unless dotted.blank?
    if field && field.key_relation.nil?
      return nil
    end
  end
  field
end

#fieldsObject



223
224
225
226
227
# File 'lib/scoped_search/definition.rb', line 223

def fields
  @profile ||= :default
  @profile_fields[@profile] ||= {}
  super_definition ? super_definition.fields.merge(@profile_fields[@profile]) : @profile_fields[@profile]
end

#operator_by_field_name(name) ⇒ Object

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



249
250
251
252
253
254
255
256
257
258
259
# File 'lib/scoped_search/definition.rb', line 249

def operator_by_field_name(name)
  field = field_by_name(name)
  return [] if field.nil?
  return field.operators                                          if field.operators
  return ['=', '!=', '>', '<', '<=', '>=', '~', '!~', '^', '!^']  if field.virtual?
  return ['=', '!=']                                              if field.set? || field.uuid?
  return ['=', '>', '<', '<=', '>=', '!=', '^', '!^']             if field.numerical?
  return ['=', '!=', '~', '!~', '^', '!^']                        if field.textual?
  return ['=', '>', '<']                                          if field.temporal?
  raise ScopedSearch::QueryNotSupported, "Unsupported type '#{field.type.inspect}')' for field '#{name}'. This can be a result of a search definition problem."
end

#parse_temporal(value) ⇒ Object

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.



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/scoped_search/definition.rb', line 283

def parse_temporal(value)
  return date_with_timezone(Date.current) if value =~ /\btoday\b/i
  return date_with_timezone(1.day.ago) if value =~ /\byesterday\b/i
  return date_with_timezone(1.day.from_now) if value =~ /\btomorrow\b/i
  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
  return date_with_timezone(eval($1.strip.gsub(/\s+/,'.').downcase)) if value =~ /\A\s*(\d+\s+\b(?:days?|weeks?|months?|years?)\b\s+\bago)\b\s*\z/i
  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
  return date_with_timezone(eval($1.strip.gsub(/from\s+now/i,'from_now').gsub(/\s+/,'.').downcase)) if value =~ /\A\s*(\d+\s+\b(?:days?|weeks?|months?|years?)\b\s+\bfrom\s+now)\b\s*\z/i
  if Time.respond_to?(:zone) && !Time.zone.nil?
    parsed = Time.zone.parse(value) rescue nil
    parsed && parsed.to_datetime
  else
    DateTime.parse(value, true) rescue nil
  end
end

#reflection_by_name(klass, name) ⇒ Object

Returns a reflection for a given klass and name



313
314
315
316
# File 'lib/scoped_search/definition.rb', line 313

def reflection_by_name(klass, name)
  return if name.nil?
  klass.reflections[name.to_sym] || klass.reflections[name.to_s]
end

#super_definitionObject



210
211
212
# File 'lib/scoped_search/definition.rb', line 210

def super_definition
  klass.superclass.try(:scoped_search_definition)
end

#unique_fieldsObject



229
230
231
232
233
# File 'lib/scoped_search/definition.rb', line 229

def unique_fields
  @profile ||= :default
  @profile_unique_fields[@profile] ||= []
  super_definition ? (super_definition.unique_fields + @profile_unique_fields[@profile]).uniq : @profile_unique_fields[@profile]
end