Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Updated searchgasm

  • Loading branch information...
commit 80ec14497fde1eb54a6293f3fbbe24a84f291d26 1 parent 46b6722
Ben Johnson yourewelcome authored

Showing 74 changed files with 1,511 additions and 570 deletions. Show diff stats Hide diff stats

  1. +5 5 config/environment.rb
  2. +9 15 config/initializers/searchgasm.rb
  3. +8 0 vendor/plugins/searchgasm/CHANGELOG.rdoc
  4. +42 3 vendor/plugins/searchgasm/Manifest
  5. +101 82 vendor/plugins/searchgasm/README.rdoc
  6. +1 3 vendor/plugins/searchgasm/TODO.rdoc
  7. +15 19 vendor/plugins/searchgasm/lib/searchgasm.rb
  8. +143 6 vendor/plugins/searchgasm/lib/searchgasm/active_record/connection_adapters/mysql_adapter.rb
  9. +148 0 vendor/plugins/searchgasm/lib/searchgasm/active_record/connection_adapters/postgresql_adapter.rb
  10. +54 0 vendor/plugins/searchgasm/lib/searchgasm/active_record/connection_adapters/sqlite_adapter.rb
  11. +59 86 vendor/plugins/searchgasm/lib/searchgasm/condition/base.rb
  12. +3 8 vendor/plugins/searchgasm/lib/searchgasm/condition/begins_with.rb
  13. +5 5 vendor/plugins/searchgasm/lib/searchgasm/condition/blank.rb
  14. +0 20 vendor/plugins/searchgasm/lib/searchgasm/condition/contains.rb
  15. +0 32 vendor/plugins/searchgasm/lib/searchgasm/condition/during_evening.rb
  16. +3 8 vendor/plugins/searchgasm/lib/searchgasm/condition/ends_with.rb
  17. +4 3 vendor/plugins/searchgasm/lib/searchgasm/condition/equals.rb
  18. +3 14 vendor/plugins/searchgasm/lib/searchgasm/condition/greater_than.rb
  19. +3 14 vendor/plugins/searchgasm/lib/searchgasm/condition/greater_than_or_equal_to.rb
  20. +3 8 vendor/plugins/searchgasm/lib/searchgasm/condition/keywords.rb
  21. +3 14 vendor/plugins/searchgasm/lib/searchgasm/condition/less_than.rb
  22. +3 14 vendor/plugins/searchgasm/lib/searchgasm/condition/less_than_or_equal_to.rb
  23. +15 0 vendor/plugins/searchgasm/lib/searchgasm/condition/like.rb
  24. +5 5 vendor/plugins/searchgasm/lib/searchgasm/condition/nil.rb
  25. +17 0 vendor/plugins/searchgasm/lib/searchgasm/condition/not_begin_with.rb
  26. +17 0 vendor/plugins/searchgasm/lib/searchgasm/condition/not_end_with.rb
  27. +5 4 vendor/plugins/searchgasm/lib/searchgasm/condition/{does_not_equal.rb → not_equal.rb}
  28. +17 0 vendor/plugins/searchgasm/lib/searchgasm/condition/not_have_keywords.rb
  29. +17 0 vendor/plugins/searchgasm/lib/searchgasm/condition/not_like.rb
  30. +4 5 vendor/plugins/searchgasm/lib/searchgasm/condition/tree.rb
  31. +218 72 vendor/plugins/searchgasm/lib/searchgasm/conditions/base.rb
  32. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/absolute.rb
  33. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/acos.rb
  34. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/asin.rb
  35. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/atan.rb
  36. +27 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/base.rb
  37. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/ceil.rb
  38. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/char_length.rb
  39. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/cos.rb
  40. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/cot.rb
  41. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/day_of_month.rb
  42. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/day_of_week.rb
  43. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/day_of_year.rb
  44. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/degrees.rb
  45. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/exp.rb
  46. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/floor.rb
  47. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/hex.rb
  48. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/hour.rb
  49. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/log.rb
  50. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/log10.rb
  51. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/log2.rb
  52. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/md5.rb
  53. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/microseconds.rb
  54. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/milliseconds.rb
  55. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/minute.rb
  56. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/month.rb
  57. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/octal.rb
  58. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/radians.rb
  59. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/round.rb
  60. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/second.rb
  61. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/sign.rb
  62. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/sin.rb
  63. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/square_root.rb
  64. +15 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/tan.rb
  65. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/week.rb
  66. +11 0 vendor/plugins/searchgasm/lib/searchgasm/modifiers/year.rb
  67. +0 10 vendor/plugins/searchgasm/lib/searchgasm/shared/utilities.rb
  68. +2 2 vendor/plugins/searchgasm/lib/searchgasm/version.rb
  69. +9 0 vendor/plugins/searchgasm/test/libs/ordered_hash.rb
  70. +21 47 vendor/plugins/searchgasm/test/test_condition_base.rb
  71. +44 44 vendor/plugins/searchgasm/test/test_condition_types.rb
  72. +34 21 vendor/plugins/searchgasm/test/test_conditions_base.rb
  73. +1 0  vendor/plugins/searchgasm/test/test_helper.rb
  74. +1 1  vendor/plugins/searchgasm/test/test_search_conditions.rb
10 config/environment.rb
@@ -45,11 +45,11 @@
45 45 # Run "rake -D time" for a list of tasks for finding time zone names. Uncomment to use default local time.
46 46 config.time_zone = 'UTC'
47 47
48   - if RAILS_ENV == "development" # putting this in environments/development.rb doesn't work, not sure why
49   - config.plugin_paths = ["#{RAILS_ROOT}/../../Libs"]
50   - config.plugins = [:searchgasm]
51   - require "ruby-debug"
52   - end
  48 + #if RAILS_ENV == "development" # putting this in environments/development.rb doesn't work, not sure why
  49 + # config.plugin_paths = ["#{RAILS_ROOT}/../../Libs"]
  50 + # config.plugins = [:searchgasm]
  51 + # require "ruby-debug"
  52 + #end
