searchgasm 1.1.3 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +10 -1
- data/Manifest +2 -2
- data/README.rdoc +2 -17
- data/Rakefile +1 -1
- data/lib/searchgasm/active_record/associations.rb +0 -20
- data/lib/searchgasm/active_record/base.rb +16 -44
- data/lib/searchgasm/conditions/base.rb +5 -4
- data/lib/searchgasm/core_ext/hash.rb +13 -0
- data/lib/searchgasm/helpers/control_types/link.rb +8 -10
- data/lib/searchgasm/helpers/control_types/links.rb +4 -4
- data/lib/searchgasm/helpers/control_types/select.rb +4 -9
- data/lib/searchgasm/helpers/utilities.rb +55 -31
- data/lib/searchgasm/search/base.rb +17 -11
- data/lib/searchgasm/search/conditions.rb +14 -3
- data/lib/searchgasm/search/ordering.rb +16 -16
- data/lib/searchgasm/search/searching.rb +32 -0
- data/lib/searchgasm/version.rb +2 -2
- data/lib/searchgasm.rb +2 -1
- data/searchgasm.gemspec +9 -6
- data/test/fixtures/user_groups.yml +13 -0
- data/test/test_active_record_associations.rb +56 -36
- data/test/test_active_record_base.rb +46 -56
- data/test/test_condition_base.rb +43 -20
- data/test/test_condition_types.rb +0 -11
- data/test/test_conditions_base.rb +113 -120
- data/test/test_conditions_protection.rb +6 -17
- data/test/test_config.rb +2 -11
- data/test/test_helper.rb +38 -7
- data/test/test_search_base.rb +7 -18
- data/test/test_search_conditions.rb +26 -14
- data/test/test_search_ordering.rb +0 -11
- data/test/test_search_pagination.rb +0 -11
- data/test/test_search_protection.rb +0 -11
- metadata +8 -5
- data/lib/searchgasm/shared/searching.rb +0 -43
- data/test/text_config.rb +0 -1
data/CHANGELOG.rdoc
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
== 1.
|
1
|
+
== 1.2.0 released 2008-09-24
|
2
|
+
|
3
|
+
* Added searchgasm_params and searchgasm_url helper to use outside of the control type helpers.
|
4
|
+
* Added dup and clone methods that work properly for searchgasm objects
|
5
|
+
* Fixed bug to remove nil scope values, HABTM likes to add :limit => nil
|
6
|
+
* Removed unnecessary build_search methods for associations
|
7
|
+
* Removed support for searching with conditions only. This just made things much more complicated when you can accomplish the same thing by starting a new search and only setting conditions.
|
8
|
+
* Fixed bug when searching with *any* conditions to use left outer joins instead of inner joins.
|
9
|
+
|
10
|
+
== 1.1.3 released 2008-09-23
|
2
11
|
|
3
12
|
* Setting a condition to nil removes it if the condition is set to ignore blanks
|
4
13
|
* Setting search.conditions = "some sql" will reset ALL conditions. Alternatively search.conditions => {:first_name_contains => "Ben"} will overwrite "some sql". The same goes with search.conditions.first_name_contains = "Ben".
|
data/Manifest
CHANGED
@@ -38,7 +38,7 @@ lib/searchgasm/search/conditions.rb
|
|
38
38
|
lib/searchgasm/search/ordering.rb
|
39
39
|
lib/searchgasm/search/pagination.rb
|
40
40
|
lib/searchgasm/search/protection.rb
|
41
|
-
lib/searchgasm/
|
41
|
+
lib/searchgasm/search/searching.rb
|
42
42
|
lib/searchgasm/shared/utilities.rb
|
43
43
|
lib/searchgasm/shared/virtual_classes.rb
|
44
44
|
lib/searchgasm/version.rb
|
@@ -49,6 +49,7 @@ Rakefile
|
|
49
49
|
README.rdoc
|
50
50
|
test/fixtures/accounts.yml
|
51
51
|
test/fixtures/orders.yml
|
52
|
+
test/fixtures/user_groups.yml
|
52
53
|
test/fixtures/users.yml
|
53
54
|
test/libs/acts_as_tree.rb
|
54
55
|
test/libs/rexml_fix.rb
|
@@ -65,4 +66,3 @@ test/test_search_conditions.rb
|
|
65
66
|
test/test_search_ordering.rb
|
66
67
|
test/test_search_pagination.rb
|
67
68
|
test/test_search_protection.rb
|
68
|
-
test/text_config.rb
|
data/README.rdoc
CHANGED
@@ -177,19 +177,6 @@ Any of the options used in the above example can be used in these, but for the s
|
|
177
177
|
search.conditions.first_name_contains = "Ben"
|
178
178
|
search.per_page = 20
|
179
179
|
search.all
|
180
|
-
|
181
|
-
== Search with conditions only
|
182
|
-
|
183
|
-
Don't need pagination, ordering, or any of the other options? Search with conditions only.
|
184
|
-
|
185
|
-
conditions = User.new_conditions(:age_gt => 18)
|
186
|
-
conditions.first_name_contains = "Ben"
|
187
|
-
conditions.all
|
188
|
-
# ... all operations above are available
|
189
|
-
|
190
|
-
Pass a conditions object right into ActiveRecord:
|
191
|
-
|
192
|
-
User.all(:conditions => conditions)
|
193
180
|
|
194
181
|
== Match ANY or ALL of the conditions
|
195
182
|
|
@@ -250,12 +237,11 @@ or
|
|
250
237
|
|
251
238
|
== Always use protection...against SQL injections
|
252
239
|
|
253
|
-
If there is one thing we all know, it's to always use protection against SQL injections. That's why searchgasm protects you by default. The new\_search
|
240
|
+
If there is one thing we all know, it's to always use protection against SQL injections. That's why searchgasm protects you by default. The new\_search methods protect mass assignments by default (instantiation and search.options = {}). This means that various checks are done to ensure it is not possible to perform any type of SQL injection during mass assignments. But this also limits how you can search, meaning you can't write raw SQL. If you want to be daring and search without protection, all that you have to do is add ! to the end of the method: new\_search!.
|
254
241
|
|
255
242
|
=== Protected from SQL injections
|
256
243
|
|
257
244
|
search = Account.new_search(params[:search])
|
258
|
-
conditions = Account.new_conditions(params[:conditions])
|
259
245
|
|
260
246
|
=== *NOT* protected from SQL injections
|
261
247
|
|
@@ -263,11 +249,10 @@ If there is one thing we all know, it's to always use protection against SQL inj
|
|
263
249
|
accounts = Account.all(params[:search])
|
264
250
|
account = Account.first(params[:search])
|
265
251
|
search = Account.new_search!(params[:search])
|
266
|
-
conditions = Account.new_conditions!(params[:conditions])
|
267
252
|
|
268
253
|
I'm sure you already knew this, but it's tempting to do this when you can pass the params hash right into these methods.
|
269
254
|
|
270
|
-
Lesson learned: use new\_search
|
255
|
+
Lesson learned: use new\_search when passing in params as *ANY* of the options.
|
271
256
|
|
272
257
|
== Available Conditions
|
273
258
|
|
data/Rakefile
CHANGED
@@ -10,7 +10,7 @@ Echoe.new 'searchgasm' do |p|
|
|
10
10
|
p.project = 'searchgasm'
|
11
11
|
p.summary = "Object based ActiveRecord searching, ordering, pagination, and more!"
|
12
12
|
p.url = "http://github.com/binarylogic/searchgasm"
|
13
|
-
p.dependencies =
|
13
|
+
p.dependencies = ['activerecord', 'activesupport >= 2.1.0']
|
14
14
|
p.include_rakefile = true
|
15
15
|
end
|
16
16
|
|
@@ -11,26 +11,6 @@ module Searchgasm
|
|
11
11
|
args << filter_options_with_searchgasm(options)
|
12
12
|
find_without_searchgasm(*args)
|
13
13
|
end
|
14
|
-
|
15
|
-
# See build_conditions under Searchgasm::ActiveRecord::Base. This is the same thing but for associations.
|
16
|
-
def build_conditions(options = {}, &block)
|
17
|
-
@reflection.klass.send(:with_scope, :find => construct_scope[:find]) { @reflection.klass.build_conditions(options, &block) }
|
18
|
-
end
|
19
|
-
|
20
|
-
# See build_conditions! under Searchgasm::ActiveRecord::Base. This is the same thing but for associations.
|
21
|
-
def build_conditions!(options = {}, &block)
|
22
|
-
@reflection.klass.send(:with_scope, :find => construct_scope[:find]) { @reflection.klass.build_conditions!(options, &block) }
|
23
|
-
end
|
24
|
-
|
25
|
-
# See build_search under Searchgasm::ActiveRecord::Base. This is the same thing but for associations.
|
26
|
-
def build_search(options = {}, &block)
|
27
|
-
@reflection.klass.send(:with_scope, :find => construct_scope[:find]) { @reflection.klass.build_search(options, &block) }
|
28
|
-
end
|
29
|
-
|
30
|
-
# See build_conditions! under Searchgasm::ActiveRecord::Base. This is the same thing but for associations.
|
31
|
-
def build_search!(options = {}, &block)
|
32
|
-
@reflection.klass.send(:with_scope, :find => construct_scope[:find]) { @reflection.klass.build_search!(options, &block) }
|
33
|
-
end
|
34
14
|
end
|
35
15
|
|
36
16
|
module Shared
|
@@ -11,6 +11,7 @@ module Searchgasm
|
|
11
11
|
def calculate_with_searchgasm(*args)
|
12
12
|
options = args.extract_options!
|
13
13
|
options = filter_options_with_searchgasm(options, false)
|
14
|
+
args[1] = primary_key if options[:distinct] && [nil, :all].include?(args[1]) # quick fix for adding a column name if distinct is true and no specific column is provided
|
14
15
|
args << options
|
15
16
|
calculate_without_searchgasm(*args)
|
16
17
|
end
|
@@ -47,30 +48,6 @@ module Searchgasm
|
|
47
48
|
with_scope_without_searchgasm(method_scoping, action, &block)
|
48
49
|
end
|
49
50
|
|
50
|
-
# This is a special method that Searchgasm adds in. It returns a new conditions object on the model. So you can search by conditions *only*.
|
51
|
-
#
|
52
|
-
# <b>This method is "protected". Meaning it checks the passed options for SQL injections. So trying to write raw SQL in *any* of the option will result in a raised exception. It's safe to pass a params object when instantiating.</b>
|
53
|
-
#
|
54
|
-
# === Examples
|
55
|
-
#
|
56
|
-
# conditions = User.new_conditions
|
57
|
-
# conditions.first_name_contains = "Ben"
|
58
|
-
# conditions.all # can call any search method: first, find(:all), find(:first), sum("id"), etc...
|
59
|
-
def build_conditions(values = {}, &block)
|
60
|
-
conditions = searchgasm_conditions
|
61
|
-
conditions.protect = true
|
62
|
-
conditions.conditions = values
|
63
|
-
yield conditions if block_given?
|
64
|
-
conditions
|
65
|
-
end
|
66
|
-
|
67
|
-
# See build_conditions. This is the same method but *without* protection. Do *NOT* pass in a params object to this method.
|
68
|
-
def build_conditions!(values = {}, &block)
|
69
|
-
conditions = searchgasm_conditions(values)
|
70
|
-
yield conditions if block_given?
|
71
|
-
conditions
|
72
|
-
end
|
73
|
-
|
74
51
|
# This is a special method that Searchgasm adds in. It returns a new search object on the model. So you can search via an object.
|
75
52
|
#
|
76
53
|
# <b>This method is "protected". Meaning it checks the passed options for SQL injections. So trying to write raw SQL in *any* of the option will result in a raised exception. It's safe to pass a params object when instantiating.</b>
|
@@ -86,7 +63,7 @@ module Searchgasm
|
|
86
63
|
# search.order_by = {:user_group => :name}
|
87
64
|
# search.all # can call any search method: first, find(:all), find(:first), sum("id"), etc...
|
88
65
|
def build_search(options = {}, &block)
|
89
|
-
search =
|
66
|
+
search = searchgasm_search
|
90
67
|
search.protect = true
|
91
68
|
search.options = options
|
92
69
|
yield search if block_given?
|
@@ -97,7 +74,7 @@ module Searchgasm
|
|
97
74
|
#
|
98
75
|
# This also has an alias "new_search!"
|
99
76
|
def build_search!(options = {}, &block)
|
100
|
-
search =
|
77
|
+
search = searchgasm_search(options)
|
101
78
|
yield search if block_given?
|
102
79
|
search
|
103
80
|
end
|
@@ -120,7 +97,7 @@ module Searchgasm
|
|
120
97
|
|
121
98
|
# This is the reverse of conditions_protected. You can specify conditions here and *only* these conditions will be allowed in mass assignment. Any condition not specified here will be blocked.
|
122
99
|
def conditions_accessible(*conditions)
|
123
|
-
write_inheritable_attribute(:conditions_accessible, Set.new(conditions.map(&:to_s)) + (
|
100
|
+
write_inheritable_attribute(:conditions_accessible, Set.new(conditions.map(&:to_s)) + (accessible_conditions || []))
|
124
101
|
end
|
125
102
|
|
126
103
|
def accessible_conditions # :nodoc:
|
@@ -130,7 +107,7 @@ module Searchgasm
|
|
130
107
|
private
|
131
108
|
def filter_options_with_searchgasm(options = {}, searching = true)
|
132
109
|
return options unless Searchgasm::Search::Base.needed?(self, options)
|
133
|
-
search = Searchgasm::Search::Base.create_virtual_class(self).new # call explicitly to avoid merging the scopes into the
|
110
|
+
search = Searchgasm::Search::Base.create_virtual_class(self).new # call explicitly to avoid merging the scopes into the search
|
134
111
|
search.acting_as_filter = true
|
135
112
|
conditions = options.delete(:conditions) || options.delete("conditions") || {}
|
136
113
|
if conditions
|
@@ -145,15 +122,7 @@ module Searchgasm
|
|
145
122
|
search.sanitize(searching)
|
146
123
|
end
|
147
124
|
|
148
|
-
def
|
149
|
-
searcher = Searchgasm::Conditions::Base.create_virtual_class(self).new
|
150
|
-
conditions = scope(:find) && scope(:find)[:conditions]
|
151
|
-
searcher.scope = {:conditions => conditions} if conditions
|
152
|
-
searcher.conditions = options
|
153
|
-
searcher
|
154
|
-
end
|
155
|
-
|
156
|
-
def searchgasm_searcher(options = {})
|
125
|
+
def searchgasm_search(options = {})
|
157
126
|
scope = {}
|
158
127
|
current_scope = scope(:find) && scope(:find).deep_dup
|
159
128
|
if current_scope
|
@@ -162,12 +131,17 @@ module Searchgasm
|
|
162
131
|
next if value.blank?
|
163
132
|
scope[option] = value
|
164
133
|
end
|
134
|
+
|
135
|
+
# Delete nil values in the scope, for some reason habtm relationships like to pass :limit => nil
|
136
|
+
new_scope = {}
|
137
|
+
current_scope.each { |k, v| new_scope[k] = v unless v.nil? }
|
138
|
+
current_scope = new_scope
|
165
139
|
end
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
140
|
+
search = Searchgasm::Search::Base.create_virtual_class(self).new
|
141
|
+
search.scope = scope
|
142
|
+
search.options = current_scope
|
143
|
+
search.options = options
|
144
|
+
search
|
171
145
|
end
|
172
146
|
end
|
173
147
|
end
|
@@ -181,8 +155,6 @@ module ActiveRecord #:nodoc: all
|
|
181
155
|
alias_method_chain :calculate, :searchgasm
|
182
156
|
alias_method_chain :find, :searchgasm
|
183
157
|
alias_method_chain :with_scope, :searchgasm
|
184
|
-
alias_method :new_conditions, :build_conditions
|
185
|
-
alias_method :new_conditions!, :build_conditions!
|
186
158
|
alias_method :new_search, :build_search
|
187
159
|
alias_method :new_search!, :build_search!
|
188
160
|
|
@@ -6,7 +6,6 @@ module Searchgasm
|
|
6
6
|
# Each condition has its own file and class and the source for each condition is pretty self explanatory.
|
7
7
|
class Base
|
8
8
|
include Shared::Utilities
|
9
|
-
include Shared::Searching
|
10
9
|
include Shared::VirtualClasses
|
11
10
|
|
12
11
|
attr_accessor :any, :relationship_name
|
@@ -65,6 +64,8 @@ module Searchgasm
|
|
65
64
|
end
|
66
65
|
|
67
66
|
def needed?(model_class, conditions) # :nodoc:
|
67
|
+
return false if conditions.blank?
|
68
|
+
|
68
69
|
if conditions.is_a?(Hash)
|
69
70
|
return true if conditions[:any]
|
70
71
|
column_names = model_class.column_names
|
@@ -105,11 +106,11 @@ module Searchgasm
|
|
105
106
|
end
|
106
107
|
|
107
108
|
# A list of joins to use when searching, includes relationships
|
108
|
-
def
|
109
|
+
def auto_joins
|
109
110
|
j = []
|
110
111
|
associations.each do |association|
|
111
112
|
next if association.conditions.blank?
|
112
|
-
association_joins = association.
|
113
|
+
association_joins = association.auto_joins
|
113
114
|
j << (association_joins.blank? ? association.relationship_name.to_sym : {association.relationship_name.to_sym => association_joins})
|
114
115
|
end
|
115
116
|
j.blank? ? nil : (j.size == 1 ? j.first : j)
|
@@ -262,7 +263,7 @@ module Searchgasm
|
|
262
263
|
end
|
263
264
|
|
264
265
|
def reset_objects!
|
265
|
-
objects.each { |object| eval("@#{object.name} = nil") }
|
266
|
+
objects.each { |object| object.class < ::Searchgasm::Conditions::Base ? eval("@#{object.relationship_name} = nil") : eval("@#{object.name} = nil") }
|
266
267
|
objects.clear
|
267
268
|
end
|
268
269
|
|
@@ -16,6 +16,19 @@ module Searchgasm
|
|
16
16
|
new_hash
|
17
17
|
end
|
18
18
|
|
19
|
+
def deep_delete_duplicates(hash)
|
20
|
+
hash.each do |k, v|
|
21
|
+
if v.is_a?(Hash) && self[k]
|
22
|
+
self[k].deep_delete_duplicates(v)
|
23
|
+
self.delete(k) if self[k].blank?
|
24
|
+
else
|
25
|
+
self.delete(k)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
19
32
|
# assert_valid_keys was killing performance. Array.flatten was the culprit, so I rewrote this method, got a 35% performance increase
|
20
33
|
def fast_assert_valid_keys(valid_keys)
|
21
34
|
unknown_keys = keys - valid_keys
|
@@ -186,35 +186,33 @@ module Searchgasm
|
|
186
186
|
|
187
187
|
private
|
188
188
|
def add_order_by_link_defaults!(order_by, options = {})
|
189
|
-
|
189
|
+
add_searchgasm_control_defaults!(:order_by, options)
|
190
190
|
options[:text] ||= determine_order_by_text(order_by)
|
191
191
|
options[:asc_indicator] ||= Config.asc_indicator
|
192
192
|
options[:desc_indicator] ||= Config.desc_indicator
|
193
193
|
options[:text] += options[:search_obj].desc? ? options[:desc_indicator] : options[:asc_indicator] if options[:search_obj].order_by == order_by
|
194
|
-
|
195
|
-
url_hash[:order_as] = (options[:search_obj].order_by == order_by && options[:search_obj].asc?) ? "DESC" : "ASC"
|
196
|
-
options[:url] = searchgasm_url(url_hash, options)
|
194
|
+
options[:url] = searchgasm_params(options.merge(:search_params => {:order_by => order_by}))
|
197
195
|
options
|
198
196
|
end
|
199
197
|
|
200
198
|
def add_order_as_link_defaults!(order_as, options = {})
|
201
|
-
|
199
|
+
add_searchgasm_control_defaults!(:order_as, options)
|
202
200
|
options[:text] ||= order_as.to_s
|
203
|
-
options[:url] =
|
201
|
+
options[:url] = searchgasm_params(options.merge(:search_params => {:order_as => order_as}))
|
204
202
|
options
|
205
203
|
end
|
206
204
|
|
207
205
|
def add_per_page_link_defaults!(per_page, options = {})
|
208
|
-
|
206
|
+
add_searchgasm_control_defaults!(:per_page, options)
|
209
207
|
options[:text] ||= per_page.blank? ? "Show all" : "#{per_page} per page"
|
210
|
-
options[:url] =
|
208
|
+
options[:url] = searchgasm_params(options.merge(:search_params => {:per_page => per_page}))
|
211
209
|
options
|
212
210
|
end
|
213
211
|
|
214
212
|
def add_page_link_defaults!(page, options = {})
|
215
|
-
|
213
|
+
add_searchgasm_control_defaults!(:page, options)
|
216
214
|
options[:text] ||= page.to_s
|
217
|
-
options[:url] =
|
215
|
+
options[:url] = searchgasm_params(options.merge(:search_params => {:page => page}))
|
218
216
|
options
|
219
217
|
end
|
220
218
|
|
@@ -148,19 +148,19 @@ module Searchgasm
|
|
148
148
|
|
149
149
|
private
|
150
150
|
def add_order_by_links_defaults!(options)
|
151
|
-
|
151
|
+
add_searchgasm_control_defaults!(:order_by, options)
|
152
152
|
options[:choices] ||= options[:search_obj].klass.column_names.map(&:humanize)
|
153
153
|
options
|
154
154
|
end
|
155
155
|
|
156
156
|
def add_order_as_links_defaults!(options)
|
157
|
-
|
157
|
+
add_searchgasm_control_defaults!(:order_as, options)
|
158
158
|
options[:choices] = [:asc, :desc]
|
159
159
|
options
|
160
160
|
end
|
161
161
|
|
162
162
|
def add_per_page_links_defaults!(options)
|
163
|
-
|
163
|
+
add_searchgasm_control_defaults!(:per_page, options)
|
164
164
|
options[:choices] ||= Config.per_page_choices.dup
|
165
165
|
if !options[:search_obj].per_page.blank? && !options[:choices].include?(options[:search_obj].per_page)
|
166
166
|
options[:choices] << options[:search_obj].per_page
|
@@ -173,7 +173,7 @@ module Searchgasm
|
|
173
173
|
end
|
174
174
|
|
175
175
|
def add_page_links_defaults!(options)
|
176
|
-
|
176
|
+
add_searchgasm_control_defaults!(:page, options)
|
177
177
|
options[:first_page] ||= 1
|
178
178
|
options[:last_page] ||= options[:search_obj].page_count
|
179
179
|
options[:current_page] ||= options[:search_obj].page
|
@@ -13,7 +13,7 @@ module Searchgasm
|
|
13
13
|
|
14
14
|
# Please see order_as_links. All options are the same and applicable here. The only difference is that instead of a group of links, this gets returned as a select form element that will perform the same function when the value is changed.
|
15
15
|
def order_as_select(options = {})
|
16
|
-
|
16
|
+
add_order_as_select_defaults!(options)
|
17
17
|
searchgasm_state_for(:order_as, options) + select(options[:params_scope], :order_as, options[:choices], options[:tag], options[:html])
|
18
18
|
end
|
19
19
|
|
@@ -59,19 +59,14 @@ module Searchgasm
|
|
59
59
|
options[:tag] ||= {}
|
60
60
|
options[:tag][:object] = options[:search_obj]
|
61
61
|
|
62
|
-
url =
|
63
|
-
url.delete(option)
|
64
|
-
url = searchgasm_url(url, options)
|
65
|
-
url = url_for(url)
|
66
|
-
url_option = CGI.escape((options[:params_scope].blank? ? "#{option}" : "#{options[:params_scope]}[#{option}]")) + "='+this.value"
|
67
|
-
url += (url.last == "?" ? "" : (url.include?("?") ? "&" : "?")) + url_option
|
62
|
+
url = searchgasm_url(options.merge(:literal_search_params => {option => "'+this.value+'"}))
|
68
63
|
|
69
64
|
options[:html][:onchange] ||= ""
|
70
65
|
options[:html][:onchange] += ";"
|
71
66
|
if !options[:remote]
|
72
|
-
options[:html][:onchange] += "window.location='#{url};"
|
67
|
+
options[:html][:onchange] += "window.location='#{url}';"
|
73
68
|
else
|
74
|
-
options[:html][:onchange] += remote_function(:url => url, :method => :get).gsub(/\\'\+this.value'/, "'+this.value")
|
69
|
+
options[:html][:onchange] += remote_function(:url => url, :method => :get).gsub(/\\'\+this.value\+\\'/, "'+this.value+'")
|
75
70
|
end
|
76
71
|
options[:html][:id] ||= ""
|
77
72
|
options
|
@@ -1,41 +1,63 @@
|
|
1
1
|
module Searchgasm
|
2
2
|
module Helpers #:nodoc:
|
3
3
|
module Utilities # :nodoc:
|
4
|
+
# Builds a hash of params for creating a url.
|
5
|
+
def searchgasm_params(options = {})
|
6
|
+
add_searchgasm_defaults!(options)
|
7
|
+
options[:search_params] ||= {}
|
8
|
+
options[:literal_search_params] ||= {}
|
9
|
+
options[:params] ||= {}
|
10
|
+
params_copy = params.deep_dup.with_indifferent_access
|
11
|
+
search_params = options[:params_scope].blank? ? params_copy : params_copy.delete(options[:params_scope])
|
12
|
+
search_params ||= {}
|
13
|
+
search_params = search_params.with_indifferent_access
|
14
|
+
search_params.delete(:commit)
|
15
|
+
search_params.delete(:page)
|
16
|
+
search_params.deep_delete_duplicates(options[:literal_search_params])
|
17
|
+
|
18
|
+
if options[:search_params]
|
19
|
+
search_params.deep_merge!(options[:search_params])
|
20
|
+
|
21
|
+
if options[:search_params][:order_by] && !options[:search_params][:order_as]
|
22
|
+
search_params[:order_as] = (options[:search_obj].order_by == options[:search_params][:order_by] && options[:search_obj].asc?) ? "DESC" : "ASC"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
new_params = params_copy
|
27
|
+
new_params.deep_merge!(options[:params])
|
28
|
+
|
29
|
+
if options[:params_scope].blank? || search_params.blank?
|
30
|
+
new_params
|
31
|
+
else
|
32
|
+
new_params.merge(options[:params_scope] => search_params)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def searchgasm_url(options = {})
|
37
|
+
search_params = searchgasm_params(options)
|
38
|
+
url = url_for(search_params)
|
39
|
+
literal_param_strings = literal_param_strings(options[:literal_search_params], options[:params_scope].blank? ? "" : "#{options[:params_scope]}")
|
40
|
+
url += (url.last == "?" ? "" : (url.include?("?") ? "&" : "?")) + literal_param_strings.join("&")
|
41
|
+
end
|
42
|
+
|
4
43
|
private
|
5
44
|
# Adds default options for all helper methods.
|
6
|
-
def
|
45
|
+
def add_searchgasm_defaults!(options)
|
7
46
|
options[:params_scope] = :search unless options.has_key?(:params_scope)
|
8
47
|
options[:search_obj] ||= instance_variable_get("@#{options[:params_scope]}")
|
9
48
|
raise(ArgumentError, "@search object could not be inferred, please specify: :search_obj => @search or :params_scope => :search_obj_name") unless options[:search_obj].is_a?(Searchgasm::Search::Base)
|
49
|
+
options
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds default options for all control type helper methods.
|
53
|
+
def add_searchgasm_control_defaults!(option, options)
|
54
|
+
add_searchgasm_defaults!(options)
|
10
55
|
options[:html] ||= {}
|
11
56
|
options[:html][:class] ||= ""
|
12
57
|
searchgasm_add_class!(options[:html], option)
|
13
58
|
options
|
14
59
|
end
|
15
60
|
|
16
|
-
def searchgasm_url(url_hash, options)
|
17
|
-
url = options[:params_scope].blank? ? url_hash : {options[:params_scope] => url_hash}
|
18
|
-
!options[:url_params].is_a?(Hash) ? url : url.merge(options[:url_params])
|
19
|
-
end
|
20
|
-
|
21
|
-
def searchgasm_url_hash(option, value, options)
|
22
|
-
params_copy = params.deep_dup.with_indifferent_access
|
23
|
-
params_copy.delete(:commit)
|
24
|
-
|
25
|
-
# Extract search params from params
|
26
|
-
search_params = options[:params_scope].blank? ? params_copy : params_copy[options[:params_scope]]
|
27
|
-
search_params ||= {}
|
28
|
-
search_params = search_params.with_indifferent_access
|
29
|
-
|
30
|
-
# Never want to keep page
|
31
|
-
search_params.delete(:page)
|
32
|
-
|
33
|
-
# Use special order_by value
|
34
|
-
search_params[option] = option == :order_by ? searchgasm_order_by_value(value) : value
|
35
|
-
|
36
|
-
search_params
|
37
|
-
end
|
38
|
-
|
39
61
|
def searchgasm_add_class!(html_options, new_class)
|
40
62
|
new_class = new_class.to_s
|
41
63
|
classes = html_options[:class].split(" ")
|
@@ -63,20 +85,22 @@ module Searchgasm
|
|
63
85
|
html
|
64
86
|
end
|
65
87
|
|
66
|
-
|
67
|
-
|
68
|
-
new_hash = {}
|
88
|
+
def literal_param_strings(literal_params, prefix)
|
89
|
+
param_strings = []
|
69
90
|
|
70
|
-
|
91
|
+
literal_params.each do |k, v|
|
92
|
+
param_string = prefix.blank? ? k.to_s : "#{prefix}[#{k}]"
|
71
93
|
case v
|
72
94
|
when Hash
|
73
|
-
|
95
|
+
literal_param_strings(v, param_string).each do |literal_param_string|
|
96
|
+
param_strings << literal_param_string
|
97
|
+
end
|
74
98
|
else
|
75
|
-
|
99
|
+
param_strings << (CGI.escape(param_string) + "=#{v}")
|
76
100
|
end
|
77
101
|
end
|
78
102
|
|
79
|
-
|
103
|
+
param_strings
|
80
104
|
end
|
81
105
|
end
|
82
106
|
end
|
@@ -3,10 +3,8 @@ module Searchgasm #:nodoc:
|
|
3
3
|
# = Searchgasm
|
4
4
|
#
|
5
5
|
# Please refer the README.rdoc for usage, examples, and installation.
|
6
|
-
|
7
6
|
class Base
|
8
7
|
include Searchgasm::Shared::Utilities
|
9
|
-
include Searchgasm::Shared::Searching
|
10
8
|
include Searchgasm::Shared::VirtualClasses
|
11
9
|
|
12
10
|
# Options ActiveRecord allows when searching
|
@@ -24,11 +22,14 @@ module Searchgasm #:nodoc:
|
|
24
22
|
OPTIONS = SPECIAL_FIND_OPTIONS + AR_OPTIONS # the order is very important, these options get set in this order
|
25
23
|
|
26
24
|
attr_accessor *AR_OPTIONS
|
25
|
+
attr_reader :auto_joins
|
27
26
|
|
28
27
|
class << self
|
29
28
|
# Used in the ActiveRecord methods to determine if Searchgasm should get involved or not.
|
30
29
|
# This keeps Searchgasm out of the way unless it is needed.
|
31
30
|
def needed?(model_class, options)
|
31
|
+
return false if options.blank?
|
32
|
+
|
32
33
|
SPECIAL_FIND_OPTIONS.each do |option|
|
33
34
|
return true if options.symbolize_keys.keys.include?(option)
|
34
35
|
end
|
@@ -52,6 +53,18 @@ module Searchgasm #:nodoc:
|
|
52
53
|
@acting_as_filter == true
|
53
54
|
end
|
54
55
|
|
56
|
+
# Specific implementation of cloning
|
57
|
+
def clone
|
58
|
+
options = {}
|
59
|
+
(OPTIONS - [:conditions]).each { |option| options[option] = send(option) }
|
60
|
+
options[:conditions] = conditions.conditions
|
61
|
+
obj = self.class.new(options)
|
62
|
+
obj.protect = protected?
|
63
|
+
obj.scope = scope
|
64
|
+
obj
|
65
|
+
end
|
66
|
+
alias_method :dup, :clone
|
67
|
+
|
55
68
|
# Makes using searchgasm in the console less annoying and keeps the output meaningful and useful
|
56
69
|
def inspect
|
57
70
|
current_find_options = {}
|
@@ -66,13 +79,8 @@ module Searchgasm #:nodoc:
|
|
66
79
|
"#<#{klass}Search #{current_find_options.inspect}>"
|
67
80
|
end
|
68
81
|
|
69
|
-
def joins=(value)
|
70
|
-
@memoized_joins = nil
|
71
|
-
@joins = value
|
72
|
-
end
|
73
|
-
|
74
82
|
def joins
|
75
|
-
@
|
83
|
+
merge_joins(@joins, auto_joins)
|
76
84
|
end
|
77
85
|
|
78
86
|
def limit=(value)
|
@@ -116,9 +124,7 @@ module Searchgasm #:nodoc:
|
|
116
124
|
if searching
|
117
125
|
find_options[:group] ||= "#{quote_table_name(klass.table_name)}.#{quote_column_name(klass.primary_key)}"
|
118
126
|
else
|
119
|
-
|
120
|
-
# performance hit that they have when searching. Plus it's cleaner.
|
121
|
-
find_options[:include] = merge_joins(find_options[:include], find_options.delete(:joins))
|
127
|
+
find_options[:distinct] = true
|
122
128
|
end
|
123
129
|
end
|
124
130
|
|
@@ -10,6 +10,7 @@ module Searchgasm
|
|
10
10
|
alias_method_chain :initialize, :conditions
|
11
11
|
alias_method_chain :conditions=, :conditions
|
12
12
|
alias_method_chain :conditions, :conditions
|
13
|
+
alias_method_chain :auto_joins, :conditions
|
13
14
|
alias_method_chain :joins, :conditions
|
14
15
|
alias_method_chain :sanitize, :conditions
|
15
16
|
end
|
@@ -34,7 +35,7 @@ module Searchgasm
|
|
34
35
|
# now you can create the rest of your search and your "scope" will get merged into your final SQL.
|
35
36
|
# What this does is determine if the value a hash or a conditions object, if not it sets it up as a scope.
|
36
37
|
def conditions_with_conditions=(values)
|
37
|
-
@
|
38
|
+
@memoized_auto_joins = nil
|
38
39
|
case values
|
39
40
|
when Searchgasm::Conditions::Base
|
40
41
|
@conditions = values
|
@@ -44,7 +45,7 @@ module Searchgasm
|
|
44
45
|
end
|
45
46
|
|
46
47
|
def conditions_with_conditions
|
47
|
-
@
|
48
|
+
@memoized_auto_joins = nil # have to assume they are calling a condition on a relationship
|
48
49
|
conditions_without_conditions
|
49
50
|
end
|
50
51
|
|
@@ -52,8 +53,18 @@ module Searchgasm
|
|
52
53
|
#
|
53
54
|
# <b>Be careful!</b>
|
54
55
|
# ActiveRecord associations can be an SQL train wreck. Make sure you think about what you are searching and that you aren't joining a table with a million records.
|
56
|
+
def auto_joins_with_conditions
|
57
|
+
@memoized_auto_joins ||= merge_joins(auto_joins_without_conditions, conditions.auto_joins)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Changes joins to use left outer joins if conditions.any == true.
|
55
61
|
def joins_with_conditions
|
56
|
-
|
62
|
+
if conditions.any?
|
63
|
+
join_dependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(klass, joins_without_conditions, nil)
|
64
|
+
join_dependency.join_associations.collect { |assoc| assoc.association_join }.join
|
65
|
+
else
|
66
|
+
joins_without_conditions
|
67
|
+
end
|
57
68
|
end
|
58
69
|
|
59
70
|
def sanitize_with_conditions(searching = true) # :nodoc:
|