meta_search 0.9.8 → 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ Changes since 0.9.8 (2010-10-20):
2
+ * ARel 2.x and Rails 3.0.2 compatability
3
+ * sort_link uses search_key from builder. Search_key defaults to "search"
4
+ * sort_link will localize attribute names.
5
+ * You can now create two scopes on your model named sort_by_something_asc
6
+ and sort_by_something_desc, and sort_link will then allow you to specify
7
+ :something as a parameter, then use your scope to perform custom sorting.
8
+
1
9
  Changes since 0.9.7 (2010-10-12):
2
10
  * Play nicely regardless of MetaWhere/MetaSearch load order.
3
11
  * Big fix - stop altering the supplied hash in Builder#build.
data/Gemfile CHANGED
@@ -1,9 +1,3 @@
1
1
  source :rubygems
2
- gem "arel", "~> 1.0.1"
3
- gem "activerecord", "~> 3.0.0"
4
- gem "activesupport", "~> 3.0.0"
5
- gem "actionpack", "~> 3.0.0"
6
- group :test do
7
- gem "rake"
8
- gem "shoulda"
9
- end
2
+
3
+ gemspec
data/README.rdoc CHANGED
@@ -309,13 +309,27 @@ Normally, you won't supply this parameter yourself, but instead will use the hel
309
309
  <tt>sort_link</tt> in your views, like so:
310
310
 
311
311
  <%= sort_link @search, :title %>
312
- <%= sort_link @search, :created_at %>
312
+
313
+ Or, if in the context of a form_for against a MetaSearch::Builder:
314
+
315
+ <%= f.sort_link :title %>
313
316
 
314
317
  The <tt>@search</tt> object is the instance of MetaSearch::Builder you got back earlier from
315
318
  your controller. The other required parameter is the attribute name itself. Optionally,
316
319
  you can provide a string as a 3rd parameter to override the default link name, and then
317
320
  additional hashed for the +options+ and +html_options+ hashes for link_to.
318
321
 
322
+ You can sort by more than one column as well, by creating a link like:
323
+
324
+ <%= sort_link :name_and_salary %>
325
+
326
+ If you'd like to do a custom sort, you can do so by setting up two scopes in your model:
327
+
328
+ scope :sort_by_custom_name_asc, order('custom_name ASC')
329
+ scope :sort_by_custom_name_desc, order('custom_name DESC')
330
+
331
+ You can then do <tt>sort_link @search, :custom_name</tt> and it will work as you expect.
332
+
319
333
  All <tt>sort_link</tt>-generated links will have the CSS class sort_link, as well as a
320
334
  directional class (ascending or descending) if the link is for a currently sorted column,
321
335
  for your styling enjoyment.
data/Rakefile CHANGED
@@ -15,10 +15,10 @@ begin
15
15
  gem.homepage = "http://metautonomo.us/projects/metasearch/"
16
16
  gem.authors = ["Ernie Miller"]
17
17
  gem.add_development_dependency "shoulda"
18
- gem.add_dependency "activerecord", "~> 3.0.0"
19
- gem.add_dependency "activesupport", "~> 3.0.0"
20
- gem.add_dependency "actionpack", "~> 3.0.0"
21
- gem.add_dependency "arel", "~> 1.0.1"
18
+ gem.add_dependency "activerecord", "~> 3.0.2"
19
+ gem.add_dependency "activesupport", "~> 3.0.2"
20
+ gem.add_dependency "actionpack", "~> 3.0.2"
21
+ gem.add_dependency "arel", "~> 2.0.2"
22
22
  gem.post_install_message = <<END
23
23
 
24
24
  *** Thanks for installing MetaSearch! ***
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.8
1
+ 0.9.9
@@ -22,7 +22,7 @@ module MetaSearch
22
22
  include ModelCompatibility
23
23
  include Utility
24
24
 
25
- attr_reader :base, :search_attributes, :join_dependency, :errors
25
+ attr_reader :base, :search_key, :search_attributes, :join_dependency, :errors
26
26
  delegate *RELATION_METHODS + [:to => :relation]
27
27
 
28
28
  # Initialize a new Builder. Requires a base model to wrap, and supports a couple of options
@@ -30,7 +30,7 @@ module MetaSearch
30
30
  def initialize(base_or_relation, opts = {})
31
31
  @relation = base_or_relation.scoped
32
32
  @base = @relation.klass
33
- @opts = opts
33
+ @search_key = opts[:search_key] ? opts[:search_key].to_s : 'search'
34
34
  @join_dependency = build_join_dependency
35
35
  @search_attributes = {}
36
36
  @errors = ActiveModel::Errors.new(self)
@@ -42,19 +42,23 @@ module MetaSearch
42
42
  end
43
43
 
44
44
  def get_column(column, base = @base)
45
- if base._metasearch_include_attributes.blank?
46
- base.columns_hash[column.to_s] unless base._metasearch_exclude_attributes.include?(column.to_s)
47
- else
48
- base.columns_hash[column.to_s] if base._metasearch_include_attributes.include?(column.to_s)
49
- end
45
+ base.columns_hash[column.to_s] unless base_excludes_attribute(base, column)
46
+ end
47
+
48
+ def base_excludes_attribute(base, column)
49
+ base._metasearch_include_attributes.blank? ?
50
+ base._metasearch_exclude_attributes.include?(column.to_s) :
51
+ !base._metasearch_include_attributes.include?(column.to_s)
50
52
  end
51
53
 
52
54
  def get_association(assoc, base = @base)
53
- if base._metasearch_include_associations.blank?
54
- base.reflect_on_association(assoc.to_sym) unless base._metasearch_exclude_associations.include?(assoc.to_s)
55
- else
56
- base.reflect_on_association(assoc.to_sym) if base._metasearch_include_associations.include?(assoc.to_s)
57
- end
55
+ base.reflect_on_association(assoc.to_sym) unless base_excludes_association(base, assoc)
56
+ end
57
+
58
+ def base_excludes_association(base, assoc)
59
+ base._metasearch_include_associations.blank? ?
60
+ base._metasearch_exclude_associations.include?(assoc.to_s) :
61
+ !base._metasearch_include_associations.include?(assoc.to_s)
58
62
  end