53 53
54 54 # Your secret key for verifying cookie session data integrity.
55 55 # If you change this key, all old sessions will become invalid!
24 config/initializers/searchgasm.rb
@@ -4,28 +4,22 @@
4 4 end
5 5
6 6 # Actual function for MySQL databases only
7   -class SoundsLikeCondition < Searchgasm::Condition::Base
  7 +class SoundsLike < Searchgasm::Condition::Base
8 8 class << self
9   - # I pass you the column, you tell me what you want the method to be called.
10   - # If you don't want to add this condition for that column, return nil
11   - # It defaults to "#{column.name}_sounds_like". So if thats what you want you don't even need to do this.
12   - def name_for_column(column)
13   - return unless column.type == :string
14   - super
15   - end
16   -
17   - # Only do this if you want aliases for your condition
18   - def aliases_for_column(column)
19   - ["#{column.name}_sounds", "#{column.name}_similar_to"]
  9 + # The name of the conditions. By default its the name of the class, if you want alternate or alias conditions just add them on.
  10 + # If you don't want to add aliases you don't even need to define this method
  11 + def name_for_columsn(column)
  12 + super + ["sounds", "similar_to"]
20 13 end
21 14 end
22 15
23 16 # You can return an array or a string. NOT a hash, because all of these conditions
24 17 # need to eventually get merged together. The array or string can be anything you would put in
25   - # the :conditions option for ActiveRecord::Base.find()
  18 + # the :conditions option for ActiveRecord::Base.find(). Also notice the column_sql variable. This is essentail
  19 + # for applying modifiers and should be used in your conditions wherever you want the column.
26 20 def to_conditions(value)
27   - ["#{quoted_table_name}.#{quoted_column_name} SOUNDS LIKE ?", value]
  21 + ["#{column_sql} SOUNDS LIKE ?", value]
28 22 end
29 23 end
30 24
31   -Searchgasm::Conditions::Base.register_condition(SoundsLikeCondition)
  25 +Searchgasm::Conditions::Base.register_condition(SoundsLike)
8 vendor/plugins/searchgasm/CHANGELOG.rdoc
Source Rendered
... ... @@ -1,3 +1,11 @@
  1 +== 1.3.0 released 2008-09-29
  2 +
  3 +* Added modifiers into the mix: hour_of_created_at_less_than = 10, etc.
  4 +* Changed how the Searchgasm::Conditions::Base class works. Instead of predefining all methods for all conditions upon instantiation, they are defined as needed via method_missing. Similar to
  5 + ActiveRecord's dynamic finders: User.find_by_name_and_email(name, email). Once the are defined they never hit method_missing again, acts like a cache.
  6 +* Altered how values are handled for each condition, meaningless values are ignored completely.
  7 +* Added in more "not" conditions: not_like, not_begin_with, not_have_keywords, etc
  8 +
1 9 == 1.2.2 released 2008-09-29
2 10
3 11 * Fixed bug when reverse engineering order to order_by, assumed ASC and DESC would always be present when they are not.
45 vendor/plugins/searchgasm/Manifest
@@ -10,10 +10,7 @@ lib/searchgasm/condition/base.rb
10 10 lib/searchgasm/condition/begins_with.rb
11 11 lib/searchgasm/condition/blank.rb
12 12 lib/searchgasm/condition/child_of.rb
13   -lib/searchgasm/condition/contains.rb
14 13 lib/searchgasm/condition/descendant_of.rb
15   -lib/searchgasm/condition/does_not_equal.rb
16   -lib/searchgasm/condition/during_evening.rb
17 14 lib/searchgasm/condition/ends_with.rb
18 15 lib/searchgasm/condition/equals.rb
19 16 lib/searchgasm/condition/greater_than.rb
@@ -22,7 +19,13 @@ lib/searchgasm/condition/inclusive_descendant_of.rb
22 19 lib/searchgasm/condition/keywords.rb
23 20 lib/searchgasm/condition/less_than.rb
24 21 lib/searchgasm/condition/less_than_or_equal_to.rb
  22 +lib/searchgasm/condition/like.rb
25 23 lib/searchgasm/condition/nil.rb
  24 +lib/searchgasm/condition/not_begin_with.rb
  25 +lib/searchgasm/condition/not_end_with.rb
  26 +lib/searchgasm/condition/not_equal.rb
  27 +lib/searchgasm/condition/not_have_keywords.rb
  28 +lib/searchgasm/condition/not_like.rb
26 29 lib/searchgasm/condition/sibling_of.rb
27 30 lib/searchgasm/condition/tree.rb
28 31 lib/searchgasm/conditions/base.rb
@@ -37,6 +40,41 @@ lib/searchgasm/helpers/control_types/remote_select.rb
37 40 lib/searchgasm/helpers/control_types/select.rb
38 41 lib/searchgasm/helpers/form.rb
39 42 lib/searchgasm/helpers/utilities.rb
  43 +lib/searchgasm/modifiers/absolute.rb
  44 +lib/searchgasm/modifiers/acos.rb
  45 +lib/searchgasm/modifiers/asin.rb
  46 +lib/searchgasm/modifiers/atan.rb
  47 +lib/searchgasm/modifiers/base.rb
  48 +lib/searchgasm/modifiers/ceil.rb
  49 +lib/searchgasm/modifiers/char_length.rb
  50 +lib/searchgasm/modifiers/cos.rb
  51 +lib/searchgasm/modifiers/cot.rb
  52 +lib/searchgasm/modifiers/day_of_month.rb
  53 +lib/searchgasm/modifiers/day_of_week.rb
  54 +lib/searchgasm/modifiers/day_of_year.rb
  55 +lib/searchgasm/modifiers/degrees.rb
  56 +lib/searchgasm/modifiers/exp.rb
  57 +lib/searchgasm/modifiers/floor.rb
  58 +lib/searchgasm/modifiers/hex.rb
  59 +lib/searchgasm/modifiers/hour.rb
  60 +lib/searchgasm/modifiers/log.rb
  61 +lib/searchgasm/modifiers/log10.rb
  62 +lib/searchgasm/modifiers/log2.rb
  63 +lib/searchgasm/modifiers/md5.rb
  64 +lib/searchgasm/modifiers/microseconds.rb
  65 +lib/searchgasm/modifiers/milliseconds.rb
  66 +lib/searchgasm/modifiers/minute.rb
  67 +lib/searchgasm/modifiers/month.rb
  68 +lib/searchgasm/modifiers/octal.rb
  69 +lib/searchgasm/modifiers/radians.rb
  70 +lib/searchgasm/modifiers/round.rb
  71 +lib/searchgasm/modifiers/second.rb
  72 +lib/searchgasm/modifiers/sign.rb
  73 +lib/searchgasm/modifiers/sin.rb
  74 +lib/searchgasm/modifiers/square_root.rb
  75 +lib/searchgasm/modifiers/tan.rb
  76 +lib/searchgasm/modifiers/week.rb
  77 +lib/searchgasm/modifiers/year.rb
