yoomee-searchlogic 2.4.27

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.
Files changed (44) hide show
  1. data/.gitignore +6 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +308 -0
  4. data/Rakefile +35 -0
  5. data/VERSION.yml +5 -0
  6. data/init.rb +1 -0
  7. data/lib/searchlogic.rb +56 -0
  8. data/lib/searchlogic/active_record/association_proxy.rb +19 -0
  9. data/lib/searchlogic/active_record/consistency.rb +49 -0
  10. data/lib/searchlogic/active_record/named_scope_tools.rb +101 -0
  11. data/lib/searchlogic/core_ext/object.rb +43 -0
  12. data/lib/searchlogic/core_ext/proc.rb +17 -0
  13. data/lib/searchlogic/named_scopes/alias_scope.rb +67 -0
  14. data/lib/searchlogic/named_scopes/association_conditions.rb +132 -0
  15. data/lib/searchlogic/named_scopes/association_ordering.rb +44 -0
  16. data/lib/searchlogic/named_scopes/conditions.rb +232 -0
  17. data/lib/searchlogic/named_scopes/or_conditions.rb +141 -0
  18. data/lib/searchlogic/named_scopes/ordering.rb +48 -0
  19. data/lib/searchlogic/rails_helpers.rb +79 -0
  20. data/lib/searchlogic/search.rb +26 -0
  21. data/lib/searchlogic/search/base.rb +26 -0
  22. data/lib/searchlogic/search/conditions.rb +58 -0
  23. data/lib/searchlogic/search/date_parts.rb +23 -0
  24. data/lib/searchlogic/search/implementation.rb +14 -0
  25. data/lib/searchlogic/search/method_missing.rb +123 -0
  26. data/lib/searchlogic/search/ordering.rb +10 -0
  27. data/lib/searchlogic/search/scopes.rb +19 -0
  28. data/lib/searchlogic/search/to_yaml.rb +38 -0
  29. data/lib/searchlogic/search/unknown_condition_error.rb +15 -0
  30. data/rails/init.rb +1 -0
  31. data/searchlogic.gemspec +98 -0
  32. data/spec/searchlogic/active_record/association_proxy_spec.rb +23 -0
  33. data/spec/searchlogic/active_record/consistency_spec.rb +28 -0
  34. data/spec/searchlogic/core_ext/object_spec.rb +9 -0
  35. data/spec/searchlogic/core_ext/proc_spec.rb +8 -0
  36. data/spec/searchlogic/named_scopes/alias_scope_spec.rb +23 -0
  37. data/spec/searchlogic/named_scopes/association_conditions_spec.rb +203 -0
  38. data/spec/searchlogic/named_scopes/association_ordering_spec.rb +27 -0
  39. data/spec/searchlogic/named_scopes/conditions_spec.rb +319 -0
  40. data/spec/searchlogic/named_scopes/or_conditions_spec.rb +66 -0
  41. data/spec/searchlogic/named_scopes/ordering_spec.rb +34 -0
  42. data/spec/searchlogic/search_spec.rb +497 -0
  43. data/spec/spec_helper.rb +132 -0
  44. metadata +136 -0