59
63
 
60
64
  def get_attribute(name, parent = @join_dependency.join_base)
@@ -62,9 +66,9 @@ module MetaSearch
62
66
  if get_column(name, parent.active_record)
63
67
  if parent.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
64
68
  relation = parent.relation.is_a?(Array) ? parent.relation.last : parent.relation
65
- attribute = relation.table[name]
69
+ attribute = relation[name]
66
70
  else
67
- attribute = @relation.table[name]
71
+ attribute = @relation.arel_table[name]
68
72
  end
69
73
  elsif (segments = name.to_s.split(/_/)).size > 1
70
74
  remainder = []
@@ -103,9 +107,8 @@ module MetaSearch
103
107
  end
104
108
 
105
109
  def respond_to?(method_id, include_private = false)
106
- return true if super # Hopefully we've already defined the method.
110
+ return true if super
107
111
 
108
- # Curses! Looks like we'll need to do this the hard way.
109
112
  method_name = method_id.to_s
110
113
  if RELATION_METHODS.map(&:to_s).include?(method_name)
111
114
  true
@@ -123,6 +126,22 @@ module MetaSearch
123
126
 
124
127
  private
125
128
 
129
+ def assign_attributes(opts)
130
+ opts.each_pair do |k, v|
131
+ self.send("#{k}=", v)
132
+ end
133
+ end
134
+
135
+ def enforce_join_depth_limit!
136
+ raise JoinDepthError, "Maximum join depth of #{MAX_JOIN_DEPTH} exceeded." if @join_dependency.join_associations.detect {|ja|
137
+ gauge_depth_of_join_association(ja) > MAX_JOIN_DEPTH
138
+ }
139
+ end
140
+
141
+ def gauge_depth_of_join_association(ja)
142
+ 1 + (ja.respond_to?(:parent) ? gauge_depth_of_join_association(ja.parent) : 0)
143
+ end
144
+
126
145
  def method_missing(method_id, *args, &block)
127
146
  method_name = method_id.to_s
128
147
  if method_name =~ /^meta_sort=?$/
@@ -141,43 +160,22 @@ module MetaSearch
141
160
  end
142
161
  end
143
162
 
144
- def build_join_dependency
145
- joins = @relation.joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
146
-
147
- association_joins = joins.select do |j|
148
- [Hash, Array, Symbol].include?(j.class) && !array_of_strings?(j)
149
- end
150
-
151
- stashed_association_joins = joins.select do |j|
152
- j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
153
- end
154
-
155
- non_association_joins = (joins - association_joins - stashed_association_joins)
156
- custom_joins = custom_join_sql(*non_association_joins)
157
-
158
- ActiveRecord::Associations::ClassMethods::JoinDependency.new(@base, association_joins, custom_joins)
163
+ def matches_named_method(name)
164
+ method_name = name.to_s.sub(/\=$/, '')
165
+ return method_name if @base._metasearch_methods.has_key?(method_name)
159
166
  end
160
167
 
161
- def custom_join_sql(*joins)
162
- arel = @relation.table
163
- joins.each do |join|
164
- next if join.blank?
165
-
166
- case join
167
- when Hash, Array, Symbol
168
- if array_of_strings?(join)
169
- join_string = join.join(' ')
170
- arel = arel.join(join_string)
171
- end
172
- else
173
- arel = arel.join(join)
174
- end
168
+ def matches_attribute_method(method_id)
169
+ method_name = preferred_method_name(method_id)
170
+ where = Where.new(method_id) rescue nil
171
+ return nil unless method_name && where
172
+ match = method_name.match("^(.*)_(#{where.name})=?$")
173
+ attribute, predicate = match.captures
174
+ attributes = attribute.split(/_or_/)
175
+ if attributes.all? {|a| where.types.include?(column_type(a))}
176
+ return match
175
177
  end
176
- arel.joins(arel)
177
- end
178
-
179
- def array_of_strings?(o)
180
- o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
178
+ nil
181
179
  end
182
180
 
183
181
  def get_sort
@@ -187,36 +185,24 @@ module MetaSearch
187
185
  def set_sort(val)
188
186
  column, direction = val.split('.')
189
187
  direction ||= 'asc'
190
- if ['asc','desc'].include?(direction) && attribute = get_attribute(column)
191
- search_attributes['meta_sort'] = val
192
- @relation = @relation.order(attribute.send(direction).to_sql)
193
- end
194
- end
195
-
196
- def column_type(name, base = @base)
197
- type = nil
198
- if column = get_column(name, base)
199
- type = column.type
200
- elsif (segments = name.split(/_/)).size > 1
201
- remainder = []
202
- found_assoc = nil
203
- while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do
204
- if found_assoc = get_association(segments.join('_'), base)
205
- if found_assoc.options[:polymorphic]
206
- unless delimiter = remainder.index('type')
207
- raise PolymorphicAssociationMissingTypeError, "Polymorphic association specified without a type"
208
- end
209
- polymorphic_class, attribute_name = remainder[0...delimiter].join('_'),
210
- remainder[delimiter + 1...remainder.size].join('_')
211
- polymorphic_class = polymorphic_class.classify.constantize
212
- type = column_type(attribute_name, polymorphic_class)
213
- else
214
- type = column_type(remainder.join('_'), found_assoc.klass)
188
+ if ['asc','desc'].include?(direction)
189
+ if @base.respond_to?("sort_by_#{column}_#{direction}")
190
+ search_attributes['meta_sort'] = val
191
+ @relation = @relation.send("sort_by_#{column}_#{direction}")
192
+ elsif attribute = get_attribute(column)
193
+ search_attributes['meta_sort'] = val
194
+ @relation = @relation.order(attribute.send(direction).to_sql)
195
+ elsif column.scan('_and_').present?
196
+ attribute_names = column.split('_and_')
197
+ attributes = attribute_names.map {|n| get_attribute(n)}
198
+ if attribute_names.size == attributes.compact.size # We found all attributes
199
+ search_attributes['meta_sort'] = val
200
+ attributes.each do |attribute|
201
+ @relation = @relation.order(attribute.send(direction).to_sql)
215
202
  end
