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.
@@ -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