40 78 lib/searchgasm/search/base.rb
41 79 lib/searchgasm/search/conditions.rb
42 80 lib/searchgasm/search/ordering.rb
@@ -56,6 +94,7 @@ test/fixtures/orders.yml
56 94 test/fixtures/user_groups.yml
57 95 test/fixtures/users.yml
58 96 test/libs/acts_as_tree.rb
  97 +test/libs/ordered_hash.rb
59 98 test/libs/rexml_fix.rb
60 99 test/test_active_record_associations.rb
61 100 test/test_active_record_base.rb
183 vendor/plugins/searchgasm/README.rdoc
Source Rendered
@@ -33,7 +33,9 @@ Now try out some of the examples below:
33 33 User.all(
34 34 :conditions => {
35 35 :first_name_contains => "Ben", # first_name like '%Ben%'
36   - :email_ends_with => "binarylogic.com" # email like '%binarylogic.com'
  36 + :email_ends_with => "binarylogic.com", # email like '%binarylogic.com'
  37 + :created_after => Time.now, # created_at > Time.now
  38 + :created_at_hour_gt => 5 # HOUR(created_at) > 5 (depends on DB type)
37 39 },
38 40 :per_page => 20, # limit 20
39 41 :page => 3, # offset 40, which starts us on page 3
@@ -46,6 +48,8 @@ same as above, but object based
46 48 search = User.new_search
47 49 search.conditions.first_name_contains = "Ben"
48 50 search.conditions.email_ends_with = "binarylogic.com"
  51 + search.conditions.created_after = Time.now
  52 + search.conditiona.created_at_hour_gt = 5
49 53 search.per_page = 20
50 54 search.page = 3
51 55 search.order_as = "ASC"
@@ -196,27 +200,6 @@ As you saw above, the nice thing about Searchgasm is it's integration with forms
196 200
197 201 search = @current_user.orders.build_search(:conditions => {:total_lte => 500})
198 202
199   -== Searching trees
200   -
201   -For tree data structures you get a few nifty methods. Let's assume Users is a tree data structure.
202   -
203   - # Child of
204   - User.all(:conditions => {:child_of => User.roots.first})
205   - User.all(:conditions => {:child_of => User.roots.first.id})
206   -
207   - # Sibling of
208   - User.all(:conditions => {:sibling_of => User.roots.first})
209   - User.all(:conditions => {:sibling_of => User.roots.first.id})
210   -
211   - # Descendant of (includes all recursive children: children, grand children, great grand children, etc)
212   - User.all(:conditions => {:descendant_of => User.roots.first})
213   - User.all(:conditions => {:descendant_of => User.roots.first.id})
214   -
215   - # Inclusive descendant_of. Same as above but includes the root
216   - User.all(:conditions => {:inclusive_descendant_of => User.roots.first})
217   - User.all(:conditions => {:inclusive_descendant_of => User.roots.first.id})
218   -
219   -
220 203 == Scope support
221 204
222 205 Not only can you use searchgasm when searching, but you can use it when using scopes.
@@ -256,82 +239,114 @@ Lesson learned: use new\_search when passing in params as *ANY* of the options.
256 239
257 240 == Available Conditions
258 241
259   -Depending on the type, each column comes preloaded with a bunch of nifty conditions:
  242 +The conditions are pretty self explanitory, but if you need more information checkout the docs or the source. The code is very simple and self explanatory.
260 243
261   - all columns
262   - => :equals, :does_not_equal
  244 +=== Column conditions
263 245
264   - :string, :text
265   - => :begins_with, :contains, :keywords, :ends_with
  246 +Each column can be used with any of the following conditions
266 247
267   - :integer, :float, :decimal,:datetime, :timestamp, :time, :date
268   - => :greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to
  248 + Name Aliases Description
  249 + :begins_with :starts_with, :sw, :bw, :start col LIKE 'value%'
  250 + :ends_with :ew, :ends, :end col LIKE '%value'
  251 + :equals :is, "" Lets ActiveRecord handle this
  252 + :greater_than :gt, :after col > value
  253 + :greater_than_or_equal_to :at_least, :least, :gte col >= value
  254 + :less_than :lt, :before col < value
  255 + :less_than_or_equal_to :at_most, :most, :lte col <= value
  256 + :keywords :kwords, :kw Splits into each word and omits meaningless words, a true keyword search
  257 + :like :contains, :has col LIKE '%value%'
  258 +
  259 + :not_begin_with :not_bw, :not_sw, :not_start_with, :not_start, :beginning_is_not, :beginning_not
  260 + :not_end_with :not_ew, :not_end, :end_is_not, :end_not
  261 + :not_equal :does_not_equal, :is_not, :not
  262 + :not_have_keywords :not_have_keywords, :not_keywords, :not_have_kw, :not_kw, :not_have_kwwords, :not_kwwords
  263 + :not_like :not_contain, :not_have
269 264
270   - tree data structures (see above "searching trees")
271   - => :child_of, :sibling_of, :descendant_of, :inclusive_descendant_of
  265 +=== Class level conditions
272 266
273   -Some of these conditions come with aliases, so you have your choice how to call the conditions. For example you can use "greater\_than" or "gt":
  267 +Each model comes preloaded with class level conditions as well. The difference is that these are not applied to each column, but instead to the model as a whole. Example: search.conditions.child_of = 2
274 268
275   - :equals => :is
276   - :does_not_equal => :is_not, :not
277   - :is_nil => :nil, :is_null, :null
278   - :is_blank => :blank
279   - :begins_with => :starts_with, :sw, :bw, :start
280   - :contains => :like, :has
281   - :ends_with => :ew, :ends, :end
282   - :greater_than => :gt, :after
283   - :greater_than_or_equal_to => :at_least, :gte
284   - :keywords => :kwords, :kw
285   - :less_than => :lt, :before
286   - :less_than_or_equal_to => :at_most, :lte
  269 + Name Description
  270 + :child_of Returns all children of value
  271 + :descendant_of Returns all descendants (children, grandchildren, grandgrandchildren, etc)
  272 + :inclusive_descendant_of Same as above but also includes the root
  273 + :sibling_of Returns all records that have the same parent
287 274
288   -For more information on each condition see Searchgasm::Condition. Each condition has it's own class and the source is pretty simple and self explanatory.
  275 +== Modifiers
289 276
290   -=== Enhanced searching and blacklisted words
  277 +=== What are modifiers?
291 278
292   -You will notice above there is "contains" and "keywords". The difference is that "keywords" is an enhanced search. It acts like a real keyword search. It finds those keywords, in any order, and blacklists meaningless words such as "and", "the", etc. "contains" finds the EXACT string in the column you are searching, spaces and all.
  279 +ActiveRecord does a great job when it comes to keeping your code database agnostic. But I feel like it neglected searching when it came to that goal. What if you want to find all records that were created after 7am? Depending on your database you would have to do something like the following:
293 280
294   -=== Roll your own conditions
  281 + MySQL: HOUR(created_at)
  282 + PostgreSQL: date_part('hour', created_at)
  283 + SQLite: strftime('%H', created_at)
295 284
296   -I didn't include this function because its MySQL specific, and it's probably rarely used, but MySQL supports a "SOUNDS LIKE" function.
  285 +All of a sudden your app is not database agnostic. Searchgasm to the rescue! Searchgasm creates what I like to call "modifiers" to handle this nonsense for you. A modifier modifies a column. For example, the hour modifier modifies a datetime column to return the hour.
297 286
298   -I want to use it, so let's add it:
  287 +The last thing to keep in mind is that <b>not all modifiers are available for every database</b>. MySQL and PostgreSQL support all of these, but SQLite does not. SQLite is nice, in the sense that its really is "lite". The only modifiers it supports are the datetime modifiers. If you want support for the other modifiers you have to write the SQLite function yourself and register the modifier in searchgasm.
299 288
300   - # config/initializers/searchgasm.rb
301   - # Actual function for MySQL databases only
302   - class SoundsLike < Searchgasm::Condition::Base
303   - class << self
304   - # I pass you the column, you tell me what you want the method to be called.
305   - # If you don't want to add this condition for that column, return nil
306   - # It defaults to "#{column.name}_sounds_like" (using the class name). So if thats what you want you don't even need to do this.
307   - def name_for_column(column)
308   - super
309   - end
310   -
311   - # Only do this if you want aliases for your condition
312   - def aliases_for_column(column)
313   - ["#{column.name}_sounds", "#{column.name}_similar_to"]
314   - end
315   - end
316   -
317   - # You can return an array or a string. NOT a hash, because all of these conditions
318   - # need to eventually get merged together. The array or string can be anything you would put in
319   - # the :conditions option for ActiveRecord::Base.find(). Also, for a list of methods / variables you can use check out
320   - # Searchgasm::Condition::Base.
321   - def to_conditions(value)
322   - ["#{quoted_table_name}.#{quoted_column_name} SOUNDS LIKE ?", value]
323   - end
324   - end
  289 +Here are all of the available modifiers:
  290 +
  291 +=== Available modifiers
  292 +
  293 + Name Aliases Description
  294 + :microsecond :microseconds, :microsecs, :microsec Extracts the microseconds
  295 + :millisecond :milliseconds, :millisecs, :millisec Extracts the milliseconds
  296 + :second :sec Extracts the seconds
  297 + :minute :min Extracts the minute
  298 + :hour Extracts the hour
  299 + :day_of_week :dow Extracts the day of week (1-7)
  300 + :day_of_month :dom Extracts the day of month (1-31)
  301 + :day_of_year :doy Extracts the day of year (1-366)
  302 + :week Extracts the week (1-53), 53rd week can be a "run-over" week to the next year
  303 + :month :mon Extracts the month (1-12)
  304 + :year Extracts the year
  305 +
  306 + :md5 Converts to a MD5
  307 + :char_length :length The length of the string (integer)
325 308
326   - Searchgasm::Conditions::Base.register_condition(SoundsLike)
  309 + :absolute :abs The absolute value (-1 => 1)
  310 + :acos The arc cosine
  311 + :asin The arc sine
  312 + :atan The arc tangent
  313 + :ceil :round_up Rounds up to the nearest int
  314 + :cos :cosine The cosine
  315 + :cot :cotangent The cotangent
  316 + :degrees Converts radians to degrees
  317 + :exp :exponential Returns the value of e (the base of natural logarithms) raised to the power of X
  318 + :floor :round_down Rounds down to the nearest int
  319 + :hex Converts the number to a hex
  320 + :log :ln The natural logarithm
  321 + :log10 Returns the base-10 logarithm
  322 + :log2 Returns the base-2 logarithm
  323 + :octal :oct Return an octal representation of a decimal number
  324 + :radians Converts to radians
  325 + :round Rounds the number
  326 + :sign The sign of the number
  327 + :sin The sine of the number
  328 + :square_root :sqrt, :sq_rt The square root of the number
  329 + :tan :tangent The tangent of the number
  330 +
  331 +=== How to use modifiers
  332 +
  333 +Here's what the above table means. Let's take the created_at column. The created_at column is a datetime column, laet's apply modifiers that make sense for a datetime column.
  334 +
  335 + search.conditions.second_of_created_at = 2
  336 + search.conditions.sec_of_created_at_greater_than = 3
  337 + search.conditions.year_of_created_at_most = 5
  338 + search.conditions.year_of_created_at_lt = 2000
  339 + # ... any of the modifiers that apply to datetime columns
  340 +
  341 +Here's the cool part. Chaining modifiers:
327 342
328   -Now test it out:
  343 + search.conditions.ceil_of_cos_of_sec_of_created_at_greater_than = 3
  344 +
  345 +As long as the modifier chain makes sense the possibilities are endless.
329 346
330   - search = User.new_search
331   - search.conditions.first_name_sounds_like = "Ben"
332   - search.all # will return any user that has a first name that sounds like "Ben"
  347 +== Roll your own conditions & modifiers
333 348
334   -Pretty nifty, huh? You can create any condition ultimately creating any SQL you want. The sky is the limit. For more information see Searchgasm::Condition::Base
  349 +For more information on this please see Searchgasm::Conditions::Base
335 350
336 351 == Under the hood
337 352
@@ -339,7 +354,11 @@ I'm a big fan of understanding what I'm using, so here's a quick explanation: Th
339 354
340 355 ActiveRecord should never know about Searchgasm
341 356
342   -What that rule means is that any options you pass when searching get "sanitized" down into options ActiveRecord can understand. Searchgasm serves as a transparent filter between you and ActiveRecord. It doesn't dig into the ActiveRecord internals, it only uses what is publicly available. It jumps in and helps out <em>only</em> when needed, otherwise it sits back and stays completely out of the way. Between that and the extensive tests, this is a solid and fast plugin.
  357 +What that rule means is that any options you pass when searching get "sanitized" down into options ActiveRecord can understand. Searchgasm serves as a transparent filter between you and ActiveRecord. It doesn't dig into the ActiveRecord internals, it only uses what is publicly available. It jumps in and helps out <em>only</em> when needed, otherwise it sits back and stays completely out of the way.
  358 +
  359 +Lastly, Searchgasm is lazy. It only creates objects, methods, and classes when needed. Once it creates them it caches them. For example, all of the nifty conditions are created via meta programming. The first time you execute something like User.new_search all of that method creation gets cached into Searchgasm::Cache::UserSearch. The next time you execute User.new_search it will be over 50 times faster because it uses the cached class.
  360 +
  361 +Between that and the extensive tests, this is a solid and fast plugin.
343 362
344 363 == Credits
345 364
4 vendor/plugins/searchgasm/TODO.rdoc
Source Rendered
... ... @@ -1,5 +1,3 @@
1 1 = To Do
2 2
3   -1. Add month condition for date and datetime columns. So you can specify a specific month, by name, number, etc.
4   -2. Add day condition for time and datetime columns. So you can specify that the time is during the day or the night.
5   -3. Add weekend condition for date and datetime columns. So you can specify if the date is on a friday, saturday, or sunday
  3 +1. Perform "more efficient" checks: year_of_created_at = 2008 and month_of_created_at = 8. Should result in a "BETWEEN" statement utilizing the column indexes. Thanks Georg for letting me know about this.
34 vendor/plugins/searchgasm/lib/searchgasm.rb
@@ -2,6 +2,9 @@
2 2
3 3 require "active_record"
4 4 require "active_record/version"
  5 +require "active_record/connection_adapters/mysql_adapter"
  6 +require "active_record/connection_adapters/postgresql_adapter"
  7 +require "active_record/connection_adapters/sqlite_adapter"
5 8 require "active_support"
6 9
7 10 # Core Ext
@@ -18,6 +21,9 @@
18 21 # ActiveRecord
19 22 require "searchgasm/active_record/base"
20 23 require "searchgasm/active_record/associations"
  24 +require "searchgasm/active_record/connection_adapters/mysql_adapter"
  25 +require "searchgasm/active_record/connection_adapters/postgresql_adapter"
  26 +require "searchgasm/active_record/connection_adapters/sqlite_adapter"
21 27
22 28 # Search
23 29 require "searchgasm/search/ordering"
@@ -33,23 +39,14 @@
33 39
34 40 # Condition
35 41 require "searchgasm/condition/base"
36   -require "searchgasm/condition/begins_with"
37   -require "searchgasm/condition/blank"
38   -require "searchgasm/condition/contains"
39   -require "searchgasm/condition/does_not_equal"
40   -require "searchgasm/condition/ends_with"
41   -require "searchgasm/condition/equals"
42   -require "searchgasm/condition/greater_than"
43   -require "searchgasm/condition/greater_than_or_equal_to"
44   -require "searchgasm/condition/keywords"
45   -require "searchgasm/condition/less_than"
46   -require "searchgasm/condition/less_than_or_equal_to"
47   -require "searchgasm/condition/nil"
48 42 require "searchgasm/condition/tree"
49   -require "searchgasm/condition/child_of"
50   -require "searchgasm/condition/descendant_of"
51   -require "searchgasm/condition/inclusive_descendant_of"
52   -require "searchgasm/condition/sibling_of"
  43 +SEARCHGASM_CONDITIONS = [:begins_with, :blank, :child_of, :descendant_of, :ends_with, :equals, :greater_than, :greater_than_or_equal_to, :inclusive_descendant_of, :like, :nil, :not_begin_with, :not_end_with, :not_equal, :not_have_keywords, :keywords, :less_than, :less_than_or_equal_to, :sibling_of]
  44 +SEARCHGASM_CONDITIONS.each { |condition| require "searchgasm/condition/#{condition}" }
  45 +
  46 +# Modifiers
  47 +require "searchgasm/modifiers/base"
  48 +SEARCHGASM_MODIFIERS = [:day_of_month, :day_of_week, :day_of_year, :hour, :microseconds, :milliseconds, :minute, :month, :second, :week, :year]
  49 +SEARCHGASM_MODIFIERS.each { |modifier| require "searchgasm/modifiers/#{modifier}" }
53 50
54 51 # Helpers
55 52 require "searchgasm/helpers/utilities"
@@ -78,9 +75,8 @@ class Base
78 75 include Protection
79 76 end
80 77
81   - [:begins_with, :blank, :child_of, :contains, :descendant_of, :does_not_equal, :ends_with, :equals, :greater_than, :greater_than_or_equal_to, :inclusive_descendant_of, :nil, :keywords, :less_than, :less_than_or_equal_to, :sibling_of].each do |condition|
82   - Base.register_condition("Searchgasm::Condition::#{condition.to_s.camelize}".constantize)
83   - end
  78 + SEARCHGASM_CONDITIONS.each { |condition| Base.register_condition("Searchgasm::Condition::#{condition.to_s.camelize}".constantize) }
  79 + SEARCHGASM_MODIFIERS.each { |modifier| Base.register_modifier("Searchgasm::Modifiers::#{modifier.to_s.camelize}".constantize) }
84 80 end
85 81
86 82 # The namespace I put all cached search classes.
149 vendor/plugins/searchgasm/lib/searchgasm/active_record/connection_adapters/mysql_adapter.rb
... ... @@ -1,15 +1,152 @@
1 1 module Searchgasm
2 2 module ActiveRecord
3   - module ConnectionAdapters
  3 + module ConnectionAdapters # :nodoc: all
4 4 module MysqlAdapter
5   - def hour_sql
6   - "HOUR(?)"
  5 + # Date / time functions
  6 + def microseconds_sql(column_name)
  7 + "MICROSECOND(#{column_name})"
7 8 end
8 9
9   - def month_sql
10   - "MONTH(?)"
  10 + def milliseconds_sql(column_name)
  11 + "(MICROSECOND(#{column_name}) / 1000)"
  12 + end
  13 +
  14 + def second_sql(column_name)
  15 + "SECOND(#{column_name})"
  16 + end
  17 +
  18 + def minute_sql(column_name)
  19 + "MINUTE(#{column_name})"
  20 + end
  21 +
  22 + def hour_sql(column_name)
  23 + "HOUR(#{column_name})"
  24 + end
  25 +
  26 + def day_of_week_sql(column_name)
  27 + "DAYOFWEEK(#{column_name})"
  28 + end
  29 +
  30 + def day_of_month_sql(column_name)
  31 + "DAYOFMONTH(#{column_name})"
  32 + end
  33 +
  34 + def day_of_year_sql(column_name)
  35 + "DAYOFYEAR(#{column_name})"
  36 + end
  37 +
  38 + def week_sql(column_name)
  39 + "WEEK(#{column_name}, 2)"
  40 + end
  41 +
  42 + def month_sql(column_name)
  43 + "MONTH(#{column_name})"
  44 + end
  45 +
  46 + def year_sql(column_name)
  47 + "MONTH(#{column_name})"
  48 + end
  49 +
  50 + # String functions
  51 + def char_length_sql(column_name)
  52 + "CHAR_LENGTH(#{column_name})"
  53 + end
  54 +
  55 + def md5_sql(column_name)
  56 + "MD5(#{column_name})"
  57 + end
  58 +
  59 + # Number functions
  60 + def absolute_sql(column_name)
  61 + "ABS(#{column_name})"
  62 + end
  63 +
  64 + def acos_sql(column_name)
  65 + "ACOS(#{column_name})"
  66 + end
  67 +
  68 + def asin_sql(column_name)
  69 + "ASIN(#{column_name})"
  70 + end
  71 +
  72 + def atan_sql(column_name)
  73 + "ATAN(#{column_name})"
  74 + end
  75 +
  76 + def ceil_sql(column_name)
  77 + "CEIL(#{column_name})"
  78 + end
  79 +
  80 + def cos_sql(column_name)
  81 + "COS(#{column_name})"
  82 + end
  83 +
  84 + def cot_sql(column_name)
  85 + "COT(#{column_name})"
  86 + end
  87 +
  88 + def degrees_sql(column_name)
  89 + "DEGREES(#{column_name})"
  90 + end
  91 +
  92 + def exp_sql(column_name)
  93 + "EXP(#{column_name})"
  94 + end
  95 +
  96 + def floor_sql(column_name)
  97 + "FLOOR(#{column_name})"
  98 + end
  99 +
  100 + def hex_sql(column_name)
  101 + "HEX(#{column_name})"
  102 + end
  103 +
  104 + def ln_sql(column_name)
  105 + "LN(#{column_name})"
  106 + end
  107 +
  108 + def log_sql(column_name)
  109 + "LOG(#{column_name})"
  110 + end
  111 +
  112 + def log2_sql(column_name)
  113 + "LOG2(#{column_name})"
  114 + end
  115 +
  116 + def log10_sql(column_name)
  117 + "LOG10(#{column_name})"
  118 + end
  119 +
  120 + def octal_sql(column_name)
  121 + "OCT(#{column_name})"
  122 + end
  123 +
  124 + def radians_sql(column_name)
  125 + "RADIANS(#{column_name})"
  126 + end
  127 +
  128 + def round_sql(column_name)
  129 + "ROUND(#{column_name})"
  130 + end
  131 +
  132 + def sign_sql(column_name)
  133 + "SIGN(#{column_name})"
  134 + end
  135 +
  136 + def sin_sql(column_name)
  137 + "SIN(#{column_name})"
  138 + end
  139 +
  140 + def square_root_sql(column_name)
  141 + "SQRT(#{column_name})"
  142 + end
  143 +
  144 + def tan_sql(column_name)
  145 + "TAN(#{column_name})"
11 146 end
12 147 end
13 148 end
14 149 end
15   -end
  150 +end
  151 +
  152 +::ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include, Searchgasm::ActiveRecord::ConnectionAdapters::MysqlAdapter)