216
203
  end
217
204
  end
218
205
  end
219
- type
220
206
  end
221
207
 
222
208
  def get_named_method_value(name)
@@ -250,6 +236,38 @@ module MetaSearch
250
236
  end
251
237
  end
252
238
 
239
+ def column_type(name, base = @base)
240
+ type = nil
241
+ if column = get_column(name, base)
242
+ type = column.type
243
+ elsif (segments = name.split(/_/)).size > 1
244
+ type = type_from_association_segments(segments, base)
245
+ end
246
+ type
247
+ end
248
+
249
+ def type_from_association_segments(segments, base)
250
+ remainder = []
251
+ found_assoc = nil
252
+ type = nil
253
+ while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do
254
+ if found_assoc = get_association(segments.join('_'), base)
255
+ if found_assoc.options[:polymorphic]
256
+ unless delimiter = remainder.index('type')
257
+ raise PolymorphicAssociationMissingTypeError, "Polymorphic association specified without a type"
258
+ end
259
+ polymorphic_class, attribute_name = remainder[0...delimiter].join('_'),
260
+ remainder[delimiter + 1...remainder.size].join('_')
261
+ polymorphic_class = polymorphic_class.classify.constantize
262
+ type = column_type(attribute_name, polymorphic_class)
263
+ else
264
+ type = column_type(remainder.join('_'), found_assoc.klass)
265
+ end
266
+ end
267
+ end
268
+ type
269
+ end
270
+
253
271
  def build_or_find_association(association, parent = @join_dependency.join_base, klass = nil)
254
272
  found_association = @join_dependency.join_associations.detect do |assoc|
255
273
  assoc.reflection.name == association.to_sym &&
@@ -257,75 +275,47 @@ module MetaSearch
257
275
  assoc.parent == parent
258
276
  end
259
277
  unless found_association
260
- @join_dependency.send(:build_with_metasearch, association, parent, Arel::OuterJoin, klass)
278
+ @join_dependency.send(:build_with_metasearch, association, parent, Arel::Nodes::OuterJoin, klass)
261
279
  found_association = @join_dependency.join_associations.last
262
280
  @relation = @relation.joins(found_association)
263
281
  end
264
282
  found_association
265
283
  end
266
284
 
267
- def enforce_join_depth_limit!
268
- raise JoinDepthError, "Maximum join depth of #{MAX_JOIN_DEPTH} exceeded." if @join_dependency.join_associations.detect {|ja|
269
- gauge_depth_of_join_association(ja) > MAX_JOIN_DEPTH
270
- }
271
- end
272
-
273
- def gauge_depth_of_join_association(ja)
274
- 1 + (ja.respond_to?(:parent) ? gauge_depth_of_join_association(ja.parent) : 0)
275
- end
276
-
277
- def matches_named_method(name)
278
- method_name = name.to_s.sub(/\=$/, '')
279
- return method_name if @base._metasearch_methods.has_key?(method_name)
280
- end
285
+ def build_join_dependency
286
+ joins = @relation.joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
281
287
 
282
- def matches_attribute_method(method_id)
283
- method_name = preferred_method_name(method_id)
284
- where = Where.new(method_id) rescue nil
285
- return nil unless method_name && where
286
- match = method_name.match("^(.*)_(#{where.name})=?$")
287
- attribute, predicate = match.captures
288
- attributes = attribute.split(/_or_/)
289
- if attributes.all? {|a| where.types.include?(column_type(a))}
290
- return match
288
+ association_joins = joins.select do |j|
289
+ [Hash, Array, Symbol].include?(j.class) && !array_of_strings?(j)
291
290
  end
292
- nil
293
- end
294
291
 
