searchlogic 1.5.4 → 1.5.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +13 -0
- data/Manifest +7 -1
- data/README.rdoc +78 -11
- data/Rakefile +15 -11
- data/lib/searchlogic/condition/base.rb +5 -1
- data/lib/searchlogic/condition/keywords.rb +2 -2
- data/lib/searchlogic/conditions/any_or_all.rb +42 -0
- data/lib/searchlogic/conditions/base.rb +36 -312
- data/lib/searchlogic/conditions/groups.rb +37 -0
- data/lib/searchlogic/conditions/magic_methods.rb +268 -0
- data/lib/searchlogic/conditions/protection.rb +1 -1
- data/lib/searchlogic/config/helpers.rb +77 -28
- data/lib/searchlogic/helpers/control_types/links.rb +1 -0
- data/lib/searchlogic/helpers/form.rb +1 -1
- data/lib/searchlogic/shared/utilities.rb +9 -2
- data/lib/searchlogic/version.rb +1 -1
- data/lib/searchlogic.rb +6 -0
- data/searchlogic.gemspec +9 -7
- data/test/active_record_tests/base_test.rb +13 -13
- data/test/condition_tests/{keyswords_test.rb → keywords_test.rb} +4 -0
- data/test/conditions_tests/any_or_all_test.rb +23 -0
- data/test/conditions_tests/base_test.rb +45 -88
- data/test/conditions_tests/groups_test.rb +51 -0
- data/test/conditions_tests/magic_methods_test.rb +24 -0
- data/test/search_tests/base_test.rb +7 -3
- data/test/test_helper.rb +12 -28
- metadata +17 -5
@@ -8,33 +8,9 @@ module Searchlogic
|
|
8
8
|
include Shared::Utilities
|
9
9
|
include Shared::VirtualClasses
|
10
10
|
|
11
|
-
attr_accessor :
|
11
|
+
attr_accessor :object_name
|
12
12
|
|
13
13
|
class << self
|
14
|
-
attr_accessor :added_klass_conditions, :added_column_equals_conditions, :added_associations
|
15
|
-
|
16
|
-
def column_details # :nodoc:
|
17
|
-
return @column_details if @column_details
|
18
|
-
|
19
|
-
@column_details = []
|
20
|
-
|
21
|
-
klass.columns.each do |column|
|
22
|
-
column_detail = {:column => column}
|
23
|
-
column_detail[:aliases] = case column.type
|
24
|
-
when :datetime, :time, :timestamp
|
25
|
-
[column.name.gsub(/_at$/, "")]
|
26
|
-
when :date
|
27
|
-
[column.name.gsub(/_at$/, "")]
|
28
|
-
else
|
29
|
-
[]
|
30
|
-
end
|
31
|
-
|
32
|
-
@column_details << column_detail
|
33
|
-
end
|
34
|
-
|
35
|
-
@column_details
|
36
|
-
end
|
37
|
-
|
38
14
|
# Registers a condition as an available condition for a column or a class. MySQL supports a "sounds like" function. I want to use it, so let's add it.
|
39
15
|
#
|
40
16
|
# === Example
|
@@ -44,7 +20,7 @@ module Searchlogic
|
|
44
20
|
# class SoundsLike < Searchlogic::Condition::Base
|
45
21
|
# # The name of the conditions. By default its the name of the class, if you want alternate or alias conditions just add them on.
|
46
22
|
# # If you don't want to add aliases you don't even need to define this method
|
47
|
-
# def self.condition_names_for_column
|
23
|
+
# def self.condition_names_for_column
|
48
24
|
# super + ["similar_to", "sounds"]
|
49
25
|
# end
|
50
26
|
#
|
@@ -119,16 +95,6 @@ module Searchlogic
|
|
119
95
|
@@modifiers ||= []
|
120
96
|
end
|
121
97
|
|
122
|
-
# A list of all associations created, used for caching and performance
|
123
|
-
def association_names
|
124
|
-
@association_names ||= []
|
125
|
-
end
|
126
|
-
|
127
|
-
# A list of all conditions available, users for caching and performance
|
128
|
-
def condition_names
|
129
|
-
@condition_names ||= []
|
130
|
-
end
|
131
|
-
|
132
98
|
def needed?(model_class, conditions) # :nodoc:
|
133
99
|
return false if conditions.blank?
|
134
100
|
|
@@ -147,82 +113,49 @@ module Searchlogic
|
|
147
113
|
end
|
148
114
|
end
|
149
115
|
|
116
|
+
# Initializes a conditions object, accepts a hash of conditions as the single parameter
|
150
117
|
def initialize(init_conditions = {})
|
151
|
-
add_associations!
|
152
|
-
add_column_equals_conditions!
|
153
118
|
self.conditions = init_conditions
|
154
119
|
end
|
155
120
|
|
156
|
-
# Determines if we should join the conditions with "AND" or "OR".
|
157
|
-
#
|
158
|
-
# === Examples
|
159
|
-
#
|
160
|
-
# search.conditions.any = true # will join all conditions with "or", you can also set this to "true", "1", or "yes"
|
161
|
-
# search.conditions.any = false # will join all conditions with "and"
|
162
|
-
def any=(value)
|
163
|
-
associations.each { |name, association| association.any = value }
|
164
|
-
@any = value
|
165
|
-
end
|
166
|
-
|
167
|
-
def any # :nodoc:
|
168
|
-
any?
|
169
|
-
end
|
170
|
-
|
171
|
-
# Convenience method for determining if we should join the conditions with "AND" or "OR".
|
172
|
-
def any?
|
173
|
-
["true", "1", "yes"].include? @any.to_s
|
174
|
-
end
|
175
|
-
|
176
|
-
# Sets the conditions to be searched by "or"
|
177
|
-
def any!
|
178
|
-
any = true
|
179
|
-
end
|
180
|
-
|
181
|
-
def all # :nodoc:
|
182
|
-
not any?
|
183
|
-
end
|
184
|
-
|
185
|
-
# Sets the conditions to be searched by "and"
|
186
|
-
def all!
|
187
|
-
any = false
|
188
|
-
end
|
189
|
-
|
190
121
|
# A list of joins to use when searching, includes relationships
|
191
122
|
def auto_joins
|
192
123
|
j = []
|
193
|
-
|
124
|
+
association_objects.each do |association|
|
194
125
|
next if association.conditions.blank?
|
195
126
|
association_joins = association.auto_joins
|
196
|
-
j << (association_joins.blank? ?
|
127
|
+
j << (association_joins.blank? ? association.object_name : {association.object_name => association_joins})
|
197
128
|
end
|
198
129
|
j.blank? ? nil : (j.size == 1 ? j.first : j)
|
199
130
|
end
|
200
131
|
|
132
|
+
# Provides a much more informative and easier to understand inspection of the object
|
201
133
|
def inspect
|
202
134
|
"#<#{klass}Conditions#{conditions.blank? ? "" : " #{conditions.inspect}"}>"
|
203
135
|
end
|
204
136
|
|
205
137
|
# Sanitizes the conditions down into conditions that ActiveRecord::Base.find can understand.
|
206
138
|
def sanitize
|
207
|
-
return @conditions if @conditions
|
208
|
-
|
139
|
+
return @conditions if @conditions # return the conditions if the user set them with a string, aka sql conditions
|
140
|
+
joined_conditions = nil
|
141
|
+
objects.each { |object| joined_conditions = merge_conditions(joined_conditions, object.class == self.class ? scope_condition(object.sanitize) : object.sanitize, :any => object.any?, :scope => false) }
|
142
|
+
joined_conditions
|
209
143
|
end
|
210
144
|
|
211
145
|
# Allows you to set the conditions via a hash.
|
212
146
|
def conditions=(value)
|
213
147
|
case value
|
214
148
|
when Hash
|
215
|
-
assert_valid_conditions(value)
|
216
149
|
remove_conditions_from_protected_assignement(value).each do |condition, condition_value|
|
150
|
+
next if [:conditions].include?(condition.to_sym) # protect sensitive methods
|
217
151
|
|
218
152
|
# delete all blanks from mass assignments, forms submit blanks, blanks are meaningless
|
219
153
|
# equals condition thinks everything is meaningful, and arrays can be pased
|
220
154
|
new_condition_value = nil
|
221
155
|
case condition_value
|
222
156
|
when Array
|
223
|
-
new_condition_value =
|
224
|
-
|
225
|
-
next if new_condition_value.size == 0
|
157
|
+
new_condition_value = condition_value.reject { |v| v == "" }
|
158
|
+
next if new_condition_value.empty?
|
226
159
|
new_condition_value = new_condition_value.first if new_condition_value.size == 1
|
227
160
|
else
|
228
161
|
next if condition_value == ""
|
@@ -232,7 +165,7 @@ module Searchlogic
|
|
232
165
|
send("#{condition}=", new_condition_value)
|
233
166
|
end
|
234
167
|
else
|
235
|
-
|
168
|
+
reset!
|
236
169
|
@conditions = value
|
237
170
|
end
|
238
171
|
end
|
@@ -240,249 +173,40 @@ module Searchlogic
|
|
240
173
|
# All of the active conditions (conditions that have been set)
|
241
174
|
def conditions
|
242
175
|
return @conditions if @conditions
|
243
|
-
return if objects.blank?
|
244
176
|
|
245
177
|
conditions_hash = {}
|
246
|
-
objects.each do |name, object|
|
247
|
-
if object.class < Searchlogic::Conditions::Base
|
248
|
-
relationship_conditions = object.conditions
|
249
|
-
next if relationship_conditions.blank?
|
250
|
-
conditions_hash[name] = relationship_conditions
|
251
|
-
else
|
252
|
-
next if object.value_is_meaningless?
|
253
|
-
conditions_hash[name] = object.value
|
254
|
-
end
|
255
|
-
end
|
256
|
-
conditions_hash
|
257
|
-
end
|
258
|
-
|
259
|
-
private
|
260
|
-
def add_associations!
|
261
|
-
return true if self.class.added_associations
|
262
|
-
|
263
|
-
klass.reflect_on_all_associations.each do |association|
|
264
|
-
self.class.association_names << association.name.to_s
|
265
|
-
|
266
|
-
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
|
267
|
-
def #{association.name}
|
268
|
-
if objects[:#{association.name}].nil?
|
269
|
-
objects[:#{association.name}] = Searchlogic::Conditions::Base.create_virtual_class(#{association.class_name}).new
|
270
|
-
objects[:#{association.name}].relationship_name = "#{association.name}"
|
271
|
-
objects[:#{association.name}].protect = protect
|
272
|
-
end
|
273
|
-
objects[:#{association.name}]
|
274
|
-
end
|
275
|
-
|
276
|
-
def #{association.name}=(conditions); @conditions = nil; #{association.name}.conditions = conditions; end
|
277
|
-
def reset_#{association.name}!; objects.delete(:#{association.name}); end
|
278
|
-
end_eval
|
279
|
-
end
|
280
|
-
|
281
|
-
self.class.added_associations = true
|
282
|
-
end
|
283
178
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
179
|
+
association_objects.each do |association_object|
|
180
|
+
relationship_conditions = association_object.conditions
|
181
|
+
next if relationship_conditions.blank?
|
182
|
+
conditions_hash[association_object.object_name] = relationship_conditions
|
288
183
|
end
|
289
184
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
condition_parts = []
|
294
|
-
column = nil
|
295
|
-
while column.nil? && name_parts.size > 0
|
296
|
-
possible_column_name = name_parts.join("_")
|
297
|
-
|
298
|
-
self.class.column_details.each do |column_detail|
|
299
|
-
if column_detail[:column].name == possible_column_name || column_detail[:aliases].include?(possible_column_name)
|
300
|
-
column = column_detail
|
301
|
-
break
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
condition_parts << name_parts.pop if !column
|
306
|
-
end
|
307
|
-
|
308
|
-
return if column.nil?
|
309
|
-
|
310
|
-
condition_name = condition_parts.reverse.join("_")
|
311
|
-
condition = nil
|
312
|
-
|
313
|
-
# Find the real condition
|
314
|
-
self.class.conditions.each do |condition_klass|
|
315
|
-
if condition_klass.condition_names_for_column.include?(condition_name)
|
316
|
-
condition = condition_klass
|
317
|
-
break
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
[column, condition]
|
185
|
+
condition_objects.each do |condition_object|
|
186
|
+
next if condition_object.value_is_meaningless?
|
187
|
+
conditions_hash[condition_object.object_name] = condition_object.value
|
322
188
|
end
|
323
189
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
name_parts.each do |modifier_name|
|
337
|
-
size_before = modifier_klasses.size
|
338
|
-
self.class.modifiers.each do |modifier_klass|
|
339
|
-
if modifier_klass.modifier_names.include?(modifier_name)
|
340
|
-
modifier_klasses << modifier_klass
|
341
|
-
break
|
342
|
-
end
|
343
|
-
end
|
344
|
-
return if modifier_klasses.size == size_before # there was an invalid modifer, return nil for everything and let it act as a nomethoderror
|
345
|
-
end
|
346
|
-
|
347
|
-
return [modifier_klasses, column_detail, condition_klass]
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
nil
|
352
|
-
end
|
353
|
-
|
354
|
-
def build_method_name(modifier_klasses, column_name, condition_name)
|
355
|
-
modifier_name_parts = []
|
356
|
-
modifier_klasses.each { |modifier_klass| modifier_name_parts << modifier_klass.modifier_names.first }
|
357
|
-
method_name_parts = []
|
358
|
-
method_name_parts << modifier_name_parts.join("_of_") + "_of" unless modifier_name_parts.blank?
|
359
|
-
method_name_parts << column_name
|
360
|
-
method_name_parts << condition_name unless condition_name.blank?
|
361
|
-
method_name_parts.join("_").underscore
|
362
|
-
end
|
363
|
-
|
364
|
-
def method_missing(name, *args, &block)
|
365
|
-
if setup_condition(name)
|
366
|
-
send(name, *args, &block)
|
367
|
-
else
|
368
|
-
super
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
def setup_condition(name)
|
373
|
-
modifier_klasses, column_detail, condition_klass = breakdown_method_name(name.to_s)
|
374
|
-
if !column_detail.nil? && !condition_klass.nil?
|
375
|
-
method_name = build_method_name(modifier_klasses, column_detail[:column].name, condition_klass.condition_names_for_column.first)
|
376
|
-
|
377
|
-
if !added_condition?(method_name)
|
378
|
-
column_type = column_sql = nil
|
379
|
-
if !modifier_klasses.blank?
|
380
|
-
# Find the column type
|
381
|
-
column_type = modifier_klasses.first.return_type
|
382
|
-
|
383
|
-
# Build the column sql
|
384
|
-
column_sql = "{table}.{column}"
|
385
|
-
modifier_klasses.each do |modifier_klass|
|
386
|
-
next unless klass.connection.respond_to?(modifier_klass.adapter_method_name)
|
387
|
-
column_sql = klass.connection.send(modifier_klass.adapter_method_name, column_sql)
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
add_condition!(condition_klass, method_name, :column => column_detail[:column], :column_type => column_type, :column_sql_format => column_sql)
|
392
|
-
|
393
|
-
([column_detail[:column].name] + column_detail[:aliases]).each do |column_name|
|
394
|
-
condition_klass.condition_names_for_column.each do |condition_name|
|
395
|
-
alias_method_name = build_method_name(modifier_klasses, column_name, condition_name)
|
396
|
-
add_condition_alias!(alias_method_name, method_name) unless added_condition?(alias_method_name)
|
397
|
-
end
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
alias_method_name = name.to_s.gsub("=", "")
|
402
|
-
add_condition_alias!(alias_method_name, method_name) unless added_condition?(alias_method_name)
|
403
|
-
|
404
|
-
return true
|
405
|
-
end
|
406
|
-
|
407
|
-
false
|
408
|
-
end
|
409
|
-
|
410
|
-
def add_condition!(condition, name, options = {})
|
411
|
-
self.class.condition_names << name
|
412
|
-
options[:column] = options[:column].name
|
413
|
-
|
414
|
-
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
|
415
|
-
def #{name}_object
|
416
|
-
if objects[:#{name}].nil?
|
417
|
-
options = {}
|
418
|
-
objects[:#{name}] = #{condition.name}.new(klass, #{options.inspect})
|
419
|
-
end
|
420
|
-
objects[:#{name}]
|
421
|
-
end
|
422
|
-
|
423
|
-
def #{name}; #{name}_object.value; end
|
424
|
-
|
425
|
-
def #{name}=(value)
|
426
|
-
@conditions = nil
|
427
|
-
|
428
|
-
#{name}_object.value = value
|
429
|
-
reset_#{name}! if #{name}_object.value_is_meaningless?
|
430
|
-
value
|
431
|
-
end
|
432
|
-
|
433
|
-
def reset_#{name}!; objects.delete(:#{name}); end
|
434
|
-
end_eval
|
435
|
-
end
|
436
|
-
|
437
|
-
def added_condition?(name)
|
438
|
-
respond_to?("#{name}_object") && respond_to?(name) && respond_to?("#{name}=") && respond_to?("reset_#{name}!")
|
439
|
-
end
|
440
|
-
|
441
|
-
def add_condition_alias!(alias_name, name)
|
442
|
-
self.class.condition_names << alias_name
|
443
|
-
|
444
|
-
self.class.class_eval do
|
445
|
-
alias_method "#{alias_name}_object", "#{name}_object"
|
446
|
-
alias_method alias_name, name
|
447
|
-
alias_method "#{alias_name}=", "#{name}="
|
448
|
-
alias_method "reset_#{alias_name}!", "reset_#{name}!"
|
449
|
-
end
|
450
|
-
end
|
451
|
-
|
452
|
-
def assert_valid_conditions(conditions)
|
453
|
-
conditions.each do |condition, value|
|
454
|
-
next if (self.class.condition_names + self.class.association_names + ["any"]).include?(condition.to_s)
|
455
|
-
|
456
|
-
go_to_next = false
|
457
|
-
self.class.column_details.each do |column_detail|
|
458
|
-
if column_detail[:column].name == condition.to_s || column_detail[:aliases].include?(condition.to_s)
|
459
|
-
go_to_next = true
|
460
|
-
break
|
461
|
-
end
|
462
|
-
end
|
463
|
-
next if go_to_next
|
464
|
-
|
465
|
-
next unless respond_to?(condition)
|
466
|
-
|
467
|
-
raise(ArgumentError, "The #{condition} condition is not a valid condition")
|
468
|
-
end
|
190
|
+
conditions_hash
|
191
|
+
end
|
192
|
+
|
193
|
+
# Resets all of the conditions, including conditions set on associations
|
194
|
+
def reset!
|
195
|
+
objects.each { |object| eval("@#{object.object_name} = nil") }
|
196
|
+
objects.clear
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
def association_objects
|
201
|
+
objects.select { |object| object.class < Base && object.class != self.class }
|
469
202
|
end
|
470
203
|
|
471
|
-
def
|
472
|
-
|
473
|
-
objects.each do |name, object|
|
474
|
-
associations[name] = object if object.class < ::Searchlogic::Conditions::Base
|
475
|
-
end
|
476
|
-
associations
|
204
|
+
def condition_objects
|
205
|
+
objects.select { |object| object.class < Condition::Base }
|
477
206
|
end
|
478
207
|
|
479
208
|
def objects
|
480
|
-
@objects ||=
|
481
|
-
end
|
482
|
-
|
483
|
-
def reset_objects!
|
484
|
-
objects.each { |name, object| eval("@#{name} = nil") }
|
485
|
-
objects.clear
|
209
|
+
@objects ||= []
|
486
210
|
end
|
487
211
|
|
488
212
|
def remove_conditions_from_protected_assignement(conditions)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module Conditions
|
3
|
+
# = Groups
|
4
|
+
#
|
5
|
+
# Allows you to group conditions, similar to how you would group conditions with parenthesis in an SQL statement. See the "Group conditions" section in the READM for examples.
|
6
|
+
module Groups
|
7
|
+
def self.included(klass)
|
8
|
+
klass.class_eval do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Creates a new group object to set condition off of. See examples at top of class on how to use this.
|
13
|
+
def group(&block)
|
14
|
+
obj = self.class.new
|
15
|
+
yield obj if block_given?
|
16
|
+
objects << obj
|
17
|
+
obj
|
18
|
+
end
|
19
|
+
|
20
|
+
# Lets you conditions to be groups or an array of conditions to be put in their own group. Each item in the array will create a new group. This is nice for using
|
21
|
+
# groups in forms.
|
22
|
+
def group=(value)
|
23
|
+
case value
|
24
|
+
when Array
|
25
|
+
value.each { |v| group.conditions = v }
|
26
|
+
else
|
27
|
+
group.conditions = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def group_objects
|
33
|
+
objects.select { |object| object.class == self.class }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|