148 vendor/plugins/searchgasm/lib/searchgasm/active_record/connection_adapters/postgresql_adapter.rb
... ... @@ -0,0 +1,148 @@
  1 +module Searchgasm
  2 + module ActiveRecord
  3 + module ConnectionAdapters
  4 + module PostgreSQLAdapter
  5 + # Datetime functions
  6 + def microseconds_sql(column_name)
  7 + "date_part('microseconds', #{column_name})"
  8 + end
  9 +
  10 + def milliseconds_sql(column_name)
  11 + "date_part('milliseconds', #{column_name})"
  12 + end
  13 +
  14 + def second_sql(column_name)
  15 + "date_part('second', #{column_name})"
  16 + end
  17 +
  18 + def minute_sql(column_name)
  19 + "date_part('minute', #{column_name})"
  20 + end
  21 +
  22 + def hour_sql(column_name)
  23 + "date_part('hour', #{column_name})"
  24 + end
  25 +
  26 + def day_of_week_sql(column_name)
  27 + "(date_part('dow', #{column_name}) + 1)"
  28 + end
  29 +
  30 + def day_of_month_sql(column_name)
  31 + "date_part('day', #{column_name})"
  32 + end
  33 +
  34 + def day_of_year_sql(column_name)
  35 + "date_part('doy', #{column_name})"
  36 + end
  37 +
  38 + def week_sql(column_name)
  39 + "date_part('week', #{column_name})"
  40 + end
  41 +
  42 + def month_sql(column_name)
  43 + "date_part('month', #{column_name})"
  44 + end
  45 +
  46 + def year_sql(column_name)
  47 + "date_part('year', #{column_name})"
  48 + end
  49 +
  50 + # String functions
  51 + def char_length_sql(column_name)
  52 + "length(#{column_name})"
  53 + end
  54 +
  55 + def md5_sql(column_name)
  56 + "md5(#{column_name})"
  57 + end
  58 +
  59 + # Number functions
  60 + def absolute_sql(column_name)
  61 + "abs(#{column_name})"
  62 + end
  63 +
  64 + def acos_sql(column_name)
  65 + "acos(#{column_name})"
  66 + end
  67 +
  68 + def asin_sql(column_name)
  69 + "asin(#{column_name})"
  70 + end
  71 +
  72 + def atan_sql(column_name)
  73 + "atan(#{column_name})"
  74 + end
  75 +
  76 + def ceil_sql(column_name)
  77 + "ceil(#{column_name})"
  78 + end
  79 +
  80 + def cos_sql(column_name)
  81 + "cos(#{column_name})"
  82 + end
  83 +
  84 + def cot_sql(column_name)
  85 + "cot(#{column_name})"
  86 + end
  87 +
  88 + def degrees_sql(column_name)
  89 + "degrees(#{column_name})"
  90 + end
  91 +
  92 + def exp_sql(column_name)
  93 + "exp(#{column_name})"
  94 + end
  95 +
  96 + def floor_sql(column_name)
  97 + "floor(#{column_name})"
  98 + end
  99 +
  100 + def hex_sql(column_name)
  101 + "to_hex(#{column_name})"
  102 + end
  103 +
  104 + def ln_sql(column_name)
  105 + "ln(#{column_name})"
  106 + end
  107 +
  108 + def log_sql(column_name)
  109 + "log(#{column_name})"
  110 + end
  111 +
  112 + def log2_sql(column_name)
  113 + "log(2.0, #{column_name})"
  114 + end
  115 +
  116 + def log10_sql(column_name)
  117 + "log(10.0, #{column_name})"
  118 + end
  119 +
  120 + def radians_sql(column_name)
  121 + "radians(#{column_name})"
  122 + end
  123 +
  124 + def round_sql(column_name)
  125 + "round(#{column_name})"
  126 + end
  127 +
  128 + def sign_sql(column_name)
  129 + "sign(#{column_name})"
  130 + end
  131 +
  132 + def sin_sql(column_name)
  133 + "sin(#{column_name})"
  134 + end
  135 +
  136 + def square_root_sql(column_name)
  137 + "sqrt(#{column_name})"
  138 + end
  139 +
  140 + def tan_sql(column_name)
  141 + "tan(#{column_name})"
  142 + end
  143 + end
  144 + end
  145 + end
  146 +end
  147 +
  148 +::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:include, Searchgasm::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