295
- def preferred_method_name(method_id)
296
- method_name = method_id.to_s
297
- where = Where.new(method_name) rescue nil
298
- return nil unless where
299
- where.aliases.each do |a|
300
- break if method_name.sub!(/#{a}(=?)$/, "#{where.name}\\1")
292
+ stashed_association_joins = joins.select do |j|
293
+ j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
301
294
  end
302
- method_name
303
- end
304
295
 
305
- def assign_attributes(opts)
306
- opts.each_pair do |k, v|
307
- self.send("#{k}=", v)
308
- end
309
- end
296
+ non_association_joins = (joins - association_joins - stashed_association_joins)
297
+ custom_joins = custom_join_sql(*non_association_joins)
310
298
 
311
- def type_for(attribute)
312
- column = self.column(attribute)
313
- column.type if column
299
+ ActiveRecord::Associations::ClassMethods::JoinDependency.new(@base, association_joins, custom_joins)
314
300
  end
315
301
 
316
- def class_for(attribute)
317
- column = self.column(attribute)
318
- column.klass if column
319
- end
302
+ def custom_join_sql(*joins)
303
+ arel = @relation.table
304
+ joins.each do |join|
305
+ next if join.blank?
320
306
 
321
- def association_type_for(association, attribute)
322
- column = self.association_column(association, attribute)
323
- column.type if column
307
+ case join
308
+ when Hash, Array, Symbol
309
+ if array_of_strings?(join)
310
+ join_string = join.join(' ')
311
+ arel = arel.join(join_string)
312
+ end
313
+ else
314
+ arel = arel.join(join)
315
+ end
316
+ end
317
+ arel.joins(arel)
324
318
  end
325
319
 
326
- def association_class_for(association, attribute)
327
- column = self.association_column(association, attribute)
328
- column.klass if column
329
- end
330
320
  end
331
321
  end
@@ -2,17 +2,22 @@ module MetaSearch
2
2
  module Helpers
3
3
  module FormHelper
4
4
  def apply_form_for_options!(object_or_array, options)
5
- if object_or_array.is_a?(Array) && (builder = object_or_array.detect {|o| o.is_a? MetaSearch::Builder})
6
- html_options = {
7
- :class => options[:as] ? "#{options[:as]}_search" : "#{builder.base.to_s.underscore}_search",
8
- :id => options[:as] ? "#{options[:as]}_search" : "#{builder.base.to_s.underscore}_search",
9
- :method => :get }
10
- options[:html] ||= {}
11
- options[:html].reverse_merge!(html_options)
12
- options[:url] ||= polymorphic_path(object_or_array.map {|o| o.is_a?(MetaSearch::Builder) ? o.base : o})
5
+ if object_or_array.is_a?(MetaSearch::Builder)
6
+ builder = object_or_array
7
+ url = polymorphic_path(object_or_array.base)
8
+ elsif object_or_array.is_a?(Array) && (builder = object_or_array.detect {|o| o.is_a?(MetaSearch::Builder)})
9
+ url = polymorphic_path(object_or_array.map {|o| o.is_a?(MetaSearch::Builder) ? o.base : o})
13
10
  else
14
- super
11
+ super and return
15
12
  end
13
+
14
+ html_options = {
15
+ :class => options[:as] ? "#{options[:as]}_search" : "#{builder.base.to_s.underscore}_search",
16
+ :id => options[:as] ? "#{options[:as]}_search" : "#{builder.base.to_s.underscore}_search",
17
+ :method => :get }
18
+ options[:html] ||= {}
19
+ options[:html].reverse_merge!(html_options)
20
+ options[:url] ||= url
16
21
  end
17
22
  end
18
23
  end
@@ -19,7 +19,7 @@ module MetaSearch
19
19
  def sort_link(builder, attribute, *args)
20
20
  raise ArgumentError, "Need a MetaSearch::Builder search object as first param!" unless builder.is_a?(MetaSearch::Builder)
21
21
  attr_name = attribute.to_s
22
- name = (args.size > 0 && !args.first.is_a?(Hash)) ? args.shift.to_s : attr_name.humanize
22
+ name = (args.size > 0 && !args.first.is_a?(Hash)) ? args.shift.to_s : builder.base.human_attribute_name(attr_name)
23
23
  prev_attr, prev_order = builder.search_attributes['meta_sort'].to_s.split('.')
24
24
  current_order = prev_attr == attr_name ? prev_order : nil
25
25
  new_order = current_order == 'asc' ? 'desc' : 'asc'
@@ -28,7 +28,7 @@ module MetaSearch
28
28
  css = ['sort_link', current_order].compact.join(' ')
29
29
  html_options[:class] = [css, html_options[:class]].compact.join(' ')
30
30
  options.merge!(
31
- 'search' => builder.search_attributes.merge(
31
+ builder.search_key => builder.search_attributes.merge(
32
32
  'meta_sort' => [attr_name, new_order].join('.')
33
33
  )
34
34
  )
@@ -13,8 +13,8 @@ module MetaSearch
13
13
  join_associations.detect {|a| association == a} ||
14
14
  (
15
15
  association.class == MetaSearch::PolymorphicJoinAssociation ?
16
- build_with_metasearch(association.reflection.name, association.find_parent_in(self) || join_base, association.join_class, association.reflection.klass) :
17
- build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_class)
16
+ build_with_metasearch(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type, association.reflection.klass) :
17
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
18
18
  )
19
19
  end
20
20
  self
@@ -22,7 +22,7 @@ module MetaSearch
22
22
 
23
23
  protected
24
24
 
25
- def build_with_metasearch(association, parent = nil, join_class = Arel::InnerJoin, polymorphic_class = nil)
25
+ def build_with_metasearch(association, parent = nil, join_type = Arel::Nodes::InnerJoin, polymorphic_class = nil)
26
26
  parent ||= @joins.last
27
27
  case association
28
28
  when Symbol, String
@@ -30,13 +30,17 @@ module MetaSearch
30
30
  raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?"
31
31
  if reflection.options[:polymorphic]
32
32
  @reflections << reflection
33
- @joins << build_polymorphic_join_association(reflection, parent, polymorphic_class).with_join_class(join_class)
33
+ association = build_polymorphic_join_association(reflection, parent, polymorphic_class)
34
+ association.join_type = join_type
35
+ @joins << association
34
36
  else
35
37
  @reflections << reflection
36
- @joins << build_join_association(reflection, parent).with_join_class(join_class)
38
+ association = build_join_association(reflection, parent)
39
+ association.join_type = join_type
40
+ @joins << association
37
41
  end
38
42
  else
39
- build(association, parent, join_class) # Shouldn't get here.
43
+ build(association, parent, join_type) # Shouldn't get here.
40
44
  end
41
45
  end
42
46
 
@@ -59,7 +63,7 @@ module MetaSearch
59
63
  @parent_table_name = @parent.active_record.table_name
60
64
  @aliased_table_name = aliased_table_name_for(table_name)
61
65
  @join = nil
62
- @join_class = Arel::InnerJoin
66
+ @join_type = Arel::Nodes::InnerJoin
63
67
  end
64
68
 
65
69
  def ==(other)
@@ -1,13 +1,13 @@
1
1
  module MetaSearch
2
- # Just a little module to mix in so that ActionPack doesn't complain.
2
+
3
3
  module ModelCompatibility
4
+
4
5
  def self.included(base)
5
6
  base.extend ClassMethods
6
7
  end
7
8
 
8
- # Force default "Update search" text
9
9
  def persisted?
10
- true
10
+ false
11
11
  end
12
12
 
13
13
  def to_key
@@ -21,26 +21,29 @@ module MetaSearch
21
21
  def to_model
22
22
  self
23
23
  end
24
+ end
24
25
 
25
- class Name < String
26
- attr_reader :singular, :plural, :element, :collection, :partial_path, :human
27
- alias_method :cache_key, :collection
28
-
29
- def initialize
30
- super("Search")
31
- @singular = "search".freeze
32
- @plural = "searches".freeze
33
- @element = "search".freeze
34
- @human = "Search".freeze
35
- @collection = "meta_search/searches".freeze
36
- @partial_path = "#{@collection}/#{@element}".freeze
37
- end
26
+ class Name < String
27
+ attr_reader :singular, :plural, :element, :collection, :partial_path, :human, :param_key, :route_key
28
+ alias_method :cache_key, :collection
29
+
30
+ def initialize
31
+ super("Search")
32
+ @singular = "search".freeze
33
+ @plural = "searches".freeze
34
+ @element = "search".freeze
35
+ @human = "Search".freeze
36
+ @collection = "meta_search/searches".freeze
37
+ @partial_path = "#{@collection}/#{@element}".freeze
38
+ @param_key = "search".freeze
39
+ @route_key = "searches".freeze
38
40
  end
41
+ end
39
42
 
40
- module ClassMethods
41
- def model_name
42
- @_model_name ||= Name.new
43
- end
43
+ module ClassMethods
44
+ def model_name
45
+ @_model_name ||= Name.new
44
46
  end
45
47
  end
48
+
46
49
  end
@@ -2,92 +2,94 @@ require 'active_support/concern'
2
2
  require 'meta_search/method'
3
3
  require 'meta_search/builder'
4
4
 
5
- module MetaSearch::Searches
6
- module ActiveRecord
5
+ module MetaSearch
6
+ module Searches
7
+ module ActiveRecord
7
8
 
8
- def self.included(base)
9
- base.extend ClassMethods
9
+ def self.included(base)
10
+ base.extend ClassMethods
10
11
 
11
- base.class_eval do
12
- class_attribute :_metasearch_include_attributes, :_metasearch_exclude_attributes
13
- class_attribute :_metasearch_include_associations, :_metasearch_exclude_associations
14
- class_attribute :_metasearch_methods
15
- self._metasearch_include_attributes =
16
- self._metasearch_exclude_attributes =
17
- self._metasearch_exclude_associations =
18
- self._metasearch_include_associations = []
19
- self._metasearch_methods = {}
12
+ base.class_eval do
13
+ class_attribute :_metasearch_include_attributes, :_metasearch_exclude_attributes
14
+ class_attribute :_metasearch_include_associations, :_metasearch_exclude_associations
15
+ class_attribute :_metasearch_methods
16
+ self._metasearch_include_attributes =
17
+ self._metasearch_exclude_attributes =
18
+ self._metasearch_exclude_associations =
19
+ self._metasearch_include_associations = []
20
+ self._metasearch_methods = {}
21
+ end
20
22
  end
21
- end
22
23
 
23
- module ClassMethods
24
- # Prepares the search to run against your model. Returns an instance of
25
- # MetaSearch::Builder, which behaves pretty much like an ActiveRecord::Relation,
26
- # in that it doesn't actually query the database until you do something that
27
- # requires it to do so.
28
- def metasearch(opts = {})
29
- opts ||= {} # to catch nil params
30
- search_options = opts.delete(:search_options) || {}
31
- builder = MetaSearch::Builder.new(self, search_options)
32
- builder.build(opts)
33
- end
24
+ module ClassMethods
25
+ # Prepares the search to run against your model. Returns an instance of
26
+ # MetaSearch::Builder, which behaves pretty much like an ActiveRecord::Relation,
27
+ # in that it doesn't actually query the database until you do something that
28
+ # requires it to do so.
29
+ def metasearch(params = nil, opts = nil)
30
+ builder = MetaSearch::Builder.new(self, opts || {})
31
+ builder.build(params || {})
32
+ end
34
33
 
35
- alias_method :search, :metasearch unless respond_to?(:search)
34
+ alias_method :search, :metasearch unless respond_to?(:search)
36
35
 
37
- private
36
+ private
38
37
 
39
- # Excludes model attributes from searchability. This means that searches can't be created against
40
- # these columns, whether the search is based on this model, or the model's attributes are being
41
- # searched by association from another model. If a Comment <tt>belongs_to :article</tt> but declares
42
- # <tt>attr_unsearchable :user_id</tt> then <tt>Comment.search</tt> won't accept parameters
43
- # like <tt>:user_id_equals</tt>, nor will an Article.search accept the parameter
44
- # <tt>:comments_user_id_equals</tt>.
45
- def attr_unsearchable(*args)
46
- args.flatten.each do |attr|
47
- attr = attr.to_s
48
- raise(ArgumentError, "No persisted attribute (column) named #{attr} in #{self}") unless self.columns_hash.has_key?(attr)
49
- self._metasearch_exclude_attributes = (self._metasearch_exclude_attributes + [attr]).uniq
38
+ # Excludes model attributes from searchability. This means that searches can't be created against
39
+ # these columns, whether the search is based on this model, or the model's attributes are being
40
+ # searched by association from another model. If a Comment <tt>belongs_to :article</tt> but declares
41
+ # <tt>attr_unsearchable :user_id</tt> then <tt>Comment.search</tt> won't accept parameters
42
+ # like <tt>:user_id_equals</tt>, nor will an Article.search accept the parameter
43
+ # <tt>:comments_user_id_equals</tt>.
44
+ def attr_unsearchable(*args)
45
+ args.flatten.each do |attr|
46
+ attr = attr.to_s
47
+ raise(ArgumentError, "No persisted attribute (column) named #{attr} in #{self}") unless self.columns_hash.has_key?(attr)
48
+ self._metasearch_exclude_attributes = (self._metasearch_exclude_attributes + [attr]).uniq
49
+ end
50
50
  end
51
- end
52
51
 
53
- # Like <tt>attr_unsearchable</tt>, but operates as a whitelist rather than blacklist. If both
54
- # <tt>attr_searchable</tt> and <tt>attr_unsearchable</tt> are present, the latter
55
- # is ignored.
56
- def attr_searchable(*args)
57
- args.flatten.each do |attr|
58
- attr = attr.to_s
59
- raise(ArgumentError, "No persisted attribute (column) named #{attr} in #{self}") unless self.columns_hash.has_key?(attr)
60
- self._metasearch_include_attributes = (self._metasearch_include_attributes + [attr]).uniq
52
+ # Like <tt>attr_unsearchable</tt>, but operates as a whitelist rather than blacklist. If both
53
+ # <tt>attr_searchable</tt> and <tt>attr_unsearchable</tt> are present, the latter
54
+ # is ignored.
55
+ def attr_searchable(*args)
56
+ args.flatten.each do |attr|
57
+ attr = attr.to_s
58
+ raise(ArgumentError, "No persisted attribute (column) named #{attr} in #{self}") unless self.columns_hash.has_key?(attr)
59
+ self._metasearch_include_attributes = (self._metasearch_include_attributes + [attr]).uniq
60
+ end
61
61
  end
62
- end
63
62
 
64
- # Excludes model associations from searchability. This mean that searches can't be created against
65
- # these associations. An article that <tt>has_many :comments</tt> but excludes comments from
66
- # searching by declaring <tt>assoc_unsearchable :comments</tt> won't make any of the
67
- # <tt>comments_*</tt> methods available.
68
- def assoc_unsearchable(*args)
69
- args.flatten.each do |assoc|
70
- assoc = assoc.to_s
71
- raise(ArgumentError, "No such association #{assoc} in #{self}") unless self.reflect_on_all_associations.map {|a| a.name.to_s}.include?(assoc)
72
- self._metasearch_exclude_associations = (self._metasearch_exclude_associations + [assoc]).uniq
63
+ # Excludes model associations from searchability. This mean that searches can't be created against
64
+ # these associations. An article that <tt>has_many :comments</tt> but excludes comments from
65
+ # searching by declaring <tt>assoc_unsearchable :comments</tt> won't make any of the
66
+ # <tt>comments_*</tt> methods available.
67
+ def assoc_unsearchable(*args)
68
+ args.flatten.each do |assoc|
69
+ assoc = assoc.to_s
70
+ raise(ArgumentError, "No such association #{assoc} in #{self}") unless self.reflect_on_all_associations.map {|a| a.name.to_s}.include?(assoc)
71
+ self._metasearch_exclude_associations = (self._metasearch_exclude_associations + [assoc]).uniq
72
+ end
73
73
  end
74
- end
75
74
 
76
- # As with <tt>attr_searchable</tt> this is the whitelist version of
77
- # <tt>assoc_unsearchable</tt>
78
- def assoc_searchable(*args)
79
- args.flatten.each do |assoc|
80
- assoc = assoc.to_s
81
- raise(ArgumentError, "No such association #{assoc} in #{self}") unless self.reflect_on_all_associations.map {|a| a.name.to_s}.include?(assoc)
82
- self._metasearch_include_associations = (self._metasearch_include_associations + [assoc]).uniq
75
+ # As with <tt>attr_searchable</tt> this is the whitelist version of
76
+ # <tt>assoc_unsearchable</tt>
77
+ def assoc_searchable(*args)
78
+ args.flatten.each do |assoc|
79
+ assoc = assoc.to_s
80
+ raise(ArgumentError, "No such association #{assoc} in #{self}") unless self.reflect_on_all_associations.map {|a| a.name.to_s}.include?(assoc)
81
+ self._metasearch_include_associations = (self._metasearch_include_associations + [assoc]).uniq
82
+ end
83
83
  end
84
- end
85
84
 
86
- def search_methods(*args)
87
- opts = args.last.is_a?(Hash) ? args.pop : {}
88
- args.flatten.map(&:to_s).each do |arg|
89
- self._metasearch_methods[arg] = MetaSearch::Method.new(arg, opts)
85
+ def search_methods(*args)
86
+ opts = args.last.is_a?(Hash) ? args.pop : {}
87
+ args.flatten.map(&:to_s).each do |arg|
88
+ self._metasearch_methods[arg] = MetaSearch::Method.new(arg, opts)
89
+ end
90
90
  end
91
+
92
+ alias_method :search_method, :search_methods
91
93
  end
92
94
  end
93
95
  end
@@ -8,6 +8,20 @@ module MetaSearch
8
8
 
9
9
  private
10
10
 
11
+ def preferred_method_name(method_id)
12
+ method_name = method_id.to_s
13
+ where = Where.new(method_name) rescue nil
14
+ return nil unless where
15
+ where.aliases.each do |a|
16
+ break if method_name.sub!(/#{a}(=?)$/, "#{where.name}\\1")
17
+ end
18
+ method_name
19
+ end
20
+
21
+ def array_of_strings?(o)
22
+ o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
23
+ end
24
+
11
25
  def array_of_arrays?(vals)
12
26
  vals.is_a?(Array) && vals.first.is_a?(Array)
13
27
  end
@@ -245,7 +245,6 @@ module MetaSearch
245
245
  create_where_from_args(*args + [{
246
246
  :types => where.types,
247
247
  :predicate => "#{where.predicate}_#{compound}".to_sym,
248
- :splat_param => true,
249
248
  # Only use valid elements in the array
250
249
  :formatter => Proc.new {|param|
251
250
  param.select {|p| where.validator.call(p)}.map {|p| where.formatter.call(p)}
data/lib/meta_search.rb CHANGED
@@ -13,11 +13,11 @@ module MetaSearch
13
13
  ['equals', 'eq', {:validator => Proc.new {|param| !param.blank? || (param == false)}}],
14
14
  ['does_not_equal', 'ne', 'not_eq', {:types => ALL_TYPES, :predicate => :not_eq}],
15
15
  ['contains', 'like', 'matches', {:types => STRINGS, :predicate => :matches, :formatter => '"%#{param}%"'}],
16
- ['does_not_contain', 'nlike', 'not_matches', {:types => STRINGS, :predicate => :not_matches, :formatter => '"%#{param}%"'}],
16
+ ['does_not_contain', 'nlike', 'not_matches', {:types => STRINGS, :predicate => :does_not_match, :formatter => '"%#{param}%"'}],
17
17
  ['starts_with', 'sw', {:types => STRINGS, :predicate => :matches, :formatter => '"#{param}%"'}],
18
- ['does_not_start_with', 'dnsw', {:types => STRINGS, :predicate => :not_matches, :formatter => '"%#{param}%"'}],
18
+ ['does_not_start_with', 'dnsw', {:types => STRINGS, :predicate => :does_not_match, :formatter => '"%#{param}%"'}],
19
19
  ['ends_with', 'ew', {:types => STRINGS, :predicate => :matches, :formatter => '"%#{param}"'}],
20
- ['does_not_end_with', 'dnew', {:types => STRINGS, :predicate => :not_matches, :formatter => '"%#{param}"'}],
20
+ ['does_not_end_with', 'dnew', {:types => STRINGS, :predicate => :does_not_match, :formatter => '"%#{param}"'}],
21
21
  ['greater_than', 'gt', {:types => (NUMBERS + DATES + TIMES), :predicate => :gt}],
22
22
  ['less_than', 'lt', {:types => (NUMBERS + DATES + TIMES), :predicate => :lt}],
23
23
  ['greater_than_or_equal_to', 'gte', 'gteq', {:types => (NUMBERS + DATES + TIMES), :predicate => :gteq}],
@@ -26,8 +26,8 @@ module MetaSearch
26
26
  ['not_in', 'ni', 'not_in', {:types => ALL_TYPES, :predicate => :not_in}],
27
27
  ['is_true', {:types => BOOLEANS, :skip_compounds => true}],
28
28
  ['is_false', {:types => BOOLEANS, :skip_compounds => true, :formatter => Proc.new {|param| !param}}],
29
- ['is_present', {:types => (NUMBERS + STRINGS), :predicate => :not_eq_all, :splat_param => true, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| [nil, '']}}],
30
- ['is_blank', {:types => (NUMBERS + STRINGS), :predicate => :eq_any, :splat_param => true, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| [nil, '']}}],
29
+ ['is_present', {:types => (NUMBERS + STRINGS), :predicate => :not_eq_all, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| [nil, '']}}],
30
+ ['is_blank', {:types => (NUMBERS + STRINGS), :predicate => :eq_any, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| [nil, '']}}],
31
31
  ['is_null', {:types => ALL_TYPES, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| nil}}],
32
32
  ['is_not_null', {:types => ALL_TYPES, :predicate => :not_eq, :skip_compounds => true, :cast => :boolean, :formatter => Proc.new {|param| nil}}]
33
33
  ]
