meta_search 0.3.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ernie Miller
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,101 @@
1
+ = MetaSearch
2
+
3
+ Extensible searching for your form_for enjoyment.
4
+
5
+ == Getting Started
6
+
7
+ Add a line to your Gemfile:
8
+
9
+ gem "meta_search"
10
+
11
+ In your controller:
12
+
13
+ def index
14
+ @search = Article.search(params[:search])
15
+ @articles = @search.all
16
+ end
17
+
18
+ In your view:
19
+
20
+ <% form_for :search, @search, :html => {:method => :get} do |f| %>
21
+ <%= f.label :title_contains %>
22
+ <%= f.text_field :title_contains %><br />
23
+ <%= f.label :comments_created_at_greater_than, 'With comments after' %>
24
+ <%= f.datetime_select :comments_created_at_greater_than, :include_blank => true %><br />
25
+ <!-- etc... -->
26
+ <%= f.submit %>
27
+ <% end %>
28
+
29
+ The default Where types are listed at MetaSearch::Where. Options for the search method are documented at MetaSearch::Searches::Base.
30
+
31
+ == Advanced usage
32
+
33
+ === Adding a new Where
34
+
35
+ If none of the built-in search criteria work for you, you can add a new Where (or 5). To do so,
36
+ create an initializer (<tt>/config/initializers/meta_search.rb</tt>, for instance) and add lines
37
+ like:
38
+
39
+ MetaSearch::Where.add :between, :btw, {:condition => 'BETWEEN', :substitutions => '? AND ?'}
40
+
41
+ See MetaSearch::Where for info on the supported options.
42
+
43
+ === multiparameter_field
44
+
45
+ The example Where above adds support for a "between" search, which requires an array with
46
+ two parameters. These can be passed using Rails multiparameter attributes. To make life easier,
47
+ MetaSearch adds a helper for this:
48
+
49
+ <%= f.multiparameter_field :moderations_value_between,
50
+ {:field_type => :text_field}, {:field_type => :text_field}, :size => 5 %>
51
+
52
+ <tt>multiparameter_field</tt> works pretty much like the other FormBuilder helpers, but it
53
+ lets you sandwich a list of fields, each in hash format, between the attribute and the usual
54
+ options hash. See MetaSearch::Helpers::FormBuilder for more info.
55
+
56
+ === check_boxes and collection_check_boxes
57
+
58
+ If you need to get an array into your where, and you don't care about parameter order,
59
+ you might choose to use a select or collection_select with multiple selection enabled,
60
+ but everyone hates multiple selection boxes. MetaSearch adds a couple of additional
61
+ helpers, +check_boxes+ and +collection_check_boxes+ to handle multiple selections in a
62
+ more visually appealing manner. It can be called with or without a block, so something
63
+ like this is possible:
64
+
65
+ <table>
66
+ <th colspan="2">How many heads?</th>
67
+ <% f.check_boxes :number_of_heads_in,
68
+ [['One', 1], ['Two', 2], ['Three', 3]], :class => 'checkboxy' do |c| %>
69
+ <tr>
70
+ <td><%= c[:check_box] %></td>
71
+ <td><%= c[:label] %></td>
72
+ </tr>
73
+ <% end %>
74
+ </table>
75
+
76
+ Again, full documentation is in MetaSearch::Helpers::FormBuilder.
77
+
78
+ === Excluding attributes and associations
79
+
80
+ If you'd like to prevent certain associations or attributes from being searchable, you can control
81
+ this inside your models:
82
+
83
+ class Article < ActiveRecord::Base
84
+ metasearch_exclude_attr :some_private_data, :another_private_column
85
+ metasearch_exclude_assoc :an_association_that_should_not_be_searched, :and_another
86
+ end
87
+
88
+ You get the idea. Excluded attributes on a model will be honored across associations, so
89
+ if an Article <tt>has_many :comments</tt> and the Comment model looks something like this:
90
+
91
+ Comment < ActiveRecord::Base
92
+ validates_presence_of :user_id, :body
93
+ metasearch_exclude_attr :user_id
94
+ end
95
+
96
+ Then your call to <tt>Article.search</tt> will allow <tt>:comments_body_contains</tt>
97
+ but not <tt>:comments_user_id_equals</tt> to be passed.
98
+
99
+ == Copyright
100
+
101
+ Copyright (c) 2010 {Ernie Miller}[http://metautonomo.us]. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "meta_search"
8
+ gem.summary = %Q{ActiveRecord 3 object-based searching.}
9
+ gem.description = %Q{Adds a search method to your ActiveRecord models which returns an object to be used in form_for while constructing a search. Works with Rails 3 only.}
10
+ gem.email = "ernie@metautonomo.us"
11
+ gem.homepage = "http://metautonomo.us"
12
+ gem.authors = ["Ernie Miller"]
13
+ gem.add_development_dependency "activerecord", ">= 3.0.0.beta"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "meta_search #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,247 @@
1
+ require 'meta_search/model_compatibility'
2
+ require 'meta_search/exceptions'
3
+ require 'meta_search/where'
4
+ require 'meta_search/utility'
5
+
6
+ module MetaSearch
7
+ # Builder is the workhorse of MetaSearch -- it is the class that handles dynamically generating
8
+ # methods based on a supplied model, and is what gets instantiated when you call your model's search
9
+ # method. Builder doesn't generate any methods until they're needed, using method_missing to compare
10
+ # requested method names against your model's attributes, associations, and the configured Where
11
+ # list.
12
+ #
13
+ # === Attributes
14
+ #
15
+ # * +base+ - The base model that Builder wraps.
16
+ # * +search_attributes+ - Attributes that have been assigned (search terms)
17
+ # * +relation+ - The ActiveRecord::Relation representing the current search.
18
+ # * +join_dependency+ - The JoinDependency object representing current association join
19
+ # dependencies. It's used internally to avoid joining association tables more than
20
+ # once when constructing search queries.
21
+ class Builder
22
+ include ModelCompatibility
23
+ include Utility
24
+
25
+ attr_reader :base, :search_attributes, :relation, :join_dependency
26
+ delegate *RELATION_METHODS, :to => :relation
27
+
28
+ # Initialize a new Builder. Requires a base model to wrap, and supports a couple of options
29
+ # for how it will expose this model and its associations to your controllers/views.
30
+ def initialize(base, opts = {})
31
+ @base = base
32
+ @opts = opts
33
+ @associations = {}
34
+ @join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@base, [], nil)
35
+ @search_attributes = {}
36
+ @relation = @base.scoped
37
+ end
38
+
39
+ # Return the column info for the given model attribute (if not excluded as outlined above)
40
+ def column(attr)
41
+ @base.columns_hash[attr.to_s] if self.includes_attribute?(attr)
42
+ end
43
+
44
+ # Return the association reflection for the named association (if not excluded as outlined
45
+ # above)
46
+ def association(association)
47
+ if self.includes_association?(association)
48
+ @associations[association.to_sym] ||= @base.reflect_on_association(association.to_sym)
49
+ end
50
+ end
51
+
52
+ # Return the column info for given association and column (if the association is not
53
+ # excluded from search)
54
+ def association_column(association, attr)
55
+ if self.includes_association?(association)
56
+ assoc = self.association(association)
57
+ assoc.klass.columns_hash[attr.to_s] unless assoc.klass._metasearch_exclude_attributes.include?(attr.to_s)
58
+ end
59
+ end
60
+
61
+ def included_attributes
62
+ @included_attributes ||= @base.column_names - @base._metasearch_exclude_attributes
63
+ end
64
+
65
+ def includes_attribute?(attr)
66
+ self.included_attributes.include?(attr.to_s)
67
+ end
68
+
69
+ def included_associations
70
+ @included_associations ||= @base.reflect_on_all_associations.map {|a| a.name.to_s} - @base._metasearch_exclude_associations
71
+ end
72
+
73
+ def includes_association?(assoc)
74
+ self.included_associations.include?(assoc.to_s)
75
+ end
76
+
77
+ # Build the search with the given search options. Options are in the form of a hash
78
+ # with keys matching the names creted by the Builder's "wheres" as outlined in
79
+ # MetaSearch::Where
80
+ def build(opts)
81
+ opts ||= {}
82
+ @relation = @base.scoped
83
+ opts.stringify_keys!
84
+ opts = collapse_multiparameter_options(opts)
85
+ assign_attributes(opts)
86
+ self
87
+ end
88
+
89
+ private
90
+
91
+ def method_missing(method_id, *args, &block)
92
+ if match = method_id.to_s.match(/^(.*)\(([0-9]+).*\)$/)
93
+ method_name, index = match.captures
94
+ vals = self.send(method_name)
95
+ vals.is_a?(Array) ? vals[index.to_i - 1] : nil
96
+ elsif match = matches_attribute_method(method_id)
97
+ condition, attribute, association = match.captures.reverse
98
+ build_method(association, attribute, condition)
99
+ self.send(preferred_method_name(method_id), *args)
100
+ elsif match = matches_where_method(method_id)
101
+ condition = match.captures.first
102
+ build_where_method(condition, Where.new(condition))
103
+ self.send(method_id, *args)
104
+ else
105
+ super
106
+ end
107
+ end
108
+
109
+ def build_method(association, attribute, suffix)
110
+ if association.blank?
111
+ build_attribute_method(attribute, suffix)
112
+ else
113
+ build_association_method(association, attribute, suffix)
114
+ end
115
+ end
116
+
117
+ def build_association_method(association, attribute, type)
118
+ metaclass.instance_eval do
119
+ define_method("#{association}_#{attribute}_#{type}") do
120
+ search_attributes["#{association}_#{attribute}_#{type}"]
121
+ end
122
+
123
+ define_method("#{association}_#{attribute}_#{type}=") do |val|
124
+ search_attributes["#{association}_#{attribute}_#{type}"] = cast_attributes(association_type_for(association, attribute), val)
125
+ where = Where.new(type)
126
+ if where.valid_substitutions?(search_attributes["#{association}_#{attribute}_#{type}"])
127
+ join = build_or_find_association(association)
128
+ self.send("add_#{type}_where", join.aliased_table_name, attribute, search_attributes["#{association}_#{attribute}_#{type}"])
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ def build_attribute_method(attribute, type)
135
+ metaclass.instance_eval do
136
+ define_method("#{attribute}_#{type}") do
137
+ search_attributes["#{attribute}_#{type}"]
138
+ end
139
+
140
+ define_method("#{attribute}_#{type}=") do |val|
141
+ search_attributes["#{attribute}_#{type}"] = cast_attributes(type_for(attribute), val)
142
+ where = Where.new(type)
143
+ if where.valid_substitutions?(search_attributes["#{attribute}_#{type}"])
144
+ self.send("add_#{type}_where", @base.table_name, attribute, search_attributes["#{attribute}_#{type}"])
145
+ end
146
+ end
147
+ end
148
+ end
149
+
150
+ def build_where_method(condition, where)
151
+ metaclass.instance_eval do
152
+ define_method("add_#{condition}_where") do |table, attribute, *args|
153
+ args.flatten! unless where.keep_arrays?
154
+ @relation = @relation.where(
155
+ "#{quote_table_name table}.#{quote_column_name attribute} " +
156
+ "#{where.condition} #{where.substitutions}", *format_params(where.formatter, *args)
157
+ )
158
+ end
159
+ end
160
+ end
161
+
162
+ def format_params(formatter, *params)
163
+ par = params.map {|p| formatter.call(p)}
164
+ end
165
+
166
+ def build_or_find_association(association)
167
+ found_association = @join_dependency.join_associations.detect do |assoc|
168
+ assoc.reflection.name == association.to_sym
169
+ end
170
+ unless found_association
171
+ @relation = @relation.joins(association.to_sym)
172
+ @join_dependency.send(:build, association.to_sym, @join_dependency.join_base)
173
+ found_association = @join_dependency.join_associations.last
174
+ end
175
+ found_association
176
+ end
177
+
178
+ def matches_attribute_method(method_id)
179
+ method_name = preferred_method_name(method_id)
180
+ where = Where.new(method_id) rescue nil
181
+ return nil unless method_name && where
182
+ match = method_name.match("^(.*)_(#{where.name})=?$")
183
+ attribute, condition = match.captures
184
+ if where.types.include?(type_for(attribute))
185
+ return match
186
+ elsif match = matches_association(method_name, attribute, condition)
187
+ association, attribute = match.captures
188
+ return match if where.types.include?(association_type_for(association, attribute))
189
+ end
190
+ nil
191
+ end
192
+
193
+ def preferred_method_name(method_id)
194
+ method_name = method_id.to_s
195
+ where = Where.new(method_name) rescue nil
196
+ return nil unless where
197
+ where.aliases.each do |a|
198
+ break if method_name.sub!(/#{a}(=?)$/, "#{where.name}\\1")
199
+ end
200
+ method_name
201
+ end
202
+
203
+ def matches_association(method_id, attribute, condition)
204
+ self.included_associations.each do |association|
205
+ test_attribute = attribute.dup
206
+ if test_attribute.gsub!(/^#{association}_/, '') &&
207
+ match = method_id.to_s.match("^(#{association})_(#{test_attribute})_(#{condition})=?$")
208
+ return match
209
+ end
210
+ end
211
+ nil
212
+ end
213
+
214
+ def matches_where_method(method_id)
215
+ if match = method_id.to_s.match(/^add_(.*)_where$/)
216
+ condition = match.captures.first
217
+ Where.get(condition) ? match : nil
218
+ end
219
+ end
220
+
221
+ def assign_attributes(opts)
222
+ opts.each_pair do |k, v|
223
+ self.send("#{k}=", v)
224
+ end
225
+ end
226
+
227
+ def type_for(attribute)
228
+ column = self.column(attribute)
229
+ column.type if column
230
+ end
231
+
232
+ def class_for(attribute)
233
+ column = self.column(attribute)
234
+ column.klass if column
235
+ end
236
+
237
+ def association_type_for(association, attribute)
238
+ column = self.association_column(association, attribute)
239
+ column.type if column
240
+ end
241
+
242
+ def association_class_for(association, attribute)
243
+ column = self.association_column(association, attribute)
244
+ column.klass if column
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,3 @@
1
+ module MetaSearch
2
+ class TypeCastError < StandardError; end
3
+ end
@@ -0,0 +1,168 @@
1
+ require 'action_view'
2
+ require 'action_dispatch'
3
+
4
+ module ActionDispatch::Http::FilterParameters #:nodoc:
5
+ protected
6
+ # Temp fix for Rails 3 beta buggy parameter filtering on arrays
7
+ def process_parameter_filter(original_params) #:nodoc:
8
+ return original_params.dup unless filtering_parameters?
9
+
10
+ filtered_params = {}
11
+ regexps, blocks = compile_parameter_filter
12
+
13
+ original_params.each do |key, value|
14
+ if regexps.find { |r| key =~ r }
15
+ value = '[FILTERED]'
16
+ elsif value.is_a?(Hash)
17
+ value = process_parameter_filter(value)
18
+ elsif value.is_a?(Array)
19
+ value = value.map { |v| v.is_a?(Hash) ? process_parameter_filter(v) : v }
20
+ elsif blocks.present?
21
+ key = key.dup
22
+ value = value.dup if value.duplicable?
23
+ blocks.each { |b| b.call(key, value) }
24
+ end
25
+
26
+ filtered_params[key] = value
27
+ end
28
+
29
+ filtered_params
30
+ end
31
+ end
32
+
33
+ module MetaSearch::Helpers
34
+ module FormBuilder
35
+ def self.enable! #:nodoc:
36
+ ::ActionView::Helpers::FormBuilder.class_eval do
37
+ include FormBuilder
38
+ self.field_helpers += ['multiparameter_field', 'check_boxes', 'collection_check_boxes']
39
+ end
40
+ end
41
+
42
+ # Like other form_for field methods (text_field, hidden_field, password_field) etc,
43
+ # but takes a list of hashes between the +method+ parameter and the trailing option hash,
44
+ # if any, to specify a number of fields to create in multiparameter fashion.
45
+ #
46
+ # Each hash *must* contain a :field_type option, which specifies a form_for method, and
47
+ # _may_ contain an optional :type_cast option, with one of the typical multiparameter
48
+ # type cast characters. Any remaining options will be merged with the defaults specified
49
+ # in the trailing option hash and passed along when creating that field.
50
+ #
51
+ # For example...
52
+ #
53
+ # <%= f.multiparameter_field :moderations_value_between,
54
+ # {:field_type => :text_field, :class => 'first'},
55
+ # {:field_type => :text_field, :type_cast => 'i'},
56
+ # :size => 5 %>
57
+ #
58
+ # ...will create the following HTML:
59
+ #
60
+ # <input class="first" id="search_moderations_value_between(1)"
61
+ # name="search[moderations_value_between(1)]" size="5" type="text" />
62
+ #
63
+ # <input id="search_moderations_value_between(2i)"
64
+ # name="search[moderations_value_between(2i)]" size="5" type="text" />
65
+ #
66
+ # As with any multiparameter input fields, these will be concatenated into an
67
+ # array and passed to the attribute named by the first parameter for assignment.
68
+ def multiparameter_field(method, *args)
69
+ defaults = has_multiparameter_defaults?(args) ? args.pop : {}
70
+ raise ArgumentError, "No multiparameter fields specified" if args.blank?
71
+ html = ''.html_safe
72
+ args.each_with_index do |field, index|
73
+ type = field.delete(:field_type) || raise(ArgumentError, "No :field_type specified.")
74
+ cast = field.delete(:type_cast) || ''
75
+ opts = defaults.merge(field)
76
+ html.safe_concat(
77
+ @template.send(
78
+ type.to_s,
79
+ @object_name,
80
+ (method.to_s + "(#{index + 1}#{cast})"),
81
+ objectify_options(opts))
82
+ )
83
+ end
84
+ html
85
+ end
86
+
87
+ # Behaves almost exactly like the select method, but instead of generating a select tag,
88
+ # generates checkboxes. Since these checkboxes are just a checkbox and label with no
89
+ # additional formatting by default, this method can also take a block parameter.
90
+ #
91
+ # *Parameters:*
92
+ #
93
+ # * +method+ - The method name on the form_for object
94
+ # * +choices+ - An array of arrays, the first value in each element is the text for the
95
+ # label, and the last is the value for the checkbox
96
+ # * +options+ - An options hash to be passed through to the checkboxes
97
+ #
98
+ # If a block is supplied, rather than just rendering the checkboxes and labels, the block
99
+ # will receive a hash with two keys, :check_box and :label
100
+ #
101
+ # *Examples:*
102
+ #
103
+ # Simple usage:
104
+ #
105
+ # <%= f.check_boxes :number_of_heads_in,
106
+ # [['One', 1], ['Two', 2], ['Three', 3]], :class => 'checkboxy' %>
107
+ #
108
+ # This will result in three checkboxes, with the labels "One", "Two", and "Three", and
109
+ # corresponding numeric values, which will be sent as an array to the :number_of_heads_in
110
+ # attribute of the form_for object.
111
+ #
112
+ # Additional formatting:
113
+ #
114
+ # <table>
115
+ # <th colspan="2">How many heads?</th>
116
+ # <% f.check_boxes :number_of_heads_in,
117
+ # [['One', 1], ['Two', 2], ['Three', 3]], :class => 'checkboxy' do |c| %>
118
+ # <tr>
119
+ # <td><%= c[:check_box] %></td>
120
+ # <td><%= c[:label] %></td>
121
+ # </tr>
122
+ # <% end %>
123
+ # </table>
124
+ #
125
+ # This example will output the checkboxes and labels in a tabular format. You get the idea.
126
+ def check_boxes(method, choices = [], options = {}, &block)
127
+ unless choices.first.respond_to?(:first) && choices.first.respond_to?(:last)
128
+ raise ArgumentError, 'invalid choice array specified'
129
+ end
130
+ collection_check_boxes(method, choices, :last, :first, options, &block)
131
+ end
132
+
133
+ # Just like +check_boxes+, but this time you can pass in a collection, value, and text method,
134
+ # as with collection_select.
135
+ #
136
+ # Example:
137
+ #
138
+ # <%= f.collection_check_boxes :head_sizes_in, HeadSize.all,
139
+ # :id, :name, :class => 'head-check' %>
140
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, &block)
141
+ html = ''.html_safe
142
+ collection.each do |choice|
143
+ text = choice.send(text_method)
144
+ value = choice.send(value_method)
145
+ c = {}
146
+ c[:check_box] = @template.check_box_tag(
147
+ "#{@object_name}[#{method}][]",
148
+ value,
149
+ [@object.send(method)].flatten.include?(value),
150
+ options.merge(:id => [@object_name, method.to_s, value.to_s.underscore].join('_'))
151
+ )
152
+ c[:label] = @template.label_tag([@object_name, method.to_s, value.to_s.underscore].join('_'),
153
+ text)
154
+ yield c if block_given?
155
+ html.safe_concat(c[:check_box] + c[:label])
156
+ end
157
+ html
158
+ end
159
+
160
+ private
161
+
162
+ # If the last element of the arguments to multiparameter_field has no :field_type
163
+ # key, we assume it's got some defaults to be used in the other hashes.
164
+ def has_multiparameter_defaults?(args)
165
+ args.size > 1 && args.last.is_a?(Hash) && !args.last.has_key?(:field_type)
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,8 @@
1
+ module MetaSearch
2
+ # Just a little module to mix in so that ActionPack doesn't complain.
3
+ module ModelCompatibility
4
+ def new_record?
5
+ false
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ require 'meta_search'
2
+
3
+ module MetaSearch
4
+ class Railtie < Rails::Railtie #:nodoc:
5
+ railtie_name :meta_search
6
+
7
+ initializer "meta_search.active_record" do |app|
8
+ if defined? ::ActiveRecord
9
+ require 'meta_search/searches/active_record'
10
+ MetaSearch::Searches::ActiveRecord.enable!
11
+ end
12
+ end
13
+
14
+ initializer "meta_search.action_view" do |app|
15
+ if defined? ::ActionView
16
+ require 'meta_search/helpers/action_view'
17
+ MetaSearch::Helpers::FormBuilder.enable!
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'meta_search/searches/base'
2
+ require 'active_record'
3
+
4
+ module MetaSearch::Searches
5
+ module ActiveRecord
6
+ include MetaSearch::Searches::Base
7
+
8
+ # Mixes MetaSearch into ActiveRecord::Base.
9
+ def self.enable!
10
+ ::ActiveRecord::Base.class_eval do
11
+ class_attribute :_metasearch_exclude_attributes
12
+ class_attribute :_metasearch_exclude_associations
13
+ self._metasearch_exclude_attributes = []
14
+ self._metasearch_exclude_associations = []
15
+ extend ActiveRecord
16
+ end
17
+ end
18
+ end
19
+ end