54 vendor/plugins/searchgasm/lib/searchgasm/active_record/connection_adapters/sqlite_adapter.rb
... ... @@ -0,0 +1,54 @@
  1 +module Searchgasm
  2 + module ActiveRecord
  3 + module ConnectionAdapters
  4 + module SQLiteAdapter
  5 + # Date functions
  6 + def microseconds_sql(column_name)
  7 + "((strftime('%f', #{column_name}) % 1) * 1000000)"
  8 + end
  9 +
  10 + def milliseconds_sql(column_name)
  11 + "((strftime('%f', #{column_name}) % 1) * 1000)"
  12 + end
  13 +
  14 + def second_sql(column_name)
  15 + "strftime('%S', #{column_name})"
  16 + end
  17 +
  18 + def minute_sql(column_name)
  19 + "strftime('%M', #{column_name})"
  20 + end
  21 +
  22 + def hour_sql(column_name)
  23 + "strftime('%H', #{column_name})"
  24 + end
  25 +
  26 + def day_of_week_sql(column_name)
  27 + "strftime('%w', #{column_name})"
  28 + end
  29 +
  30 + def day_of_month_sql(column_name)
  31 + "strftime('%d', #{column_name})"
  32 + end
  33 +
  34 + def day_of_year_sql(column_name)
  35 + "strftime('%j', #{column_name})"
  36 + end
  37 +
  38 + def week_sql(column_name)
  39 + "strftime('%W', #{column_name})"
  40 + end
  41 +
  42 + def month_sql(column_name)
  43 + "strftime('%m', #{column_name})"
  44 + end
  45 +
  46 + def year_sql(column_name)
  47 + "strftime('%Y', #{column_name})"
  48 + end
  49 + end
  50 + end
  51 + end
  52 +end
  53 +
  54 +::ActiveRecord::ConnectionAdapters::SQLiteAdapter.send(:include, Searchgasm::ActiveRecord::ConnectionAdapters::SQLiteAdapter)