data/meta_search.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{meta_search}
8
- s.version = "0.9.8"
8
+ s.version = "0.9.9"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Ernie Miller"]
12
- s.date = %q{2010-10-20}
12
+ s.date = %q{2010-11-15}
13
13
  s.description = %q{
14
14
  Allows simple search forms to be created against an AR3 model
15
15
  and its associations, has useful view helpers for sort links
@@ -57,6 +57,8 @@ Gem::Specification.new do |s|
57
57
  "test/fixtures/projects.yml",
58
58
  "test/fixtures/schema.rb",
59
59
  "test/helper.rb",
60
+ "test/locales/en.yml",
61
+ "test/locales/es.yml",
60
62
  "test/test_search.rb",
61
63
  "test/test_view_helpers.rb"
62
64
  ]
@@ -91,23 +93,23 @@ you're feeling especially appreciative. It'd help me justify this
91
93
 
92
94
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
93
95
  s.add_development_dependency(%q<shoulda>, [">= 0"])
94
- s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.0"])
95
- s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.0"])
96
- s.add_runtime_dependency(%q<actionpack>, ["~> 3.0.0"])
97
- s.add_runtime_dependency(%q<arel>, ["~> 1.0.1"])
96
+ s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.2"])
97
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.2"])
98
+ s.add_runtime_dependency(%q<actionpack>, ["~> 3.0.2"])
99
+ s.add_runtime_dependency(%q<arel>, ["~> 2.0.2"])
98
100
  else
