meta_search 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitmodules +6 -0
- data/Gemfile +7 -0
- data/README.rdoc +165 -26
- data/Rakefile +18 -5
- data/VERSION +1 -1
- data/lib/meta_search.rb +28 -18
- data/lib/meta_search/builder.rb +178 -125
- data/lib/meta_search/exceptions.rb +1 -0
- data/lib/meta_search/helpers.rb +3 -0
- data/lib/meta_search/helpers/form_builder.rb +152 -0
- data/lib/meta_search/helpers/form_helper.rb +20 -0
- data/lib/meta_search/helpers/url_helper.rb +39 -0
- data/lib/meta_search/method.rb +129 -0
- data/lib/meta_search/model_compatibility.rb +36 -2
- data/lib/meta_search/searches/active_record.rb +88 -11
- data/lib/meta_search/utility.rb +1 -1
- data/lib/meta_search/where.rb +119 -61
- data/meta_search.gemspec +33 -13
- data/test/fixtures/company.rb +15 -2
- data/test/fixtures/data_types.yml +3 -3
- data/test/fixtures/developer.rb +3 -0
- data/test/helper.rb +10 -6
- data/test/test_search.rb +502 -288
- data/test/test_view_helpers.rb +152 -53
- metadata +82 -15
- data/lib/meta_search/helpers/action_view.rb +0 -168
- data/lib/meta_search/railtie.rb +0 -21
- data/lib/meta_search/searches/base.rb +0 -46
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
require 'action_view/template'
|
3
|
+
module MetaSearch
|
4
|
+
Check = Struct.new(:box, :label)
|
5
|
+
|
6
|
+
module Helpers
|
7
|
+
module FormBuilder
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
self.field_helpers += ['multiparameter_field', 'check_boxes', 'collection_check_boxes']
|
12
|
+
end
|
13
|
+
|
14
|
+
# Like other form_for field methods (text_field, hidden_field, password_field) etc,
|
15
|
+
# but takes a list of hashes between the +method+ parameter and the trailing option hash,
|
16
|
+
# if any, to specify a number of fields to create in multiparameter fashion.
|
17
|
+
#
|
18
|
+
# Each hash *must* contain a :field_type option, which specifies a form_for method, and
|
19
|
+
# _may_ contain an optional :type_cast option, with one of the typical multiparameter
|
20
|
+
# type cast characters. Any remaining options will be merged with the defaults specified
|
21
|
+
# in the trailing option hash and passed along when creating that field.
|
22
|
+
#
|
23
|
+
# For example...
|
24
|
+
#
|
25
|
+
# <%= f.multiparameter_field :moderations_value_between,
|
26
|
+
# {:field_type => :text_field, :class => 'first'},
|
27
|
+
# {:field_type => :text_field, :type_cast => 'i'},
|
28
|
+
# :size => 5 %>
|
29
|
+
#
|
30
|
+
# ...will create the following HTML:
|
31
|
+
#
|
32
|
+
# <input class="first" id="search_moderations_value_between(1)"
|
33
|
+
# name="search[moderations_value_between(1)]" size="5" type="text" />
|
34
|
+
#
|
35
|
+
# <input id="search_moderations_value_between(2i)"
|
36
|
+
# name="search[moderations_value_between(2i)]" size="5" type="text" />
|
37
|
+
#
|
38
|
+
# As with any multiparameter input fields, these will be concatenated into an
|
39
|
+
# array and passed to the attribute named by the first parameter for assignment.
|
40
|
+
def multiparameter_field(method, *args)
|
41
|
+
defaults = has_multiparameter_defaults?(args) ? args.pop : {}
|
42
|
+
raise ArgumentError, "No multiparameter fields specified" if args.blank?
|
43
|
+
html = ''.html_safe
|
44
|
+
args.each_with_index do |field, index|
|
45
|
+
type = field.delete(:field_type) || raise(ArgumentError, "No :field_type specified.")
|
46
|
+
cast = field.delete(:type_cast) || ''
|
47
|
+
opts = defaults.merge(field)
|
48
|
+
html.safe_concat(
|
49
|
+
@template.send(
|
50
|
+
type.to_s,
|
51
|
+
@object_name,
|
52
|
+
(method.to_s + "(#{index + 1}#{cast})"),
|
53
|
+
objectify_options(opts))
|
54
|
+
)
|
55
|
+
end
|
56
|
+
html
|
57
|
+
end
|
58
|
+
|
59
|
+
# Behaves almost exactly like the select method, but instead of generating a select tag,
|
60
|
+
# generates <tt>MetaSearch::Check</tt>s. These consist of two attributes, +box+ and +label+,
|
61
|
+
# which are (unsurprisingly) the HTML for the check box and the label. Called without a block,
|
62
|
+
# this method will return an array of check boxes. Called with a block, it will yield each
|
63
|
+
# check box to your template.
|
64
|
+
#
|
65
|
+
# *Parameters:*
|
66
|
+
#
|
67
|
+
# * +method+ - The method name on the form_for object
|
68
|
+
# * +choices+ - An array of arrays, the first value in each element is the text for the
|
69
|
+
# label, and the last is the value for the checkbox
|
70
|
+
# * +options+ - An options hash to be passed through to the checkboxes
|
71
|
+
#
|
72
|
+
# *Examples:*
|
73
|
+
#
|
74
|
+
# <b>Simple formatting:</b>
|
75
|
+
#
|
76
|
+
# <h4>How many heads?</h4>
|
77
|
+
# <ul>
|
78
|
+
# <% f.check_boxes :number_of_heads_in,
|
79
|
+
# [['One', 1], ['Two', 2], ['Three', 3]], :class => 'checkboxy' do |check| %>
|
80
|
+
# <li>
|
81
|
+
# <%= check.box %>
|
82
|
+
# <%= check.label %>
|
83
|
+
# </li>
|
84
|
+
# <% end %>
|
85
|
+
# </ul>
|
86
|
+
#
|
87
|
+
# This example will output the checkboxes and labels in an unordered list format.
|
88
|
+
#
|
89
|
+
# <b>Grouping:</b>
|
90
|
+
#
|
91
|
+
# Chain <tt>in_groups_of(<num>, false)</tt> on check_boxes like so:
|
92
|
+
# <h4>How many heads?</h4>
|
93
|
+
# <p>
|
94
|
+
# <% f.check_boxes(:number_of_heads_in,
|
95
|
+
# [['One', 1], ['Two', 2], ['Three', 3]],
|
96
|
+
# :class => 'checkboxy').in_groups_of(2, false) do |checks| %>
|
97
|
+
# <% checks.each do |check| %>
|
98
|
+
# <%= check.box %>
|
99
|
+
# <%= check.label %>
|
100
|
+
# <% end %>
|
101
|
+
# <br />
|
102
|
+
# <% end %>
|
103
|
+
# </p>
|
104
|
+
def check_boxes(method, choices = [], options = {}, &block)
|
105
|
+
unless choices.first.respond_to?(:first) && choices.first.respond_to?(:last)
|
106
|
+
raise ArgumentError, 'invalid choice array specified'
|
107
|
+
end
|
108
|
+
collection_check_boxes(method, choices, :last, :first, options, &block)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Just like +check_boxes+, but this time you can pass in a collection, value, and text method,
|
112
|
+
# as with collection_select.
|
113
|
+
#
|
114
|
+
# Example:
|
115
|
+
#
|
116
|
+
# <%= f.collection_check_boxes :head_sizes_in, HeadSize.all,
|
117
|
+
# :id, :name, :class => 'headcheck' do |check| %>
|
118
|
+
# <%= check.box %> <%= check.label %>
|
119
|
+
# <% end %>
|
120
|
+
def collection_check_boxes(method, collection, value_method, text_method, options = {}, &block)
|
121
|
+
check_boxes = []
|
122
|
+
collection.each do |choice|
|
123
|
+
text = choice.send(text_method)
|
124
|
+
value = choice.send(value_method)
|
125
|
+
check = MetaSearch::Check.new
|
126
|
+
check.box = @template.check_box_tag(
|
127
|
+
"#{@object_name}[#{method}][]",
|
128
|
+
value,
|
129
|
+
[@object.send(method)].flatten.include?(value),
|
130
|
+
options.merge(:id => [@object_name, method.to_s, value.to_s.underscore].join('_'))
|
131
|
+
)
|
132
|
+
check.label = @template.label_tag([@object_name, method.to_s, value.to_s.underscore].join('_'),
|
133
|
+
text)
|
134
|
+
if block_given?
|
135
|
+
yield check
|
136
|
+
else
|
137
|
+
check_boxes << check
|
138
|
+
end
|
139
|
+
end
|
140
|
+
check_boxes unless block_given?
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# If the last element of the arguments to multiparameter_field has no :field_type
|
146
|
+
# key, we assume it's got some defaults to be used in the other hashes.
|
147
|
+
def has_multiparameter_defaults?(args)
|
148
|
+
args.size > 1 && args.last.is_a?(Hash) && !args.last.has_key?(:field_type)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module MetaSearch
|
2
|
+
module Helpers
|
3
|
+
module FormHelper
|
4
|
+
def apply_form_for_options!(object_or_array, options)
|
5
|
+
if object_or_array.is_a?(Array) && object_or_array.first.is_a?(MetaSearch::Builder)
|
6
|
+
builder = object_or_array.first
|
7
|
+
html_options = {
|
8
|
+
:class => options[:as] ? "#{options[:as]}_search" : "#{builder.base.to_s.underscore}_search",
|
9
|
+
:id => options[:as] ? "#{options[:as]}_search" : "#{builder.base.to_s.underscore}_search",
|
10
|
+
:method => :get }
|
11
|
+
options[:html] ||= {}
|
12
|
+
options[:html].reverse_merge!(html_options)
|
13
|
+
options[:url] ||= polymorphic_path(builder.base)
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MetaSearch
|
2
|
+
module Helpers
|
3
|
+
module UrlHelper
|
4
|
+
|
5
|
+
def sort_link(builder, attribute, *args)
|
6
|
+
raise ArgumentError, "Need a MetaSearch::Builder search object as first param!" unless builder.is_a?(MetaSearch::Builder)
|
7
|
+
attr_name = attribute.to_s
|
8
|
+
name = (args.size > 0 && !args.first.is_a?(Hash)) ? args.shift.to_s : attr_name.humanize
|
9
|
+
prev_attr, prev_order = builder.search_attributes['meta_sort'].to_s.split('.')
|
10
|
+
current_order = prev_attr == attr_name ? prev_order : nil
|
11
|
+
new_order = current_order == 'asc' ? 'desc' : 'asc'
|
12
|
+
options = args.first.is_a?(Hash) ? args.shift : {}
|
13
|
+
html_options = args.first.is_a?(Hash) ? args.shift : {}
|
14
|
+
css = ['sort_link', current_order].compact.join(' ')
|
15
|
+
html_options[:class] = [css, html_options[:class]].compact.join(' ')
|
16
|
+
options.merge!(
|
17
|
+
'search' => builder.search_attributes.merge(
|
18
|
+
'meta_sort' => [attr_name, new_order].join('.')
|
19
|
+
)
|
20
|
+
)
|
21
|
+
link_to [ERB::Util.h(name), order_indicator_for(current_order)].compact.join(' ').html_safe,
|
22
|
+
url_for(options),
|
23
|
+
html_options
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def order_indicator_for(order)
|
29
|
+
if order == 'asc'
|
30
|
+
'▲'
|
31
|
+
elsif order == 'desc'
|
32
|
+
'▼'
|
33
|
+
else
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'meta_search/utility'
|
2
|
+
|
3
|
+
module MetaSearch
|
4
|
+
# MetaSearch can be given access to any class method on your model to extend its search capabilities.
|
5
|
+
# The only rule is that the method must return an ActiveRecord::Relation so that MetaSearch can
|
6
|
+
# continue to extend the search with other attributes. Conveniently, scopes (formerly "named scopes")
|
7
|
+
# do this already.
|
8
|
+
#
|
9
|
+
# Consider the following model:
|
10
|
+
#
|
11
|
+
# class Company < ActiveRecord::Base
|
12
|
+
# has_many :slackers, :class_name => "Developer", :conditions => {:slacker => true}
|
13
|
+
# scope :backwards_name, lambda {|name| where(:name => name.reverse)}
|
14
|
+
# scope :with_slackers_by_name_and_salary_range,
|
15
|
+
# lambda {|name, low, high|
|
16
|
+
# joins(:slackers).where(:developers => {:name => name, :salary => low..high})
|
17
|
+
# }
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# To allow MetaSearch access to a model method, including a named scope, just use
|
21
|
+
# <tt>search_methods</tt> in the model:
|
22
|
+
#
|
23
|
+
# search_methods :backwards_name
|
24
|
+
#
|
25
|
+
# This will allow you to add a text field named :backwards_name to your search form, and
|
26
|
+
# it will behave as you might expect.
|
27
|
+
#
|
28
|
+
# In the case of the second scope, we have multiple parameters to pass in, of different
|
29
|
+
# types. We can pass the following to <tt>search_methods</tt>:
|
30
|
+
#
|
31
|
+
# search_methods :with_slackers_by_name_and_salary_range,
|
32
|
+
# :splat_param => true, :type => [:string, :integer, :integer]
|
33
|
+
#
|
34
|
+
# MetaSearch needs us to tell it that we don't want to keep the array supplied to it as-is, but
|
35
|
+
# "splat" it when passing it to the model method. And in this case, ActiveRecord would have been
|
36
|
+
# smart enough to handle the typecasting for us, but I wanted to demonstrate how we can tell
|
37
|
+
# MetaSearch that a given parameter is of a specific database "column type." This is just a hint
|
38
|
+
# MetaSearch uses in the same way it does when casting "Where" params based on the DB column
|
39
|
+
# being searched. It's also important so that things like dates get handled properly by FormBuilder.
|
40
|
+
#
|
41
|
+
# _NOTE_: If you do supply an array, rather than a single type value, to <tt>:type</tt>, MetaSearch
|
42
|
+
# will enforce that any array supplied for input by your forms has the correct number of elements
|
43
|
+
# for your eventual method.
|
44
|
+
#
|
45
|
+
# Besides <tt>:splat_param</tt> and <tt>:type</tt>, search_methods accept the same <tt>:formatter</tt>
|
46
|
+
# and <tt>:validator</tt> options that you would use when adding a new MetaSearch::Where:
|
47
|
+
#
|
48
|
+
# <tt>formatter</tt> is the Proc that will do any formatting to the variable passed to your method.
|
49
|
+
# The default proc is <tt>{|param| param}</tt>, which doesn't really do anything. If you pass a
|
50
|
+
# string, it will be +eval+ed in the context of this Proc.
|
51
|
+
#
|
52
|
+
# If your method will do a LIKE search against its parameter, you might want to pass:
|
53
|
+
#
|
54
|
+
# :formatter => '"%#{param}%"'
|
55
|
+
#
|
56
|
+
# Be sure to single-quote the string, so that variables aren't interpolated until later. If in doubt,
|
57
|
+
# just use a Proc, like so:
|
58
|
+
#
|
59
|
+
# :formatter => Proc.new {|param| "%#{param}%"}
|
60
|
+
#
|
61
|
+
# <tt>validator</tt> is the Proc that will be used to check whether a parameter supplied to the
|
62
|
+
# method is valid. If it is not valid, it won't be used in the query. The default is
|
63
|
+
# <tt>{|param| !param.blank?}</tt>, so that empty parameters aren't added to the search, but you
|
64
|
+
# can get more complex if you desire. Validations are run after typecasting, so you can check
|
65
|
+
# the class of your parameters, for instance.
|
66
|
+
class Method
|
67
|
+
include Utility
|
68
|
+
|
69
|
+
attr_reader :name, :formatter, :validator, :type
|
70
|
+
|
71
|
+
def initialize(name, opts ={})
|
72
|
+
raise ArgumentError, "Name parameter required" if name.blank?
|
73
|
+
@name = name
|
74
|
+
@type = opts[:type] || :string
|
75
|
+
@splat_param = opts[:splat_param] || false
|
76
|
+
@formatter = opts[:formatter] || Proc.new {|param| param}
|
77
|
+
if @formatter.is_a?(String)
|
78
|
+
formatter = @formatter
|
79
|
+
@formatter = Proc.new {|param| eval formatter}
|
80
|
+
end
|
81
|
+
unless @formatter.respond_to?(:call)
|
82
|
+
raise ArgumentError, "Invalid formatter for #{name}, should be a Proc or String."
|
83
|
+
end
|
84
|
+
@validator = opts[:validator] || Proc.new {|param| !param.blank?}
|
85
|
+
unless @validator.respond_to?(:call)
|
86
|
+
raise ArgumentError, "Invalid validator for #{name}, should be a Proc."
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Cast the parameter to the type specified in the Method's <tt>type</tt>
|
91
|
+
def cast_param(param)
|
92
|
+
if type.is_a?(Array)
|
93
|
+
unless param.is_a?(Array) && param.size == type.size
|
94
|
+
num_params = param.is_a?(Array) ? param.size : 1
|
95
|
+
raise ArgumentError, "Parameters supplied to #{name} could not be type cast -- #{num_params} values supplied, #{type.size} expected"
|
96
|
+
end
|
97
|
+
type.each_with_index do |t, i|
|
98
|
+
param[i] = cast_attributes(t, param[i])
|
99
|
+
end
|
100
|
+
param
|
101
|
+
else
|
102
|
+
cast_attributes(type, param)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Evaluate the method in the context of the supplied relation and parameter
|
107
|
+
def eval(relation, param)
|
108
|
+
if splat_param?
|
109
|
+
relation.send(name, *format_param(param))
|
110
|
+
else
|
111
|
+
relation.send(name, format_param(param))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def splat_param?
|
116
|
+
!!@splat_param
|
117
|
+
end
|
118
|
+
|
119
|
+
# Format a parameter for searching using the Method's defined formatter.
|
120
|
+
def format_param(param)
|
121
|
+
formatter.call(param)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Validate the parameter for use in a search using the Method's defined validator.
|
125
|
+
def validate(param)
|
126
|
+
validator.call(param)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -1,8 +1,42 @@
|
|
1
1
|
module MetaSearch
|
2
2
|
# Just a little module to mix in so that ActionPack doesn't complain.
|
3
3
|
module ModelCompatibility
|
4
|
-
def
|
5
|
-
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
6
|
end
|
7
|
+
|
8
|
+
# Force default "Update search" text
|
9
|
+
def persisted?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_key
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_param
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
class Name < String
|
22
|
+
attr_reader :singular, :plural, :element, :collection, :partial_path, :human
|
23
|
+
alias_method :cache_key, :collection
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
super("Search")
|
27
|
+
@singular = "search".freeze
|
28
|
+
@plural = "searches".freeze
|
29
|
+
@element = "search".freeze
|
30
|
+
@human = "Search".freeze
|
31
|
+
@collection = "meta_search/searches".freeze
|
32
|
+
@partial_path = "#{@collection}/#{@element}".freeze
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
def model_name
|
38
|
+
@_model_name ||= Name.new
|
39
|
+
end
|
40
|
+
end
|
7
41
|
end
|
8
42
|
end
|
@@ -1,18 +1,95 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'meta_search/method'
|
3
|
+
require 'meta_search/builder'
|
3
4
|
|
4
5
|
module MetaSearch::Searches
|
5
6
|
module ActiveRecord
|
6
|
-
|
7
|
+
extend ActiveSupport::Concern
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
self._metasearch_exclude_attributes =
|
14
|
-
self._metasearch_exclude_associations =
|
15
|
-
|
9
|
+
included do
|
10
|
+
class_attribute :_metasearch_include_attributes, :_metasearch_exclude_attributes
|
11
|
+
class_attribute :_metasearch_include_associations, :_metasearch_exclude_associations
|
12
|
+
class_attribute :_metasearch_methods
|
13
|
+
self._metasearch_include_attributes =
|
14
|
+
self._metasearch_exclude_attributes =
|
15
|
+
self._metasearch_exclude_associations =
|
16
|
+
self._metasearch_include_associations = []
|
17
|
+
self._metasearch_methods = {}
|
18
|
+
|
19
|
+
singleton_class.instance_eval do
|
20
|
+
alias_method :metasearch_include_attr, :attr_searchable
|
21
|
+
alias_method :metasearch_exclude_attr, :attr_unsearchable
|
22
|
+
alias_method :metasearch_include_assoc, :assoc_searchable
|
23
|
+
alias_method :metasearch_exclude_assoc, :assoc_unsearchable
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
# Prepares the search to run against your model. Returns an instance of
|
29
|
+
# MetaSearch::Builder, which behaves pretty much like an ActiveRecord::Relation,
|
30
|
+
# in that it doesn't actually query the database until you do something that
|
31
|
+
# requires it to do so.
|
32
|
+
def search(opts = {})
|
33
|
+
opts ||= {} # to catch nil params
|
34
|
+
search_options = opts.delete(:search_options) || {}
|
35
|
+
builder = MetaSearch::Builder.new(self, search_options)
|
36
|
+
builder.build(opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Excludes model attributes from searchability. This means that searches can't be created against
|
42
|
+
# these columns, whether the search is based on this model, or the model's attributes are being
|
43
|
+
# searched by association from another model. If a Comment <tt>belongs_to :article</tt> but declares
|
44
|
+
# <tt>attr_unsearchable :user_id</tt> then <tt>Comment.search</tt> won't accept parameters
|
45
|
+
# like <tt>:user_id_equals</tt>, nor will an Article.search accept the parameter
|
46
|
+
# <tt>:comments_user_id_equals</tt>.
|
47
|
+
def attr_unsearchable(*args)
|
48
|
+
args.flatten.each do |attr|
|
49
|
+
attr = attr.to_s
|
50
|
+
raise(ArgumentError, "No persisted attribute (column) named #{attr} in #{self}") unless self.columns_hash.has_key?(attr)
|
51
|
+
self._metasearch_exclude_attributes = (self._metasearch_exclude_attributes + [attr]).uniq
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Like <tt>attr_unsearchable</tt>, but operates as a whitelist rather than blacklist. If both
|
56
|
+
# <tt>attr_searchable</tt> and <tt>attr_unsearchable</tt> are present, the latter
|
57
|
+
# is ignored.
|
58
|
+
def attr_searchable(*args)
|
59
|
+
args.flatten.each do |attr|
|
60
|
+
attr = attr.to_s
|
61
|
+
raise(ArgumentError, "No persisted attribute (column) named #{attr} in #{self}") unless self.columns_hash.has_key?(attr)
|
62
|
+
self._metasearch_include_attributes = (self._metasearch_include_attributes + [attr]).uniq
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Excludes model associations from searchability. This mean that searches can't be created against
|
67
|
+
# these associations. An article that <tt>has_many :comments</tt> but excludes comments from
|
68
|
+
# searching by declaring <tt>assoc_unsearchable :comments</tt> won't make any of the
|
69
|
+
# <tt>comments_*</tt> methods available.
|
70
|
+
def assoc_unsearchable(*args)
|
71
|
+
args.flatten.each do |assoc|
|
72
|
+
assoc = assoc.to_s
|
73
|
+
raise(ArgumentError, "No such association #{assoc} in #{self}") unless self.reflect_on_all_associations.map {|a| a.name.to_s}.include?(assoc)
|
74
|
+
self._metasearch_exclude_associations = (self._metasearch_exclude_associations + [assoc]).uniq
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# As with <tt>attr_searchable</tt> this is the whitelist version of
|
79
|
+
# <tt>assoc_unsearchable</tt>
|
80
|
+
def assoc_searchable(*args)
|
81
|
+
args.flatten.each do |assoc|
|
82
|
+
assoc = assoc.to_s
|
83
|
+
raise(ArgumentError, "No such association #{assoc} in #{self}") unless self.reflect_on_all_associations.map {|a| a.name.to_s}.include?(assoc)
|
84
|
+
self._metasearch_include_associations = (self._metasearch_include_associations + [assoc]).uniq
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def search_methods(*args)
|
89
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
90
|
+
args.flatten.map(&:to_s).each do |arg|
|
91
|
+
self._metasearch_methods[arg] = MetaSearch::Method.new(arg, opts)
|
92
|
+
end
|
16
93
|
end
|
17
94
|
end
|
18
95
|
end
|