145 vendor/plugins/searchgasm/lib/searchgasm/condition/base.rb
@@ -7,71 +7,47 @@ module Condition # :nodoc:
7 7 class Base
8 8 include Shared::Utilities
9 9
10   - attr_accessor :column, :klass
11   - class_inheritable_accessor :ignore_meaningless, :type_cast_sql_type
12   - self.ignore_meaningless = true
  10 + attr_accessor :column, :column_for_type_cast, :column_sql, :klass
  11 + class_inheritable_accessor :handle_array_value, :ignore_meaningless_value, :value_type
  12 + self.ignore_meaningless_value = true
13 13
14 14 class << self
15   - # Name of the condition inferred from the class name
16   - def condition_name
17   - name.split("::").last.gsub(/Condition$/, "").underscore
  15 + # Name of the condition type inferred from the class name
  16 + def condition_type_name
  17 + name.split("::").last.underscore
18 18 end
19 19
20   - # I pass you a column you tell me what to call the condition. If you don't want to use this condition for the column
21   - # just return nil
22   - def name_for_column(column)
23   - "#{column.name}_#{condition_name}"
  20 + def handle_array_value?
  21 + handle_array_value == true
24 22 end
25 23
26   - # Alias methods for the column condition.
27   - def aliases_for_column(column)
28   - []
29   - end
30   -
31   - def ignore_meaningless? # :nodoc:
32   - ignore_meaningless == true
33   - end
34   -
35   - # Sane as name_for_column but for the class as a whole. For example the tree methods apply to the class as a whole and not
36   - # specific columns. Any condition that applies to columns should probably return nil here.
37   - def name_for_klass(klass)
38   - nil
  24 + def ignore_meaningless_value? # :nodoc:
  25 + ignore_meaningless_value == true