99
101
  s.add_dependency(%q<shoulda>, [">= 0"])
100
- s.add_dependency(%q<activerecord>, ["~> 3.0.0"])
101
- s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
102
- s.add_dependency(%q<actionpack>, ["~> 3.0.0"])
103
- s.add_dependency(%q<arel>, ["~> 1.0.1"])
102
+ s.add_dependency(%q<activerecord>, ["~> 3.0.2"])
103
+ s.add_dependency(%q<activesupport>, ["~> 3.0.2"])
104
+ s.add_dependency(%q<actionpack>, ["~> 3.0.2"])
105
+ s.add_dependency(%q<arel>, ["~> 2.0.2"])
104
106
  end
105
107
  else
106
108
  s.add_dependency(%q<shoulda>, [">= 0"])
107
- s.add_dependency(%q<activerecord>, ["~> 3.0.0"])
108
- s.add_dependency(%q<activesupport>, ["~> 3.0.0"])
109
- s.add_dependency(%q<actionpack>, ["~> 3.0.0"])
110
- s.add_dependency(%q<arel>, ["~> 1.0.1"])
109
+ s.add_dependency(%q<activerecord>, ["~> 3.0.2"])
110
+ s.add_dependency(%q<activesupport>, ["~> 3.0.2"])
111
+ s.add_dependency(%q<actionpack>, ["~> 3.0.2"])
112
+ s.add_dependency(%q<arel>, ["~> 2.0.2"])
111
113
  end
