Class: SearchApi::Search::Base

Inherits:
Object
  • Object
show all
Includes:
Callbacks
Defined in:
lib/search_api/search.rb,
lib/search_api.rb

Overview

The SearchApi::Search::Base class is able to encapsulate search parameters for a given model.

In order to search for instances of your model Stuff, you must:

  • ensure Stuff responds to search_api_bridge method. This is, by default, the case of ActiveRecord::Base subclasses.

  • define a subclass of SearchApi::Search::Base that uses Stuff as a model:

    class StuffSearch < SearchApi::Search::Base
      # search for Stuff
      model Stuff
    end
    

Assuming Stuff is an ActiveRecord::Base subclass, automatic search attributes are defined in StuffSearch. You can immediately use them:

Those two statements are strictly equivalent:

Stuff.find(:all, {:birth_date => Time.now})
Stuff.find(:all, StuffSearch.new(:birth_date => Time.now).find_options)

So far, so good. But that’s not very funky.

You can also define your own search attributes:

class StuffSearch < SearchApi::Search::Base
  # search for Stuff
  model Stuff
  search_accessor :max_age do |search|
    { :conditions => ['birth_date > ?', Time.now - search.max_age.years]}
  end
end

This allows you to perform searches on age:

Stuff.find(:all, StuffSearch.new(:max_age => 20).find_options)

You can mix search keys:

Stuff.find(:all, StuffSearch.new(:max_age => 20, :sex => 'M').find_options)

Constant Summary collapse

VALID_SEARCH_ATTRIBUTE_OPTIONS =
[ :store_as, :default ]

Constants included from Callbacks

Callbacks::CALLBACKS

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Callbacks

append_features, #before_find_options, #find_options_with_callbacks

Constructor Details

#initialize(attributes = nil) ⇒ Base

Initializes a search with a search attributes Hash.



347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/search_api/search.rb', line 347

def initialize(attributes=nil)
  raise "Can't create an instance without model" if self.class.model.nil?

  # initialize attributes with ignored value
  self.attributes = self.class.search_attributes.inject((attributes || {}).dup) do |attributes, search_attribute|
    if attributes.has_key?(search_attribute) || attributes.has_key?(search_attribute.to_s)
      attributes
    else
      attributes.update(search_attribute => self.class.read_inheritable_attribute(:search_attribute_default_values)[search_attribute])
    end
  end
end

Class Method Details

.model(*args) ⇒ Object

Without any argument, returns the model of this SearchApi::Search::Base class.

With a single argument, this method defines the model of this SearchApi::Search::Base class.

The model must respond_to the search_api_bridge method, which should return an object that acts like SearchApi::Bridge::Base.

The model can’t be defined twice.

Some automatic search accessors may be defined when the model is set. See:

  • Bridge::Base#automatic_search_attribute_builders

  • Bridge::ActiveRecord#automatic_search_attribute_builders

Example:

class StuffSearch < SearchApi::Search::Base
  # search for Stuff
  model Stuff
  ...
end

StuffSearch.model # => Stuff

Raises:

  • (ArgumentError)


76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/search_api/search.rb', line 76

def model(*args)
  # returns model when no arguments
  return @model if args.empty?

  # can't set model twice
  raise "model is already set" if @model

  # fetch optional options
  options = if args.last.is_a?(Hash) then args.pop else {} end

  # make sure model is the only last argument
  raise ArgumentError.new("Bad arguments for model") unless args.length == 1


  model = args.first

  # assert model responds_to search_api_bridge
  raise ArgumentError.new("#{model} doesn't respond to search_api_bridge") unless model.respond_to?(:search_api_bridge)

  # set model
  @model = model

  # infer automatics search accessors from model
  add_automatic_search_attributes(options)

  nil # don't pollute class creation
end

.search_accessor(*args, &block) ⇒ Object

This is how you add search attributes to your SearchApi::Search::Base class.

