searchlogic 1.5.4 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,33 +8,9 @@ module Searchlogic
8
8
  include Shared::Utilities
9
9
  include Shared::VirtualClasses
10
10
 
11
- attr_accessor :any, :relationship_name
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(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
- associations.each do |name, association|
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? ? name : {name => association_joins})
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
- merge_conditions(*(objects.collect { |name, object| object.sanitize } << {:any => any}))
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
- condition_value.each { |v| new_condition_value << v unless v == "" }
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
- reset_objects!
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
- def add_column_equals_conditions!
285
- return true if self.class.added_column_equals_conditions
286
- klass.column_names.each { |name| setup_condition(name) }
287
- self.class.added_column_equals_conditions = true
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
- def extract_column_and_condition_from_method_name(name)
291
- name_parts = name.gsub("=", "").split("_")
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
- def breakdown_method_name(name)
325
- column_detail, condition_klass = extract_column_and_condition_from_method_name(name)
326
- if !column_detail.nil? && !condition_klass.nil?
327
- # There were no modifiers
328
- return [[], column_detail, condition_klass]
329
- else
330
- # There might be modifiers
331
- name_parts = name.split("_of_")
332
- column_detail, condition_klass = extract_column_and_condition_from_method_name(name_parts.pop)
333
- if !column_detail.nil? && !condition_klass.nil?
334
- # There were modifiers, lets get their real names
335
- modifier_klasses = []
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 associations
472
- associations = {}
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