112
114
  end
113
115
 
@@ -2,7 +2,10 @@ class Developer < ActiveRecord::Base
2
2
  belongs_to :company
3
3
  has_and_belongs_to_many :projects
4
4
  has_many :notes, :as => :notable
5
-
5
+
6
6
  attr_searchable :name, :salary
7
7
  assoc_searchable :notes, :projects, :company
8
+
9
+ scope :sort_by_salary_and_name_asc, order('salary ASC, name ASC')
10
+ scope :sort_by_salary_and_name_desc, order('salary DESC, name DESC')
8
11
  end
data/test/helper.rb CHANGED
@@ -25,6 +25,8 @@ end
25
25
 
26
26
  Fixtures.create_fixtures(FIXTURES_PATH, ActiveRecord::Base.connection.tables)
27
27
 
28
+ I18n.load_path = Dir[File.join(File.dirname(__FILE__), 'locales', '*.{rb,yml}')]
29
+
28
30
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
29
31
  $LOAD_PATH.unshift(File.dirname(__FILE__))
30
32
 
@@ -0,0 +1,36 @@
1
+ en:
2
+ date:
3
+ formats:
4
+ # Use the strftime parameters for formats.
5
+ # When no format has been given, it uses default.
6
+ # You can provide other formats here if you like!
7
+ default: "%Y-%m-%d"
8
+ short: "%b %d"
9
+ long: "%B %d, %Y"
10
+
11
+ day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
12
+ abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
13
+
14
+ # Don't forget the nil at the beginning; there's no such thing as a 0th month
15
+ month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
16
+ abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
17
+ # Used in date_select and datetime_select.
18
+ order:
19
+ - :year
20
+ - :month
21
+ - :day
22
+
23
+ time:
24
+ formats:
25
+ default: "%a, %d %b %Y %H:%M:%S %z"
26
+ short: "%d %b %H:%M"
27
+ long: "%B %d, %Y %H:%M"
28
+ am: "am"
29
+ pm: "pm"
30
+
31
+ # Used in array.to_sentence.
32
+ support:
33
+ array:
34
+ words_connector: ", "
35
+ two_words_connector: " and "
36
+ last_word_connector: ", and "
@@ -0,0 +1,5 @@
1
+ es:
2
+ activerecord:
3
+ attributes:
4
+ company:
5
+ name: Nombre
data/test/test_search.rb CHANGED
@@ -298,6 +298,17 @@ class TestSearch < Test::Unit::TestCase
298
298
  end