39 26 end
40 27
41   - # Alias methods for the klass condition
42   - def aliases_for_klass(klass)
  28 + # Determines what to call the condition for the model
  29 + #
  30 + # Searchgasm tries to create conditions on each model. Before it does this it passes the model to this method to see what to call the condition. If the condition type doesnt want to create a condition on
  31 + # a model it will just return nil and Searchgasm will skip over it.
  32 + def condition_names_for_model
43 33 []
44 34 end
45 35
46   - # A utility method for using in name_for_column. Determines if a column contains a date.
47   - def date_column?(column)
48   - [:datetime, :date, :timestamp].include?(column.type)
49   - end
50   -
51   - # A utility method for using in name_for_column. Determines if a column contains a date and a time.
52   - def datetime_column?(column)
53   - [:datetime, :timestamp, :time, :date].include?(column.type)
54   - end
55   -
56   - # A utility method for using in name_for_column. For example the keywords condition only applied to string columns, the great than condition doesnt.
57   - def string_column?(column)
58   - [:string, :text].include?(column.type)
59   - end
60   -
61   - # A utility method for using in name_for_column. For example you wouldn't want a string column to use the greater thann condition, but you would for an integer column.
62   - def comparable_column?(column)
63   - [:integer, :float, :decimal, :datetime, :timestamp, :time, :date].include?(column.type)
64   - end
65   -
66   - # A utility method for using in name_for_column. Determines if a column contains a time.
67   - def time_column?(column)
68   - [:datetime, :timestamp, :time].include?(column.type)
  36 + # Same as condition_name_for_model, but for a model's column obj
  37 + def condition_names_for_column
  38 + [condition_type_name]
