Class SearchApi::Search::Base
In: lib/search_api/search.rb
lib/search_api.rb
Parent: Object

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)

Methods

Included Modules

SearchApi::Search::Callbacks

Constants

VALID_SEARCH_ATTRIBUTE_OPTIONS = [ :store_as, :default ]

Public Class methods

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

[Source]

     # File lib/search_api/search.rb, line 76
 76:         def model(*args)
 77:           # returns model when no arguments
 78:           return @model if args.empty?
 79:         
 80:           # can't set model twice
 81:           raise "model is already set" if @model
 82:         
 83:           # fetch optional options
 84:           options = if args.last.is_a?(Hash) then args.pop else {} end
 85:         
 86:           # make sure model is the only last argument
 87:           raise ArgumentError.new("Bad arguments for model") unless args.length == 1
 88:         
 89:         
 90:           model = args.first
 91:         
 92:           # assert model responds_to search_api_bridge
 93:           raise ArgumentError.new("#{model} doesn't respond to search_api_bridge") unless model.respond_to?(:search_api_bridge)
 94:         
 95:           # set model
 96:           @model = model
 97:         
 98:           # infer automatics search accessors from model
 99:           add_automatic_search_attributes(options)
100:         
101:           nil # don't pollute class creation
102:         end

Initializes a search with a search attributes Hash.

[Source]

     # File lib/search_api/search.rb, line 347
347:       def initialize(attributes=nil)
348:         raise "Can't create an instance without model" if self.class.model.nil?
349:       
350:         # initialize attributes with ignored value
351:         self.attributes = self.class.search_attributes.inject((attributes || {}).dup) do |attributes, search_attribute|
352:           if attributes.has_key?(search_attribute) || attributes.has_key?(search_attribute.to_s)
353:             attributes
354:           else
355:             attributes.update(search_attribute => self.class.read_inheritable_attribute(:search_attribute_default_values)[search_attribute])
356:           end
357:         end
358:       end

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

[Source]

     # File lib/search_api/search.rb, line 151
151:         def search_accessor(*args, &block)
152:         
153:           # extract SearchAttributeBuilder instances from arguments
154:         
155:           search_attributes_builders = if block.nil? && args.length == 1 && args.first.is_a?(SearchAttributeBuilder)
156:             # argument is a single SearchAttributeBuilder instance
157:           
158:             args
159:           
160:           else
161:             # arguments are search attribute names and options
162:           
163:             options = if args.last.is_a?(Hash) then args.pop else {} end
164:             args.map do |search_attribute|
165:               SearchAttributeBuilder.new(search_attribute, options, &block)
166:             end
167:           end
168:         
169:         
170:           # define search attributes from builders
171:         
172:           search_attributes_builders.each do |builder|
173:             rewrite_search_attribute_builder(builder)
174:             add_search_attribute(builder)
175:           end
176:         
177:           nil # don't pollute class creation
178:         end

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]

[Source]

     # File lib/search_api/search.rb, line 189
189:         def search_attributes
190:           read_inheritable_attribute(:search_attributes) || []
191:         end

Protected Class methods

Unless you‘re an SearchApi::Bridge::Base subclass designer, you should use search_accessor method instead.

search_attribute_builder is a SearchAttributeBuilder instance.

This methods adds a search attribute to that SearchApi::Search::Base class:

  • search_attribute_builder.name is the name of the search attribute,
  • search_attribute_builder.options are options for defining the search attribute,
  • search_attribute_builder.block is an optional proc that implement the search attribute behavior.

search_attribute_builder.options keys must be in VALID_SEARCH_ATTRIBUTE_OPTIONS.

Adding a search attributes, precisely, means:

  • 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?
      # - :a_ignored?
      class StuffSearch < SearchApi::Search::Base
        model Stuff
        add_search_attribute(SearchApi::Search::SearchAttributeBuilder.new(:a))
      end
    
  • The method find_options_for_[search attribute] is defined, if the builder‘s block is set.

    That 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.

      # Defines following StuffSearch instance methods:
      # - :a,
      # - :a=
      # - :a?
      # - :a_ignored?
      # - :find_options_for_a
      class StuffSearch < SearchApi::Search::Base
        model Stuff
        add_search_attribute(SearchApi::Search::SearchAttributeBuilder.new(:max_age)) do |search|
          { :conditions => ['birth_date > ?', Time.now - search.max_age.years]}
        end
      end
    