299
299
  end
300
300
 
301
+ context "sorted by salary and name in descending order" do
302
+ setup do
303
+ @s.meta_sort = 'salary_and_name.desc'
304
+ end
305
+
306
+ should "sort by salary and name in descending order" do
307
+ assert_equal Developer.order('salary DESC, name DESC').all,
308
+ @s.all
309
+ end
310
+ end
311
+
301
312
  context "where developer is Bob-approved" do
302
313
  setup do
303
314
  @s.notes_note_equals = "A straight shooter with upper management written all over him."
@@ -198,6 +198,80 @@ class TestViewHelpers < ActionView::TestCase
198
198
  assert_no_match /Created at &#9650;/,
199
199
  @f.sort_link(:created_at, :controller => 'companies')
200
200
  end
201
+
202
+ context "and a localization" do
203
+ setup do
204
+ I18n.locale = :es
205
+ end
206
+
207
+ teardown do
208
+ I18n.locale = nil
209
+ end
210
+
211
+ should "use the localized name for the attribute" do
212
+ assert_match /Nombre/,
213
+ @f.sort_link(:name, :controller => 'companies')
214
+ end
215
+ end
216
+ end
217
+
218
+ context "A developer search form sorted by a custom sort method" do
219
+ setup do
220
+ @s = Developer.search
221
+ @s.meta_sort = 'salary_and_name.asc'
222
+ form_for @s do |f|
223
+ @f = f
224
+ end
225
+ end
226
+
227
+ should "generate a sort link with humanized text" do
228
+ assert_match /Salary and name &#9650;/,
229
+ @f.sort_link(:salary_and_name, :controller => 'developers')
230
+ end
231
+
232
+ should "sort results as expected" do
233
+ assert_equal Developer.order('salary ASC, name ASC'),
234
+ @s.all
235
+ end
236
+ end
237
+
238
+ context "A developer search form sorted by multiple columns" do
239
+ setup do
240
+ @s = Developer.search
241
+ @s.meta_sort = 'name_and_salary.asc'
242
+ form_for @s do |f|
243
+ @f = f
244
+ end
245
+ end
246
+
247
+ should "generate a sort link with humanized text" do
248
+ assert_match /Name and salary &#9650;/,
249
+ @f.sort_link(:name_and_salary, :controller => 'developers')
250
+ end
251
+
252
+ should "order by both columns in the order they were specified" do
253
+ assert_match /ORDER BY "developers"."name" ASC, "developers"."salary" ASC/,
254
+ @s.to_sql
255
+ end
256
+
257
+ should "return expected results" do
258
+ assert_equal Developer.order('name ASC, salary ASC').all,
259
+ @s.all
260
+ end
261
+ end
262
+
263
+ context "A company search form with an alternate search_key" do
264
+ setup do
265
+ @s = Company.search({}, :search_key => 'searchy_mcsearchhead')
266
+ form_for @s do |f|
267
+ @f = f
268
+ end
269
+ end
270
+
271
+ should "generate a sort link that places meta_sort param under the specified key" do
272
+ assert_match /searchy_mcsearchhead/,
273
+ @f.sort_link(:name, :controller => 'companies')
274
+ end
201
275
  end
202
276
 
203
277
  context "A company search" do
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 9
8
- - 8
9
- version: 0.9.8
8
+ - 9
9
+ version: 0.9.9
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ernie Miller
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-10-20 00:00:00 -04:00
17
+ date: 2010-11-15 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -41,8 +41,8 @@ dependencies:
41
41
  segments:
42
42
  - 3
43
43
  - 0
44
- - 0
45
- version: 3.0.0
44
+ - 2
45
+ version: 3.0.2
46
46
  type: :runtime
47
47
  version_requirements: *id002
48
48
  - !ruby/object:Gem::Dependency
@@ -56,8 +56,8 @@ dependencies:
56
56
  segments:
57
57
  - 3
58
58
  - 0
59
- - 0
60
- version: 3.0.0
59
+ - 2
60
+ version: 3.0.2
61
61
  type: :runtime
62
62
  version_requirements: *id003
63
63
  - !ruby/object:Gem::Dependency
@@ -71,8 +71,8 @@ dependencies:
71
71
  segments:
72
72
  - 3
73
73
  - 0
74
- - 0
75
- version: 3.0.0
74
+ - 2
75
+ version: 3.0.2
76
76
  type: :runtime
77
77
  version_requirements: *id004
78
78
  - !ruby/object:Gem::Dependency
@@ -84,10 +84,10 @@ dependencies:
84
84
  - - ~>
85
85
  - !ruby/object:Gem::Version
86
86
  segments:
87
- - 1
87
+ - 2
88
88
  - 0
89
- - 1
90
- version: 1.0.1
89
+ - 2
90
+ version: 2.0.2
91
91
  type: :runtime
92
92
  version_requirements: *id005
93
93
  description: "\n Allows simple search forms to be created against an AR3 model\n and its associations, has useful view helpers for sort links\n and multiparameter fields as well.\n "
@@ -136,6 +136,8 @@ files:
136
136
  - test/fixtures/projects.yml
137
137
  - test/fixtures/schema.rb
138
138
  - test/helper.rb
139
+ - test/locales/en.yml
140
+ - test/locales/es.yml
139
141
  - test/test_search.rb
140
142
  - test/test_view_helpers.rb
141
143
  has_rdoc: true