Adding a search attribute has the following consequences:

  • A writer, a reader, an interrogation reader, and a ignored reader are defined.

    Writer and reader act as usual. Interrogation reader acts as ActiveRecord::Base’s one.

    Ignorer reader tells whether the search attribute is ignored or not.

    # Defines following StuffSearch instance methods:
    # - :a, :a=, :a? and :a_ignored?
    # - :b, :b=, :b? and :b_ignored?
    class StuffSearch < SearchApi::Search::Base
      model Stuff
      search_accessor :a, :b
    end
    
  • The method find_options_for_[search attribute] is defined, if block is provided.

The optional block takes a single parameter: a SearchApi::Search::Base instance.

Its result should be enough to define a model search.

In case of ActiveRecord models, it should be a valid Hash that can be used as ActiveRecord::Base.find argument.

Example:

class StuffSearch < SearchApi::Search::Base
  model Stuff
  search_accessor :max_age do |search|
    { :conditions => ['birth_date > ?', Time.now - search.max_age.years]}
  end
end

You can avoid passing a block, and define the find_options_for_[search attribute] method later:

class StuffSearch < SearchApi::Search::Base
  model Stuff
  search_accessor :max_age
  def find_options_for_max_age
    { :conditions => ['birth_date > ?', Time.now - max_age.years]}
  end
end


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/search_api/search.rb', line 151

def search_accessor(*args, &block)

  # extract SearchAttributeBuilder instances from arguments

  search_attributes_builders = if block.nil? && args.length == 1 && args.first.is_a?(SearchAttributeBuilder)
    # argument is a single SearchAttributeBuilder instance
  
    args
  
  else
    # arguments are search attribute names and options
  
    options = if args.last.is_a?(Hash) then args.pop else {} end
    args.map do |search_attribute|
      SearchAttributeBuilder.new(search_attribute, options, &block)
    end
  end


  # define search attributes from builders

  search_attributes_builders.each do |builder|
    rewrite_search_attribute_builder(builder)
    add_search_attribute(builder)
  end

  nil # don't pollute class creation
end

.search_attributesObject

Returns an unordered Array of all search attributes defined through search_accessor.

Example:

class StuffSearch < SearchApi::Search::Base
  search_accessor :search_key1, :search_key2
end

StuffSearch.search_attributes # => [:search_key1, :search_key2]


189
190
191
# File 'lib/search_api/search.rb', line 189

def search_attributes
  read_inheritable_attribute(:search_attributes) || []
end

Instance Method Details

#attributesObject

Returns a Hash of search attributes.



363
364
365
366
367
# File 'lib/search_api/search.rb', line 363

def attributes
  self.class.search_attributes.inject({}) do |attributes, search_attribute|
    attributes.update(search_attribute => send(search_attribute))
  end
end

#attributes=(attributes = nil) ⇒ Object

Sets search attributes via a Hash



370
371
372
373
374
# File 'lib/search_api/search.rb', line 370

def attributes=(attributes=nil)
  (attributes || {}).each do |search_attribute, value|
    send("#{search_attribute}=", value)
  end
end

#find_optionsObject

Returns an object that should be enough to define a model search.

In case of ActiveRecord models, returns a valid Hash that can be used as ActiveRecord::Base.find argument.



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/search_api/search.rb', line 393

def find_options
  # collect all find_options for not ignored attributes

  options_array = self.class.search_attributes.

    # reject ignored attributes
    reject { |search_attribute| ignored?(search_attribute) }.

    # merge options for all attributes
    map { |search_attribute| send("find_options_for_#{search_attribute}") }

                    
  # merge them options for not ignored attributes

  self.class.model.search_api_bridge.merge_find_options(options_array)
end

#ignore!(search_attribute) ⇒ Object

Ignore given search_attribute.



383
384
385
# File 'lib/search_api/search.rb', line 383

def ignore!(search_attribute)
  send("#{search_attribute}=", SearchApi::Search.ignored)
end

#ignored?(search_attribute) ⇒ Boolean

Returns whether search_attribute is ignored.

Returns:

  • (Boolean)


378
379
380
# File 'lib/search_api/search.rb', line 378

def ignored?(search_attribute)
  SearchApi::Search.ignored?(send(search_attribute))
end