searchgasm 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG +2 -0
  2. data/Manifest +34 -21
  3. data/{README.mdown → README.rdoc} +96 -63
  4. data/Rakefile +1 -1
  5. data/examples/README.rdoc +4 -0
  6. data/lib/searchgasm/active_record/associations.rb +40 -42
  7. data/lib/searchgasm/active_record/base.rb +75 -61
  8. data/lib/searchgasm/condition/base.rb +127 -0
  9. data/lib/searchgasm/condition/begins_with.rb +20 -0
  10. data/lib/searchgasm/condition/child_of.rb +11 -0
  11. data/lib/searchgasm/condition/contains.rb +20 -0
  12. data/lib/searchgasm/condition/descendant_of.rb +24 -0
  13. data/lib/searchgasm/condition/does_not_equal.rb +28 -0
  14. data/lib/searchgasm/condition/ends_with.rb +20 -0
  15. data/lib/searchgasm/condition/equals.rb +20 -0
  16. data/lib/searchgasm/condition/greater_than.rb +25 -0
  17. data/lib/searchgasm/condition/greater_than_or_equal_to.rb +20 -0
  18. data/lib/searchgasm/condition/inclusive_descendant_of.rb +13 -0
  19. data/lib/searchgasm/condition/keywords.rb +33 -0
  20. data/lib/searchgasm/condition/less_than.rb +25 -0
  21. data/lib/searchgasm/condition/less_than_or_equal_to.rb +20 -0
  22. data/lib/searchgasm/condition/sibling_of.rb +16 -0
  23. data/lib/searchgasm/condition/tree.rb +16 -0
  24. data/lib/searchgasm/conditions/base.rb +221 -0
  25. data/lib/searchgasm/conditions/protection.rb +30 -0
  26. data/lib/searchgasm/config.rb +137 -0
  27. data/lib/searchgasm/helpers/form_helper.rb +159 -0
  28. data/lib/searchgasm/helpers/search_helper.rb +178 -0
  29. data/lib/searchgasm/helpers/utilities_helper.rb +125 -0
  30. data/lib/searchgasm/search/base.rb +73 -179
  31. data/lib/searchgasm/search/conditions.rb +42 -166
  32. data/lib/searchgasm/search/ordering.rb +149 -0
  33. data/lib/searchgasm/search/pagination.rb +69 -0
  34. data/lib/searchgasm/search/protection.rb +61 -0
  35. data/lib/searchgasm/utilities.rb +30 -0
  36. data/lib/searchgasm/version.rb +44 -47
  37. data/lib/searchgasm.rb +57 -21
  38. data/searchgasm.gemspec +71 -46
  39. data/test/test_active_record_associations.rb +1 -1
  40. data/test/test_active_record_base.rb +4 -4
  41. data/test/test_condition.rb +143 -0
  42. data/test/{test_searchgasm_conditions.rb → test_conditions_base.rb} +43 -33
  43. data/test/test_search_base.rb +189 -0
  44. data/test/test_search_ordering.rb +91 -0
  45. data/test/test_search_pagination.rb +56 -0
  46. data/test/test_search_protection.rb +35 -0
  47. metadata +70 -45
  48. data/lib/searchgasm/search/condition.rb +0 -105
  49. data/lib/searchgasm/search/condition_types/begins_with_condition.rb +0 -26
  50. data/lib/searchgasm/search/condition_types/child_of_condition.rb +0 -17
  51. data/lib/searchgasm/search/condition_types/contains_condition.rb +0 -26
  52. data/lib/searchgasm/search/condition_types/descendant_of_condition.rb +0 -30
  53. data/lib/searchgasm/search/condition_types/does_not_equal_condition.rb +0 -34
  54. data/lib/searchgasm/search/condition_types/ends_with_condition.rb +0 -26
  55. data/lib/searchgasm/search/condition_types/equals_condition.rb +0 -26
  56. data/lib/searchgasm/search/condition_types/greater_than_condition.rb +0 -31
  57. data/lib/searchgasm/search/condition_types/greater_than_or_equal_to_condition.rb +0 -26
  58. data/lib/searchgasm/search/condition_types/inclusive_descendant_of_condition.rb +0 -19
  59. data/lib/searchgasm/search/condition_types/keywords_condition.rb +0 -39
  60. data/lib/searchgasm/search/condition_types/less_than_condition.rb +0 -31
  61. data/lib/searchgasm/search/condition_types/less_than_or_equal_to_condition.rb +0 -26
  62. data/lib/searchgasm/search/condition_types/sibling_of_condition.rb +0 -22
  63. data/lib/searchgasm/search/condition_types/tree_condition.rb +0 -20
  64. data/lib/searchgasm/search/utilities.rb +0 -34
  65. data/test/test_searchgasm_base.rb +0 -185
  66. data/test/test_searchgasm_condition_types.rb +0 -143
