meta_search 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|