@@ -0,0 +1,232 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Handles dynamically creating named scopes for columns. It allows you to do things like:
4
+ #
5
+ # User.first_name_like("ben")
6
+ # User.id_lt(10)
7
+ #
8
+ # Notice the constants in this class, they define which conditions Searchlogic provides.
9
+ #
10
+ # See the README for a more detailed explanation.
11
+ module Conditions
12
+ COMPARISON_CONDITIONS = {
13
+ :equals => [:is, :eq],
14
+ :does_not_equal => [:not_equal_to, :is_not, :not, :ne],
15
+ :less_than => [:lt, :before],
16
+ :less_than_or_equal_to => [:lte],
17
+ :greater_than => [:gt, :after],
18
+ :greater_than_or_equal_to => [:gte],
19
+ :between => [:is_between, :in_range],
20
+ :between_inclusive => [:is_between_inclusive, :in_range_inclusive]
21
+ }
22
+
23
+ WILDCARD_CONDITIONS = {
24
+ :like => [:contains, :includes],
25
+ :not_like => [:does_not_include],
26
+ :begins_with => [:bw],
27
+ :not_begin_with => [:does_not_begin_with],
28
+ :ends_with => [:ew],
29
+ :not_end_with => [:does_not_end_with]
30
+ }
31
+
32
+ BOOLEAN_CONDITIONS = {
33
+ :null => [:nil],
34
+ :not_null => [:not_nil],
35
+ :empty => [],
36
+ :blank => [],
37
+ :not_blank => [:present]
38
+ }
39
+
40
+ CONDITIONS = {}
41
+
42
+ # Add any / all variations to every comparison and wildcard condition
43
+ COMPARISON_CONDITIONS.merge(WILDCARD_CONDITIONS).each do |condition, aliases|
44
+ CONDITIONS[condition] = aliases
45
+ CONDITIONS["#{condition}_any".to_sym] = aliases.collect { |a| "#{a}_any".to_sym }
46
+ CONDITIONS["#{condition}_all".to_sym] = aliases.collect { |a| "#{a}_all".to_sym }
47
+ end
48
+
49
+ CONDITIONS[:equals_any] = CONDITIONS[:equals_any] + [:in]
50
+ CONDITIONS[:does_not_equal_all] = CONDITIONS[:does_not_equal_all] + [:not_in]
51
+
52
+ BOOLEAN_CONDITIONS.each { |condition, aliases| CONDITIONS[condition] = aliases }
53
+
54
+ PRIMARY_CONDITIONS = CONDITIONS.keys
55
+ ALIAS_CONDITIONS = CONDITIONS.values.flatten
56
+
57
+ # Is the name of the method a valid condition that can be dynamically created?
58
+ def condition?(name)
59
+ local_condition?(name)
60
+ end
61
+
62
+ private
63
+ def local_condition?(name)
64
+ return false if name.blank?
65
+ scope_names = scopes.keys.reject { |k| k == :scoped }
66
+ scope_names.include?(name.to_sym) || !condition_details(name).nil? || boolean_condition?(name)
67
+ end
68
+
69
+ def boolean_condition?(name)
70
+ column = columns_hash[name.to_s] || columns_hash[name.to_s.gsub(/^not_/, "")]
71
+ column && column.type == :boolean
72
+ end
73
+
74
+ def method_missing(name, *args, &block)
75
+ if details = condition_details(name)
76
+ create_condition(details[:column], details[:condition], args)
77
+ send(name, *args)
78
+ elsif boolean_condition?(name)
79
+ column = name.to_s.gsub(/^not_/, "")
80
+ named_scope name, :conditions => {column => (name.to_s =~ /^not_/).nil?}
81
+ send(name)
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+
88
+ def condition_details(method_name)
89
+ column_name_matcher = column_names.join("|")
90
+ conditions_matcher = (PRIMARY_CONDITIONS + ALIAS_CONDITIONS).join("|")
91
+
92
+ if method_name.to_s =~ /^(#{column_name_matcher})_(#{conditions_matcher})$/
93
+ {:column => $1, :condition => $2}
94
+ end
95
+ end
96
+
97
+ def create_condition(column, condition, args)
98
+ if PRIMARY_CONDITIONS.include?(condition.to_sym)
99
+ create_primary_condition(column, condition)
100
+ elsif ALIAS_CONDITIONS.include?(condition.to_sym)
101
+ create_alias_condition(column, condition, args)
102
+ end
103
+ end
104
+
105
+ def create_primary_condition(column, condition)
106
+ column_type = columns_hash[column.to_s].type
107
+ skip_conversion = skip_time_zone_conversion_for_attributes.include?(columns_hash[column.to_s].name.to_sym)
108
+ match_keyword = ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" ? "ILIKE" : "LIKE"
109
+
110
+ scope_options = case condition.to_s
111
+ when /^equals/
112
+ scope_options(condition, column_type, lambda { |a| attribute_condition("#{table_name}.#{column}", a) }, :skip_conversion => skip_conversion)
113
+ when /^does_not_equal/
114
+ scope_options(condition, column_type, "#{table_name}.#{column} != ?", :skip_conversion => skip_conversion)
115
+ when /^less_than_or_equal_to/
116
+ scope_options(condition, column_type, "#{table_name}.#{column} <= ?", :skip_conversion => skip_conversion)
117
+ when /^less_than/
118
+ scope_options(condition, column_type, "#{table_name}.#{column} < ?", :skip_conversion => skip_conversion)
119
+ when /^greater_than_or_equal_to/
120
+ scope_options(condition, column_type, "#{table_name}.#{column} >= ?", :skip_conversion => skip_conversion)
121
+ when /^greater_than/
122
+ scope_options(condition, column_type, "#{table_name}.#{column} > ?", :skip_conversion => skip_conversion)
123
+ when /^between_inclusive/
124
+ scope_options(condition, column_type, "#{table_name}.#{column} >= ? AND #{table_name}.#{column} <= ?", :skip_conversion => skip_conversion)
125
+ when /^between/
126
+ scope_options(condition, column_type, "#{table_name}.#{column} > ? AND #{table_name}.#{column} < ?", :skip_conversion => skip_conversion)
127
+ when /^like/
128
+ scope_options(condition, column_type, "#{table_name}.#{column} #{match_keyword} ?", :skip_conversion => skip_conversion, :value_modifier => :like)
129
+ when /^not_like/
130
+ scope_options(condition, column_type, "#{table_name}.#{column} NOT #{match_keyword} ?", :skip_conversion => skip_conversion, :value_modifier => :like)
131
+ when /^begins_with/
132
+ scope_options(condition, column_type, "#{table_name}.#{column} #{match_keyword} ?", :skip_conversion => skip_conversion, :value_modifier => :begins_with)
133
+ when /^not_begin_with/
134
+ scope_options(condition, column_type, "#{table_name}.#{column} NOT #{match_keyword} ?", :skip_conversion => skip_conversion, :value_modifier => :begins_with)
135
+ when /^ends_with/
136
+ scope_options(condition, column_type, "#{table_name}.#{column} #{match_keyword} ?", :skip_conversion => skip_conversion, :value_modifier => :ends_with)
137
+ when /^not_end_with/
138
+ scope_options(condition, column_type, "#{table_name}.#{column} NOT #{match_keyword} ?", :skip_conversion => skip_conversion, :value_modifier => :ends_with)
139
+ when "null"
140
+ {:conditions => "#{table_name}.#{column} IS NULL"}
141
+ when "not_null"
142
+ {:conditions => "#{table_name}.#{column} IS NOT NULL"}
143
+ when "empty"
144
+ {:conditions => "#{table_name}.#{column} = ''"}
145
+ when "blank"
146
+ {:conditions => "#{table_name}.#{column} = '' OR #{table_name}.#{column} IS NULL"}
147
+ when "not_blank"
148
+ {:conditions => "#{table_name}.#{column} != '' AND #{table_name}.#{column} IS NOT NULL"}
149
+ end
150
+
151
+ named_scope("#{column}_#{condition}".to_sym, scope_options)
152
+ end
153
+
154
+ # This method helps cut down on defining scope options for conditions that allow *_any or *_all conditions.
155
+ # Kepp in mind that the lambdas get cached in a method, so you want to keep the contents of the lambdas as
156
+ # fast as possible, which is why I didn't do the case statement inside of the lambda.
157
+ def scope_options(condition, column_type, sql, options = {})
158
+ case condition.to_s
159
+ when /_(any|all)$/
160
+ searchlogic_lambda(column_type, :skip_conversion => options[:skip_conversion]) { |*values|
161
+ unless values.empty?
162
+ values.flatten!
163
+ values.collect! { |value| value_with_modifier(value, options[:value_modifier]) }
164
+
165
+ join = $1 == "any" ? " OR " : " AND "
166
+
167
+ scope_sql = values.collect { |value| sql.is_a?(Proc) ? sql.call(value) : sql }.join(join)
168
+
169
+ {:conditions => [scope_sql, *expand_range_bind_variables(values)]}
170
+ else
171
+ {}
172
+ end
173
+ }
174
+ else
175
+ searchlogic_lambda(column_type, :skip_conversion => options[:skip_conversion]) { |*values|
176
+ values.collect! { |value| value_with_modifier(value, options[:value_modifier]) }
177
+
178
+ scope_sql = sql.is_a?(Proc) ? sql.call(*values) : sql
179
+
180
+ {:conditions => [scope_sql, *expand_range_bind_variables(values)]}
181
+ }
182
+ end
183
+ end
184
+
185
+ def value_with_modifier(value, modifier)
186
+ case modifier
187
+ when :like
188
+ "%#{value}%"
189
+ when :begins_with
190
+ "#{value}%"
191
+ when :ends_with
192
+ "%#{value}"
193
+ else
194
+ value
195
+ end
196
+ end
197
+
198
+ def create_alias_condition(column, condition, args)
199
+ primary_condition = primary_condition(condition)
200
+ alias_name = "#{column}_#{condition}"
201
+ primary_name = "#{column}_#{primary_condition}"
202
+ send(primary_name, *args) # go back to method_missing and make sure we create the method
203
+ (class << self; self; end).class_eval { alias_method alias_name, primary_name }
204
+ end
205
+
206
+ # Returns the primary condition for the given alias. Ex:
207
+ #
208
+ # primary_condition(:gt) => :greater_than
209
+ def primary_condition(alias_condition)
210
+ CONDITIONS.find { |k, v| k == alias_condition.to_sym || v.include?(alias_condition.to_sym) }.first
211
+ end
212
+
213
+ # Returns the primary name for any condition on a column. You can pass it
214
+ # a primary condition, alias condition, etc, and it will return the proper
215
+ # primary condition name. This helps simply logic throughout Searchlogic. Ex:
216
+ #
217
+ # condition_scope_name(:id_gt) => :id_greater_than
218
+ # condition_scope_name(:id_greater_than) => :id_greater_than
219
+ def condition_scope_name(name)
220
+ if details = condition_details(name)
221
+ if PRIMARY_CONDITIONS.include?(name.to_sym)
222
+ name
223
+ else
224
+ "#{details[:column]}_#{primary_condition(details[:condition])}".to_sym
225
+ end
226
+ else
227
+ nil
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,141 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Handles dynamically creating named scopes for 'OR' conditions. Please see the README for a more
4
+ # detailed explanation.
5
+ module OrConditions
6
+ class NoConditionSpecifiedError < StandardError; end
7
+ class UnknownConditionError < StandardError; end
8
+
9
+ def condition?(name) # :nodoc:
10
+ super || or_condition?(name)
11
+ end
12
+
13
+ def named_scope_options(name) # :nodoc:
14
+ super || super(or_conditions(name).try(:join, "_or_"))
15
+ end
16
+
17
+ private
18
+ def or_condition?(name)
19
+ !or_conditions(name).nil?
20
+ end
21
+
22
+ def method_missing(name, *args, &block)
23
+ if conditions = or_conditions(name)
24
+ create_or_condition(conditions, args)
25
+ (class << self; self; end).class_eval { alias_method name, conditions.join("_or_") } if !respond_to?(name)
26
+ send(name, *args)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def or_conditions(name)
33
+ # First determine if we should even work on the name, we want to be as quick as possible
34
+ # with this.
35
+ if (parts = split_or_condition(name)).size > 1
36
+ conditions = interpolate_or_conditions(parts)
37
+ if conditions.any?
38
+ conditions
39
+ else
40
+ nil
41
+ end
42
+ end
43
+ end
44
+
45
+ def split_or_condition(name)
46
+ parts = name.to_s.split("_or_")
47
+ new_parts = []
48
+ parts.each do |part|
49
+ if part =~ /^equal_to(_any|_all)?$/
50
+ new_parts << new_parts.pop + "_or_equal_to"
51
+ else
52
+ new_parts << part
53
+ end
54
+ end
55
+ new_parts
56
+ end
57
+
58
+ # The purpose of this method is to convert the method name parts into actual condition names.
59
+ #
60
+ # Example:
61
+ #
62
+ # ["first_name", "last_name_like"]
63
+ # => ["first_name_like", "last_name_like"]
64
+ #
65
+ # ["id_gt", "first_name_begins_with", "last_name", "middle_name_like"]
66
+ # => ["id_gt", "first_name_begins_with", "last_name_like", "middle_name_like"]
67
+ #
68
+ # Basically if a column is specified without a condition the next condition in the list
69
+ # is what will be used. Once we are able to get a consistent list of conditions we can easily
70
+ # create a scope for it.
71
+ def interpolate_or_conditions(parts)
72
+ conditions = []
73
+ last_condition = nil
74
+
75
+ parts.reverse.each do |part|
76
+ if details = condition_details(part)
77
+ # We are a searchlogic defined scope
78
+ conditions << "#{details[:column]}_#{details[:condition]}"
79
+ last_condition = details[:condition]
80
+ elsif association_details = association_condition_details(part, last_condition)
81
+ path = full_association_path(part, last_condition, association_details[:association])
82
+ conditions << "#{path[:path].join("_").to_sym}_#{path[:column]}_#{path[:condition]}"
83
+ last_condition = path[:condition] || nil
84
+ elsif local_condition?(part)
85
+ # We are a custom scope
86
+ conditions << part
87
+ elsif column_names.include?(part)
88
+ # we are a column, use the last condition
89
+ if last_condition.nil?
90
+ raise NoConditionSpecifiedError.new("The '#{part}' column doesn't know which condition to use, if you use an exact column " +
91
+ "name you need to specify a condition sometime after (ex: id_or_created_at_lt), where id would use the 'lt' condition.")
92
+ end
93
+
94
+ conditions << "#{part}_#{last_condition}"
95
+ else
96
+ raise UnknownConditionError.new("The condition '#{part}' is not a valid condition, we could not find any scopes that match this.")
97
+ end
98
+ end
99
+
100
+ conditions.reverse
101
+ end
102
+
103
+ def full_association_path(part, last_condition, given_assoc)
104
+ path = [given_assoc.name]
105
+ part.sub!(/^#{given_assoc.name}_/, "")
106
+ klass = self
107
+ while klass = klass.send(:reflect_on_association, given_assoc.name)
108
+ klass = klass.klass
109
+ if details = klass.send(:association_condition_details, part, last_condition)
110
+ path << details[:association]
111
+ part = details[:condition]
112
+ given_assoc = details[:association]
113
+ elsif details = klass.send(:condition_details, part)
114
+ return { :path => path, :column => details[:column], :condition => details[:condition] }
115
+ end
116
+ end
117
+ { :path => path, :column => part, :condition => last_condition }
118
+ end
119
+
120
+ def create_or_condition(scopes, args)
121
+ scopes_options = scopes.collect { |scope, *args| send(scope, *args).proxy_options }
122
+ # We're using first scope to determine column's type
123
+ scope = named_scope_options(scopes.first)
124
+ column_type = scope.respond_to?(:searchlogic_options) ? scope.searchlogic_options[:type] : :string
125
+ named_scope scopes.join("_or_"), searchlogic_lambda(column_type) { |*args|
126
+ merge_scopes_with_or(scopes.collect { |scope| clone.send(scope, *args) })
127
+ }
128
+ end
129
+
130
+ def merge_scopes_with_or(scopes)
131
+ scopes_options = scopes.collect { |scope| scope.scope(:find) }
132
+ conditions = scopes_options.reject { |o| o[:conditions].nil? }.collect { |o| sanitize_sql(o[:conditions]) }
133
+ scope = scopes_options.inject(scoped({})) { |current_scope, options| current_scope.scoped(options) }
134
+ options = {}
135
+ in_searchlogic_delegation { options = scope.scope(:find) }
136
+ options.delete(:readonly) unless scopes.any? { |scope| scope.proxy_options.key?(:readonly) }
137
+ options.merge(:conditions => "(" + conditions.join(") OR (") + ")")
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,48 @@
1
+ module Searchlogic
2
+ module NamedScopes
3
+ # Handles dynamically creating named scopes for ordering by columns. Example:
4
+ #
5
+ # User.ascend_by_id
6
+ # User.descend_by_username
7
+ #
8
+ # See the README for a more detailed explanation.
9
+ module Ordering
10
+ def condition?(name) # :nodoc:
11
+ super || ordering_condition?(name)
12
+ end
13
+
14
+ private
15
+ def ordering_condition?(name) # :nodoc:
16
+ !ordering_condition_details(name).nil?
17
+ end
18
+
19
+ def method_missing(name, *args, &block)
20
+ if name == :order
21
+ named_scope name, lambda { |scope_name|
22
+ return {} if !condition?(scope_name)
23
+ send(scope_name).proxy_options
24
+ }
25
+ send(name, *args)
26
+ elsif details = ordering_condition_details(name)
27
+ create_ordering_conditions(details[:column])
28
+ send(name, *args)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def ordering_condition_details(name)
35
+ if name.to_s =~ /^(ascend|descend)_by_(#{column_names.join("|")})$/
36
+ {:order_as => $1, :column => $2}
37
+ elsif name.to_s =~ /^order$/
38
+ {}
39
+ end
40
+ end
41
+
42
+ def create_ordering_conditions(column)
43
+ named_scope("ascend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} ASC"})
44
+ named_scope("descend_by_#{column}".to_sym, {:order => "#{table_name}.#{column} DESC"})
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,79 @@
1
+ module Searchlogic
2
+ module RailsHelpers
3
+ # Creates a link that alternates between acending and descending. It basically
4
+ # alternates between calling 2 named scopes: "ascend_by_*" and "descend_by_*"
5
+ #
6
+ # By default Searchlogic gives you these named scopes for all of your columns, but
7
+ # if you wanted to create your own, it will work with those too.
8
+ #
9
+ # Examples:
10
+ #
11
+ # order @search, :by => :username
12
+ # order @search, :by => :created_at, :as => "Created"
13
+ #
14
+ # This helper accepts the following options:
15
+ #
16
+ # * <tt>:by</tt> - the name of the named scope. This helper will prepend this value with "ascend_by_" and "descend_by_"
17
+ # * <tt>:as</tt> - the text used in the link, defaults to whatever is passed to :by
18
+ # * <tt>:ascend_scope</tt> - what scope to call for ascending the data, defaults to "ascend_by_:by"
19
+ # * <tt>:descend_scope</tt> - what scope to call for descending the data, defaults to "descend_by_:by"
20
+ # * <tt>:params</tt> - hash with additional params which will be added to generated url
21
+ # * <tt>:params_scope</tt> - the name of the params key to scope the order condition by, defaults to :search
22
+ def order(search, options = {}, html_options = {})
23
+ options[:params_scope] ||= :search
24
+ if !options[:as]
25
+ id = options[:by].to_s.downcase == "id"
26
+ options[:as] = id ? options[:by].to_s.upcase : options[:by].to_s.humanize
27
+ end
28
+ options[:ascend_scope] ||= "ascend_by_#{options[:by]}"
29
+ options[:descend_scope] ||= "descend_by_#{options[:by]}"
30
+ ascending = search.order.to_s == options[:ascend_scope]
31
+ new_scope = ascending ? options[:descend_scope] : options[:ascend_scope]
32
+ selected = [options[:ascend_scope], options[:descend_scope]].include?(search.order.to_s)
33
+ if selected
34
+ css_classes = html_options[:class] ? html_options[:class].split(" ") : []
35
+ if ascending
36
+ options[:as] = "&#9650;&nbsp;#{options[:as]}"
37
+ css_classes << "ascending"
38
+ else
39
+ options[:as] = "&#9660;&nbsp;#{options[:as]}"
40
+ css_classes << "descending"
41
+ end
42
+ html_options[:class] = css_classes.join(" ")
43
+ end
44
+ url_options = {
45
+ options[:params_scope] => search.conditions.merge( { :order => new_scope } )
46
+ }.deep_merge(options[:params] || {})
47
+
48
+ options[:as] = raw(options[:as]) if defined?(RailsXss)
49
+
50
+ link_to options[:as], url_for(url_options), html_options
51
+ end
52
+
53
+ # Automatically makes the form method :get if a Searchlogic::Search and sets
54
+ # the params scope to :search
55
+ def form_for(*args, &block)
56
+ if search_obj = args.find { |arg| arg.is_a?(Searchlogic::Search) }
57
+ options = args.extract_options!
58
+ options[:html] ||= {}
59
+ options[:html][:method] ||= :get
60
+ options[:url] ||= url_for
61
+ args.unshift(:search) if args.first == search_obj
62
+ args << options
63
+ end
64
+ super
65
+ end
66
+
67
+ # Automatically adds an "order" hidden field in your form to preserve how the data
68
+ # is being ordered.
69
+ def fields_for(*args, &block)
70
+ if search_obj = args.find { |arg| arg.is_a?(Searchlogic::Search) }
71
+ args.unshift(:search) if args.first == search_obj
72
+ concat(content_tag("div", hidden_field_tag("#{args.first}[order]", search_obj.order)))
73
+ super
74
+ else
75
+ super
76
+ end
77
+ end
78
+ end
79
+ end