69 39 end
70 40 end
71 41
72   - def initialize(klass, column = nil)
  42 + def initialize(klass, column_obj = nil, column_type = nil, column_sql = nil)
73 43 self.klass = klass
74   - self.column = column.is_a?(String) ? klass.columns_hash[column] : column
  44 +
  45 + if column_obj
  46 + self.column = column_obj.class < ::ActiveRecord::ConnectionAdapters::Column ? column_obj : klass.columns_hash[column_obj.to_s]
  47 + column_type ||= column.type
  48 + self.column_for_type_cast = column.class.new(column.name, column.default.to_s, self.class.value_type.to_s || column_type.to_s, column.null)
  49 + self.column_sql = column_sql || "#{klass.connection.quote_table_name(klass.table_name)}.#{klass.connection.quote_column_name(column.name)}"
  50 + end
75 51 end
76 52
77 53 # Allows nils to be meaninful values
@@ -84,63 +60,60 @@ def explicitly_set_value?
84 60 @explicitly_set_value == true
85 61 end
86 62
87   - # A convenience method for the name of the method for that specific column or klass
88   - def name
89   - column ? self.class.name_for_column(column) : self.class.name_for_klass(klass)
90   - end
91   -
92   - # A convenience method for the name of this condition
93   - def condition_name
94   - self.class.condition_name
95   - end
96   -
97   - # Quotes a column name properly for sql.
98   - def quote_column_name(column_name)
99   - klass.connection.quote_column_name(column_name)
100   - end
101   -
102   - # A convenience method for using when writing your sql in to_conditions. This is the proper way to use a column name in a query for most databases
103   - def quoted_column_name
104   - quote_column_name(column.name)
105   - end
106   -
107   - # Quotes a table name properly for sql
108   - def quote_table_name(table_name)
109   - klass.connection.quote_table_name(table_name)
110   - end
111   -
112   - # A convenience method for using when writing your sql in to_conditions. This is the proper way to use a table name in a query for most databases
113   - def quoted_table_name
114   - quote_table_name(klass.table_name)
  63 + def meaningless_value?
  64 + !explicitly_set_value? || (self.class.ignore_meaningless_value? && meaningless?(@value))
115 65 end
116 66
117 67 # You should refrain from overwriting this method, it performs various tasks before callign your to_conditions method, allowing you to keep to_conditions simple.
118 68 def sanitize(alt_value = nil) # :nodoc:
119   - return unless explicitly_set_value?
  69 + return if meaningless_value?
120 70 v = alt_value || value
121   - if v.is_a?(Array) && !["equals", "does_not_equal"].include?(condition_name)
  71 + if v.is_a?(Array) && !self.class.handle_array_value?
122 72 merge_conditions(*v.collect { |i| sanitize(i) })
123 73 else
124   - v = v.utc if column && [:time, :timestamp, :datetime].include?(column.type) && klass.time_zone_aware_attributes && !klass.skip_time_zone_conversion_for_attributes.include?(column.name.to_sym)
  74 + v = v.utc if column && v.respond_to?(:utc) && [:time, :timestamp, :datetime].include?(column.type) && klass.time_zone_aware_attributes && !klass.skip_time_zone_conversion_for_attributes.include?(column.name.to_sym)
125 75 to_conditions(v)
126 76 end
127 77 end
128   -
  78 +
129 79 # The value for the condition
130 80 def value
131   - @value.is_a?(String) ? column_for_type_cast.type_cast(@value) : @value
  81 + return @casted_value if @casted_value
  82 +
  83 + if !column_for_type_cast || meaningless_value?
  84 + @casted_value = @value
  85 + else