meta_search 0.9.8 → 0.9.9

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/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