searchlogic 1.5.4 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.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
|