| 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:
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)
| VALID_SEARCH_ATTRIBUTE_OPTIONS | = | [ :store_as, :default ] |
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:
Example:
class StuffSearch < SearchApi::Search::Base
# search for Stuff
model Stuff
...
end
StuffSearch.model # => Stuff
# 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.
# 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:
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 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
# 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]
# File lib/search_api/search.rb, line 189
189: def search_attributes
190: read_inheritable_attribute(:search_attributes) || []
191: end
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.options keys must be in VALID_SEARCH_ATTRIBUTE_OPTIONS.
Adding a search attributes, precisely, means:
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
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
# 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
Returns a Hash of search attributes.
# 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
# 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.
# 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.
# File lib/search_api/search.rb, line 383
383: def ignore!(search_attribute)
384: send("#{search_attribute}=", SearchApi::Search.ignored)
385: end