You can avoid defining the builder‘s block, and define the find_options_for_[search attribute] method yourself.

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

[Source]

     # File lib/search_api/search.rb, line 259
259:         def add_search_attribute(search_attribute_builder)
260:           search_attribute =  search_attribute_builder.name
261:           options =           search_attribute_builder.options
262:           block =             search_attribute_builder.block
263: 
264: 
265:           # check options
266:           options ||= {}
267:           invalid_options = options.keys - VALID_SEARCH_ATTRIBUTE_OPTIONS
268:           raise ArgumentError.new("invalid options #{invalid_options.inspect}") unless invalid_options.empty?
269: 
270: 
271:           # fill search_attributes array
272:           write_inheritable_array(:search_attributes, [search_attribute])
273:         
274:         
275:           # store default value
276:           options[:default] ||= SearchApi::Search.ignored
277:           write_inheritable_hash(:search_attribute_default_values, { search_attribute => options[:default]})
278:         
279: 
280:           # define reader
281:           attr_reader search_attribute
282: 
283: 
284:           # define writer
285:           if store_as_proc = options[:store_as]
286:             define_method("#{search_attribute}=") do |value|
287:               instance_variable_set("@#{search_attribute}", if SearchApi::Search.ignored?(value) then value else store_as_proc.call(value) end)
288:             end
289:           else
290:             attr_writer search_attribute
291:           end
292: 
293: 
294:           # define interrogation reader
295:           define_method("#{search_attribute}?") do
296:             !!send(search_attribute)
297:           end
298: 
299: 
300:           # define ignored reader
301:           define_method("#{search_attribute}_ignored?") do
302:             SearchApi::Search.ignored?(send(search_attribute))
303:           end
304: 
305: 
306:           # Define find_options_for_xxx method
307:           if block
308:             # user-defined method
309:             define_method("find_options_for_#{search_attribute}") do
310:               if SearchApi::Search.ignored?(send(search_attribute))
311:                 {}
312:               else
313:                 block.call(self)
314:               end
315:             end
316:           end
317:         end

Public Instance methods

Returns a Hash of search attributes.

[Source]

     # File lib/search_api/search.rb, line 363
363:       def attributes
364:         self.class.search_attributes.inject({}) do |attributes, search_attribute|
365:           attributes.update(search_attribute => send(search_attribute))
366:         end
367:       end

Sets search attributes via a Hash

[Source]

     # File lib/search_api/search.rb, line 370
370:       def attributes=(attributes=nil)
371:         (attributes || {}).each do |search_attribute, value|
372:           send("#{search_attribute}=", value)
373:         end
374:       end

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.

[Source]

     # File lib/search_api/search.rb, line 393
393:       def find_options
394:         # collect all find_options for not ignored attributes
395:       
396:         options_array = self.class.search_attributes.
397:       
398:           # reject ignored attributes
399:           reject { |search_attribute| ignored?(search_attribute) }.
400: 
401:           # merge options for all attributes
402:           map { |search_attribute| send("find_options_for_#{search_attribute}") }
403: 
404:                           
405:         # merge them options for not ignored attributes
406:       
407:         self.class.model.search_api_bridge.merge_find_options(options_array)
408:       end

Ignore given search_attribute.

[Source]

     # File lib/search_api/search.rb, line 383
383:       def ignore!(search_attribute)
384:         send("#{search_attribute}=", SearchApi::Search.ignored)
385:       end

Returns whether search_attribute is ignored.

[Source]

     # File lib/search_api/search.rb, line 378
378:       def ignored?(search_attribute)
379:         SearchApi::Search.ignored?(send(search_attribute))
380:       end

[Validate]