@@ -0,0 +1,221 @@
1
+ module Searchgasm
2
+ module Conditions # :nodoc:
3
+ # = Conditions
4
+ #
5
+ # Represents a collection of conditions and performs various tasks on that collection. For information on each condition see Searchgasm::Condition.
6
+ # Each condition has its own file and class and the source for each condition is pretty self explanatory.
7
+ class Base
8
+ include Utilities
9
+
10
+ attr_accessor :klass, :relationship_name, :scope
11
+
12
+ class << self
13
+ # Registers a condition as an available condition for a column or a class.
14
+ #
15
+ # === Example
16
+ #
17
+ # config/initializers/searchgasm.rb
18
+ # # Actual function for MySQL databases only
19
+ # class SoundsLike < Searchgasm::Condition::Base
20
+ # class << self
21
+ # # I pass you the column, you tell me what you want the method to be called.
22
+ # # If you don't want to add this condition for that column, return nil
23
+ # # It defaults to "#{column.name}_sounds_like". So if thats what you want you don't even need to do this.
24
+ # def name_for_column(column)
25
+ # super
26
+ # end
27
+ #
28
+ # # Only do this if you want aliases for your condition
29
+ # def aliases_for_column(column)
30
+ # ["#{column.name}_sounds", "#{column.name}_similar_to"]
31
+ # end
32
+ # end
33
+ #
34
+ # # You can return an array or a string. NOT a hash, because all of these conditions
35
+ # # need to eventually get merged together. The array or string can be anything you would put in
36
+ # # the :conditions option for ActiveRecord::Base.find()
37
+ # def to_conditions(value)
38
+ # ["#{quoted_table_name}.#{quoted_column_name} SOUNDS LIKE ?", value]
39
+ # end
40
+ # end
41
+ #
42
+ # Searchgasm::Seearch::Conditions.register_condition(SoundsLikeCondition)
43
+ def register_condition(klass)
44
+ raise(ArgumentError, "You can only register conditions that extend Searchgasm::Condition::Base") unless klass.ancestors.include?(Searchgasm::Condition::Base)
45
+ conditions << klass unless conditions.include?(klass)
46
+ end
47
+
48
+ # A list of available condition type classes
49
+ def conditions
50
+ @@conditions ||= []
51
+ end
52
+
53
+ def needed?(klass, conditions) # :nodoc:
54
+ if conditions.is_a?(Hash)
55
+ conditions.stringify_keys.keys.each do |condition|
56
+ return true unless klass.column_names.include?(condition)
57
+ end
58
+ end
59
+
60
+ false
61
+ end
62
+ end
63
+
64
+ def initialize(klass, init_conditions = {})
65
+ self.klass = klass
66
+ add_klass_conditions!
67
+ add_column_conditions!
68
+ add_associations!
69
+ self.conditions = init_conditions
70
+ end
71
+
72
+ # Setup methods for searching
73
+ [:all, :average, :calculate, :count, :find, :first, :maximum, :minimum, :sum].each do |method|
74
+ class_eval <<-"end_eval", __FILE__, __LINE__
75
+ def #{method}(*args)
76
+ self.conditions = args.extract_options!
77
+ args << {:conditions => sanitize}
78
+ klass.#{method}(*args)
79
+ end
80
+ end_eval
81
+ end
82
+
83
+ # A list of includes to use when searching, includes relationships
84
+ def includes
85
+ i = []
86
+ associations.each do |association|
87
+ association_includes = association.includes
88
+ i << (association_includes.blank? ? association.relationship_name.to_sym : {association.relationship_name.to_sym => association_includes})
89
+ end
90
+ i.blank? ? nil : (i.size == 1 ? i.first : i)
91
+ end
92
+
93
+ # Sanitizes the conditions down into conditions that ActiveRecord::Base.find can understand.
94
+ def sanitize
95
+ conditions = merge_conditions(*objects.collect { |object| object.sanitize })
96
+ return scope if conditions.blank?
97
+ merge_conditions(conditions, scope)
98
+ end
99
+
100
+ # Allows you to set the conditions via a hash. If you do not pass a hash it will set scope instead, so that you can continue to add conditions and ultimately
101
+ # merge it all together at the end.
102
+ def conditions=(conditions)
103
+ case conditions
104
+ when Hash
105
+ assert_valid_conditions(conditions)
106
+ remove_conditions_from_protected_assignement(conditions).each { |condition, value| send("#{condition}=", value) }
107
+ else
108
+ self.scope = conditions
109
+ end
110
+ end
111
+
112
+ # All of the active conditions (conditions that have been set)
113
+ def conditions
114
+ conditions_hash = {}
115
+ objects.each do |object|
116
+ case object
117
+ when self.class
118
+ relationship_conditions = object.conditions
119
+ next if relationship_conditions.blank?
120
+ conditions_hash[object.relationship_name.to_sym] = relationship_conditions
121
+ else
122
+ next unless object.explicitly_set_value?
123
+ conditions_hash[object.name.to_sym] = object.value
124
+ end
125
+ end
126
+ conditions_hash
127
+ end
128
+
129
+ private
130
+ def add_associations!
131
+ klass.reflect_on_all_associations.each do |association|
132
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
133
+ def #{association.name}
134
+ if @#{association.name}.nil?
135
+ @#{association.name} = self.class.new(#{association.class_name})
136
+ @#{association.name}.relationship_name = "#{association.name}"
137
+ objects << @#{association.name}
138
+ end
139
+ @#{association.name}
140
+ end
141
+
142
+ def #{association.name}=(conditions); #{association.name}.conditions = conditions; end
143
+ def reset_#{association.name}!; objects.delete(#{association.name}); @#{association.name} = nil; end
144
+ end_eval
145
+ end
146
+ end
147
+
148
+ def add_column_conditions!
149
+ klass.columns.each do |column|
150
+ self.class.conditions.each do |condition_klass|
151
+ name = condition_klass.name_for_column(column)
152
+ next if name.blank?
153
+ add_condition!(condition_klass, name, column)
154
+ condition_klass.aliases_for_column(column).each { |alias_name| add_condition_alias!(alias_name, name) }
155
+ end
156
+ end
157
+ end
158
+
159
+ def add_condition!(condition, name, column = nil)
160
+ condition_names << name
161
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
162
+ def #{name}_object
163
+ if @#{name}.nil?
164
+ @#{name} = #{condition.name}.new(klass#{column.nil? ? "" : ", \"#{column.name}\""})
165
+ objects << @#{name}
166
+ end
167
+ @#{name}
168
+ end
169
+
170
+ def #{name}; #{name}_object.value; end
171
+ def #{name}=(value); #{name}_object.value = value; end
172
+ def reset_#{name}!; objects.delete(#{name}_object); @#{name} = nil; end
173
+ end_eval
174
+ end
175
+
176
+ def add_condition_alias!(alias_name, name)
177
+ condition_names << alias_name
178
+ self.class.class_eval do
179
+ alias_method alias_name, name
180
+ alias_method "#{alias_name}=", "#{name}="
181
+ end
182
+ end
183
+
184
+ def add_klass_conditions!
185
+ self.class.conditions.each do |condition|
186
+ name = condition.name_for_klass(klass)
187
+ next if name.blank?
188
+ add_condition!(condition, name)
189
+ condition.aliases_for_klass(klass).each { |alias_name| add_condition_alias!(alias_name, name) }
190
+ end
191
+ end
192
+
193
+ def assert_valid_conditions(conditions)
194
+ keys = condition_names.collect { |condition_name| condition_name.to_sym }
195
+ keys += klass.reflect_on_all_associations.collect { |association| association.name }
196
+ conditions.symbolize_keys.assert_valid_keys(keys)
197
+ end
198
+
199
+ def associations
200
+ objects.select { |object| object.is_a?(self.class) }
201
+ end
202
+
203
+ def condition_names
204
+ @condition_names ||= []
205
+ end
206
+
207
+ def objects
208
+ @objects ||= []
209
+ end
210
+
211
+ def remove_conditions_from_protected_assignement(conditions)
212
+ return conditions if klass.accessible_conditions.nil? && klass.protected_conditions.nil?
213
+ if klass.accessible_conditions
214
+ conditions.reject { |condition, value| !klass.accessible_conditions.include?(condition.to_s) }
215
+ elsif klass.protected_conditions
216
+ conditions.reject { |condition, value| klass.protected_conditions.include?(condition.to_s) }
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,30 @@
1
+ module Searchgasm
2
+ module Conditions
3
+ # = Conditions Protection
4
+ #
5
+ # Adds protection from SQL injections. Just set protect = true and it will limit what kind of conditions it will accept.
6
+ module Protection
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ attr_accessor :protect
10
+ alias_method_chain :conditions=, :protection
11
+ end
12
+ end
13
+
14
+ def conditions_with_protection=(conditions)
15
+ unless conditions.is_a?(Hash)
16
+ if protect?
17
+ return if conditions.blank?
18
+ raise(ArgumentError, "You can not set a scope or pass SQL while the search is being protected")
19
+ end
20
+ end
21
+
22
+ self.conditions_without_protection = conditions
23
+ end
24
+
25
+ def protect?
26
+ protect == true
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,137 @@
1
+ module Searchgasm
2
+ # = Config
3
+ # Adds default configuration for all of searchgasm. Just make sure you set your config before you use Searchgasm.
4
+ # For rails the best place to do this is in config/initializers. Create a file in there called searchgasm.rb with the following content:
5
+ #
6
+ # === Example
7
+ #
8
+ # # config/iniitializers/searchgasm.rb
9
+ # Searchgasm::Config.configure do |config|
10
+ # config.you_option_here = your_value # see methods below
11
+ # end
12
+ class Config
13
+ class << self
14
+ # Convenience method for setting configuration
15
+ # See example at top of class.
16
+ def configure
17
+ yield self
18
+ end
19
+
20
+ def asc_indicator # :nodoc:
21
+ @asc_indicator ||= "&nbsp;&#9650;"
22
+ end
23
+
24
+ # The indicator that is used when the sort of a column is ascending
25
+ #
26
+ # * <tt>Default:</tt> &nbsp;&#9650;
27
+ # * <tt>Accepts:</tt> String or a Proc.
28
+ #
29
+ # === Examples
30
+ #
31
+ # config.asc_indicator = "(ASC)"
32
+ # config.asc_indicator = Proc.new { |template| template.image_tag("asc.jpg") }
33
+ def asc_indicator=(value)
34
+ @asc_indicator = value
35
+ end
36
+
37
+ def desc_indicator # :nodoc:
38
+ @desc_indicator ||= "&nbsp;&#9660;"
39
+ end
40
+
41
+ # See asc_indicator=
42
+ def desc_indicator=(value)
43
+ @desc_indicator = value
44
+ end
45
+
46
+ def pages_type # :nodoc:
47
+ @pages_type ||= :select
48
+ end
49
+
50
+ # The default value for the :type option in the pages helper.
51
+ #
52
+ # * <tt>Default:</tt> :select
53
+ # * <tt>Accepts:</tt> :select, :links
54
+ def pages_type=(value)
55
+ @pages_type = value.to_sym
56
+ end
57
+
58
+ def per_page_choices # :nodoc:
59
+ @per_page_choices ||= [10, 25, 50, 100, 150, 200, nil]
60
+ end
61
+
62
+ # The choices used in the per_page helper
63
+ #
64
+ # * <tt>Default:</tt> [10, 25, 50, 100, 150, 200, nil]
65
+ # * <tt>Accepts:</tt> Array
66
+ #
67
+ # nil means "Show all"
68
+ def per_page_choices=(value)
69
+ @per_page_choices = value
70
+ end
71
+
72
+ def per_page_type # :nodoc:
73
+ @per_page_type ||= :select
74
+ end
75
+
76
+ # The default value for the :type option in the per_page helper.
77
+ #
78
+ # * <tt>Default:</tt> :select
79
+ # * <tt>Accepts:</tt> :select, :links
80
+ def per_page_type=(value)
81
+ @per_page_type = value.to_sym
82
+ end
83
+
84
+ def hidden_fields # :nodoc:
85
+ @hidden_fields ||= (Search::Base::SPECIAL_FIND_OPTIONS - [:page])
86
+ end
87
+
88
+ # Which hidden fields to automatically include when creating a form with a Searchgasm object. See Searchgasm::Helpers::FormHelper for more info.
89
+ #
90
+ # * <tt>Default:</tt> [:order_by, :order_as, :per_page]
91
+ # * <tt>Accepts:</tt> Array, nil, false
92
+ def hidden_fields=(value)
93
+ @hidden_fields = value
94
+ end
95
+
96
+ def remote_helpers # :nodoc:
97
+ @remote_helpers ||= false
98
+ end
99
+
100
+ # Tells all helpers to default to using remote links (AJAX) instead of normal links.
101
+ #
102
+ # * <tt>Default:</tt> false
103
+ # * <tt>Accepts:</tt> Boolean
104
+ #
105
+ # nil means "Show all"
106
+ def remote_helpers=(value)
107
+ @remote_helpers = value
108
+ end
109
+
110
+ def remote_helpers? # :nodoc:
111
+ remote_helpers == true
112
+ end
113
+
114
+ def search_scope # :nodoc:
115
+
116
+ end
117
+
118
+ def search_obj_name # :nodoc:
119
+ @search_obj_name ||= :@search
120
+ end
121
+
122
+ # The instance variable name you use to assign your search to. This allows the helpers to grab your Searchgasm object without having
123
+ # to specify it everywhere.
124
+ #
125
+ # * <tt>Default:</tt> :@search
126
+ # * <tt>Accepts:</tt> String or Symbol.
127
+ #
128
+ # === Examples
129
+ #
130
+ # config.search_obj_name = :@search
131
+ # config.search_obj_name = "@search"
132
+ def search_obj_name=(value)
133
+ @search_obj_name = value
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,159 @@
1
+ module Searchgasm
2
+ module Helpers
3
+ # = Form Helper
4
+ #
5
+ # Enables you to use form_for and fields_for just like you do with an ActiveRecord object.
6
+ #
7
+ # === Examples
8
+ #
9
+ # Let's assume @search is searching Address
10
+ #
11
+ # form_for(@search) # is equivalent to form_for(:search, @search, :url => addresses_path)
12
+ # form_for([@current_user, @search]) # is equivalent to form_for(:search, @search, :url => user_addresses_path(@current_user))
13
+ # form_for([:admin, @search]) # is equivalent to form_for(:search, @search, :url => admin_addresses_path)
14
+ # form_for(:search, @search, :url => whatever_path)
15
+ #
16
+ # The goal was to mimic ActiveRecord. You can also pass a Searchgasm::Conditions::Base object as well and it will function the same way.
17
+ #
18
+ # === Automatic hidden fields generation
19
+ #
20
+ # If you pass a Searchgasm::Search::Base object it automatically adds the :order_by, :order_as, and :per_page hidden fields. This is done so that when someone
21
+ # creates a new search, their options are remembered. It keeps the search consisten and is much more user friendly. If you want to override this you can pass the
22
+ # following options. Or you can set this up in your configuration, see Searchgasm::Config for more details.
23
+ #
24
+ # === Options
25
+ #
26
+ # * <tt>:hidden_fields</tt> --- Array, a list of hidden fields to include. Defaults to [:order_by, :order_as, :per_page]. Pass false, nil, or a blank array to not include any.
27
+ module FormHelper
28
+ module Shared # :nodoc:
29
+ private
30
+ def searchgasm_object?(object)
31
+ object.is_a?(Search::Base) || object.is_a?(Conditions::Base)
32
+ end
33
+
34
+ def find_searchgasm_object(args)
35
+ case args.first
36
+ when String, Symbol
37
+ search_object = searchgasm_object?(args[1]) ? args[1] : instance_variable_get("@#{args.first}")
38
+ when Array
39
+ search_object = args.first.last
40
+ else
41
+ search_object = args.first
42
+ end
43
+
44
+ searchgasm_object?(search_object) ? search_object : nil
45
+ end
46
+
47
+ def searchgasm_args(args, search_object, for_helper = nil)
48
+ args = args.dup
49
+ first = args.shift
50
+
51
+ # Setup args
52
+ case first
53
+ when String, Symbol
54
+ args.unshift(search_object).unshift(first)
55
+ else
56
+ name = search_object.is_a?(Conditions::Base) ? (search_object.relationship_name || :conditions) : :search
57
+ args.unshift(search_object).unshift(name)
58
+ end
59
+
60
+ if for_helper != :fields_for
61
+ options = args.extract_options!
62
+ options[:html] ||= {}
63
+ options[:html][:method] ||= :get
64
+ options[:method] ||= options[:html][:method] if for_helper == :remote_form_for
65
+ options[:html][:id] ||= searchgasm_form_id(search_object)
66
+
67
+ # Setup options
68
+ case first
69
+ when Array
70
+ first.pop
71
+ first << search_object.klass.new
72
+ options[:url] ||= polymorphic_path(first)
73
+ else
74
+ options[:url] ||= polymorphic_path(search_object.klass.new)
75
+ end
76
+
77
+ args << options
78
+ end
79
+
80
+ args
81
+ end
82
+
83
+ def insert_searchgasm_fields(args, search_object)
84
+ return unless search_object.is_a?(Search::Base)
85
+ name = args.first
86
+ options = args.extract_options!
87
+ (options.delete(:hidden_fields) || Config.hidden_fields).each do |option|
88
+ concat(hidden_field(name, option, :object => search_object, :value => (option == :order_by ? searchgasm_order_by_value(search_object.order_by) : search_object.send(option))))
89
+ end
90
+ args << options
91
+ end
92
+ end
93
+
94
+ module Base # :nodoc:
95
+ include Shared
96
+
97
+ def fields_for_with_searchgasm(*args, &block)
98
+ search_object = find_searchgasm_object(args)
99
+ if search_object
100
+ new_args = searchgasm_args(args, search_object, :fields_for)
101
+ insert_searchgasm_fields(new_args, search_object)
102
+ fields_for_without_searchgasm(*new_args, &block)
103
+ else
104
+ fields_for_without_searchgasm(*args, &block)
105
+ end
106
+ end
107
+
108
+ def form_for_with_searchgasm(*args, &block)
109
+ search_object = find_searchgasm_object(args)
110
+ if search_object
111
+ form_for_without_searchgasm(*searchgasm_args(args, search_object, :form_for), &block)
112
+ else
113
+ form_for_without_searchgasm(*args, &block)
114
+ end
115
+ end
116
+
117
+ def remote_form_for_with_searchgasm(*args, &block)
118
+ search_object = find_searchgasm_object(args)
119
+ if search_object
120
+ remote_form_for_without_searchgasm(*searchgasm_args(args, search_object, :remote_form_for), &block)
121
+ else
122
+ remote_form_for_without_searchgasm(*args, &block)
123
+ end
124
+ end
125
+ end
126
+
127
+ module FormBuilder # :nodoc:
128
+ include Shared
129
+
130
+ def fields_for_with_searchgasm(*args, &block)
131
+ search_object = find_searchgasm_object(args)
132
+ if search_object
133
+ new_args = searchgasm_args(args, search_object, :fields_for)
134
+ insert_searchgasm_fields(new_args, search_object)
135
+ fields_for_without_searchgasm(*new_args, &block)
136
+ else
137
+ fields_for_without_searchgasm(*args, &block)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ if defined?(ActionView)
146
+ ActionView::Base.send(:include, Searchgasm::Helpers::FormHelper::Base)
147
+
148
+ ActionView::Base.class_eval do
149
+ alias_method_chain :fields_for, :searchgasm
150
+ alias_method_chain :form_for, :searchgasm
151
+ alias_method_chain :remote_form_for, :searchgasm
152
+ end
153
+
154
+ ActionView::Helpers::FormBuilder.send(:include, Searchgasm::Helpers::FormHelper::FormBuilder)
155
+
156
+ ActionView::Helpers::FormBuilder.class_eval do
157
+ alias_method_chain :fields_for, :searchgasm
158
+ end
159
+ end