Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Released v1.3.0, added modifiers

  • Loading branch information...
commit 015f74898e79281e6b270dd053d6cda4910e7a1b 1 parent 20fe2a8
@binarylogic binarylogic authored
Showing with 1,497 additions and 550 deletions.
  1. +8 −0 CHANGELOG.rdoc
  2. +42 −3 Manifest
  3. +101 −82 README.rdoc
  4. +1 −3 TODO.rdoc
  5. +15 −19 lib/searchgasm.rb
  6. +143 −6 lib/searchgasm/active_record/connection_adapters/mysql_adapter.rb
  7. +148 −0 lib/searchgasm/active_record/connection_adapters/postgresql_adapter.rb
  8. +54 −0 lib/searchgasm/active_record/connection_adapters/sqlite_adapter.rb
  9. +59 −86 lib/searchgasm/condition/base.rb
  10. +3 −8 lib/searchgasm/condition/begins_with.rb
  11. +5 −5 lib/searchgasm/condition/blank.rb
  12. +0 −20 lib/searchgasm/condition/contains.rb
  13. +0 −32 lib/searchgasm/condition/during_evening.rb
  14. +3 −8 lib/searchgasm/condition/ends_with.rb
  15. +4 −3 lib/searchgasm/condition/equals.rb
  16. +3 −14 lib/searchgasm/condition/greater_than.rb
  17. +3 −14 lib/searchgasm/condition/greater_than_or_equal_to.rb
  18. +3 −8 lib/searchgasm/condition/keywords.rb
  19. +3 −14 lib/searchgasm/condition/less_than.rb
  20. +3 −14 lib/searchgasm/condition/less_than_or_equal_to.rb
  21. +15 −0 lib/searchgasm/condition/like.rb
  22. +5 −5 lib/searchgasm/condition/nil.rb
  23. +17 −0 lib/searchgasm/condition/not_begin_with.rb
  24. +17 −0 lib/searchgasm/condition/not_end_with.rb
  25. +5 −4 lib/searchgasm/condition/{does_not_equal.rb → not_equal.rb}
  26. +17 −0 lib/searchgasm/condition/not_have_keywords.rb
  27. +17 −0 lib/searchgasm/condition/not_like.rb
  28. +4 −5 lib/searchgasm/condition/tree.rb
  29. +218 −72 lib/searchgasm/conditions/base.rb
  30. +15 −0 lib/searchgasm/modifiers/absolute.rb
  31. +11 −0 lib/searchgasm/modifiers/acos.rb
  32. +11 −0 lib/searchgasm/modifiers/asin.rb
  33. +11 −0 lib/searchgasm/modifiers/atan.rb
  34. +27 −0 lib/searchgasm/modifiers/base.rb
  35. +15 −0 lib/searchgasm/modifiers/ceil.rb
  36. +15 −0 lib/searchgasm/modifiers/char_length.rb
  37. +15 −0 lib/searchgasm/modifiers/cos.rb
  38. +15 −0 lib/searchgasm/modifiers/cot.rb
  39. +15 −0 lib/searchgasm/modifiers/day_of_month.rb
  40. +15 −0 lib/searchgasm/modifiers/day_of_week.rb
  41. +15 −0 lib/searchgasm/modifiers/day_of_year.rb
  42. +11 −0 lib/searchgasm/modifiers/degrees.rb
  43. +15 −0 lib/searchgasm/modifiers/exp.rb
  44. +15 −0 lib/searchgasm/modifiers/floor.rb
  45. +11 −0 lib/searchgasm/modifiers/hex.rb
  46. +11 −0 lib/searchgasm/modifiers/hour.rb
  47. +15 −0 lib/searchgasm/modifiers/log.rb
  48. +11 −0 lib/searchgasm/modifiers/log10.rb
  49. +11 −0 lib/searchgasm/modifiers/log2.rb
  50. +11 −0 lib/searchgasm/modifiers/md5.rb
  51. +11 −0 lib/searchgasm/modifiers/microseconds.rb
  52. +11 −0 lib/searchgasm/modifiers/milliseconds.rb
  53. +15 −0 lib/searchgasm/modifiers/minute.rb
  54. +15 −0 lib/searchgasm/modifiers/month.rb
  55. +15 −0 lib/searchgasm/modifiers/octal.rb
  56. +11 −0 lib/searchgasm/modifiers/radians.rb
  57. +11 −0 lib/searchgasm/modifiers/round.rb
  58. +15 −0 lib/searchgasm/modifiers/second.rb
  59. +11 −0 lib/searchgasm/modifiers/sign.rb
  60. +11 −0 lib/searchgasm/modifiers/sin.rb
  61. +15 −0 lib/searchgasm/modifiers/square_root.rb
  62. +15 −0 lib/searchgasm/modifiers/tan.rb
  63. +11 −0 lib/searchgasm/modifiers/week.rb
  64. +11 −0 lib/searchgasm/modifiers/year.rb
  65. +0 −10 lib/searchgasm/shared/utilities.rb
  66. +2 −2 lib/searchgasm/version.rb
  67. +9 −0 test/libs/ordered_hash.rb
  68. +21 −47 test/test_condition_base.rb
  69. +44 −44 test/test_condition_types.rb
  70. +34 −21 test/test_conditions_base.rb
  71. +1 −0  test/test_helper.rb
  72. +1 −1  test/test_search_conditions.rb
View
8 CHANGELOG.rdoc
@@ -1,3 +1,11 @@
+== 1.3.0 released 2008-09-29
+
+* Added modifiers into the mix: hour_of_created_at_less_than = 10, etc.
+* 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
+ 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.
+* Altered how values are handled for each condition, meaningless values are ignored completely.
+* Added in more "not" conditions: not_like, not_begin_with, not_have_keywords, etc
+
== 1.2.2 released 2008-09-29
* Fixed bug when reverse engineering order to order_by, assumed ASC and DESC would always be present when they are not.
View
45 Manifest
@@ -10,10 +10,7 @@ lib/searchgasm/condition/base.rb
lib/searchgasm/condition/begins_with.rb
lib/searchgasm/condition/blank.rb
lib/searchgasm/condition/child_of.rb
-lib/searchgasm/condition/contains.rb
lib/searchgasm/condition/descendant_of.rb
-lib/searchgasm/condition/does_not_equal.rb
-lib/searchgasm/condition/during_evening.rb
lib/searchgasm/condition/ends_with.rb
lib/searchgasm/condition/equals.rb
lib/searchgasm/condition/greater_than.rb
@@ -22,7 +19,13 @@ lib/searchgasm/condition/inclusive_descendant_of.rb
lib/searchgasm/condition/keywords.rb
lib/searchgasm/condition/less_than.rb
lib/searchgasm/condition/less_than_or_equal_to.rb
+lib/searchgasm/condition/like.rb
lib/searchgasm/condition/nil.rb
+lib/searchgasm/condition/not_begin_with.rb
+lib/searchgasm/condition/not_end_with.rb
+lib/searchgasm/condition/not_equal.rb
+lib/searchgasm/condition/not_have_keywords.rb
+lib/searchgasm/condition/not_like.rb
lib/searchgasm/condition/sibling_of.rb
lib/searchgasm/condition/tree.rb
lib/searchgasm/conditions/base.rb
@@ -37,6 +40,41 @@ lib/searchgasm/helpers/control_types/remote_select.rb
lib/searchgasm/helpers/control_types/select.rb
lib/searchgasm/helpers/form.rb
lib/searchgasm/helpers/utilities.rb
+lib/searchgasm/modifiers/absolute.rb
+lib/searchgasm/modifiers/acos.rb
+lib/searchgasm/modifiers/asin.rb
+lib/searchgasm/modifiers/atan.rb
+lib/searchgasm/modifiers/base.rb
+lib/searchgasm/modifiers/ceil.rb
+lib/searchgasm/modifiers/char_length.rb
+lib/searchgasm/modifiers/cos.rb
+lib/searchgasm/modifiers/cot.rb
+lib/searchgasm/modifiers/day_of_month.rb
+lib/searchgasm/modifiers/day_of_week.rb
+lib/searchgasm/modifiers/day_of_year.rb
+lib/searchgasm/modifiers/degrees.rb
+lib/searchgasm/modifiers/exp.rb
+lib/searchgasm/modifiers/floor.rb
+lib/searchgasm/modifiers/hex.rb
+lib/searchgasm/modifiers/hour.rb
+lib/searchgasm/modifiers/log.rb
+lib/searchgasm/modifiers/log10.rb
+lib/searchgasm/modifiers/log2.rb
+lib/searchgasm/modifiers/md5.rb
+lib/searchgasm/modifiers/microseconds.rb
+lib/searchgasm/modifiers/milliseconds.rb
+lib/searchgasm/modifiers/minute.rb
+lib/searchgasm/modifiers/month.rb
+lib/searchgasm/modifiers/octal.rb
+lib/searchgasm/modifiers/radians.rb
+lib/searchgasm/modifiers/round.rb
+lib/searchgasm/modifiers/second.rb
+lib/searchgasm/modifiers/sign.rb
+lib/searchgasm/modifiers/sin.rb
+lib/searchgasm/modifiers/square_root.rb
+lib/searchgasm/modifiers/tan.rb
+lib/searchgasm/modifiers/week.rb
+lib/searchgasm/modifiers/year.rb
lib/searchgasm/search/base.rb
lib/searchgasm/search/conditions.rb
lib/searchgasm/search/ordering.rb
@@ -56,6 +94,7 @@ test/fixtures/orders.yml
test/fixtures/user_groups.yml
test/fixtures/users.yml
test/libs/acts_as_tree.rb
+test/libs/ordered_hash.rb
test/libs/rexml_fix.rb
test/test_active_record_associations.rb
test/test_active_record_base.rb
View
183 README.rdoc
@@ -33,7 +33,9 @@ Now try out some of the examples below:
User.all(
:conditions => {
:first_name_contains => "Ben", # first_name like '%Ben%'
- :email_ends_with => "binarylogic.com" # email like '%binarylogic.com'
+ :email_ends_with => "binarylogic.com", # email like '%binarylogic.com'
+ :created_after => Time.now, # created_at > Time.now
+ :created_at_hour_gt => 5 # HOUR(created_at) > 5 (depends on DB type)
},
:per_page => 20, # limit 20
:page => 3, # offset 40, which starts us on page 3
@@ -46,6 +48,8 @@ same as above, but object based
search = User.new_search
search.conditions.first_name_contains = "Ben"
search.conditions.email_ends_with = "binarylogic.com"
+ search.conditions.created_after = Time.now
+ search.conditiona.created_at_hour_gt = 5
search.per_page = 20
search.page = 3
search.order_as = "ASC"
@@ -196,27 +200,6 @@ As you saw above, the nice thing about Searchgasm is it's integration with forms
search = @current_user.orders.build_search(:conditions => {:total_lte => 500})
-== Searching trees
-
-For tree data structures you get a few nifty methods. Let's assume Users is a tree data structure.
-
- # Child of
- User.all(:conditions => {:child_of => User.roots.first})
- User.all(:conditions => {:child_of => User.roots.first.id})
-
- # Sibling of
- User.all(:conditions => {:sibling_of => User.roots.first})
- User.all(:conditions => {:sibling_of => User.roots.first.id})
-
- # Descendant of (includes all recursive children: children, grand children, great grand children, etc)
- User.all(:conditions => {:descendant_of => User.roots.first})
- User.all(:conditions => {:descendant_of => User.roots.first.id})
-
- # Inclusive descendant_of. Same as above but includes the root
- User.all(:conditions => {:inclusive_descendant_of => User.roots.first})
- User.all(:conditions => {:inclusive_descendant_of => User.roots.first.id})
-
-
== Scope support
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.
== Available Conditions
-Depending on the type, each column comes preloaded with a bunch of nifty conditions:
+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.
- all columns
- => :equals, :does_not_equal
+=== Column conditions
- :string, :text
- => :begins_with, :contains, :keywords, :ends_with
+Each column can be used with any of the following conditions
- :integer, :float, :decimal,:datetime, :timestamp, :time, :date
- => :greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to
+ Name Aliases Description
+ :begins_with :starts_with, :sw, :bw, :start col LIKE 'value%'
+ :ends_with :ew, :ends, :end col LIKE '%value'
+ :equals :is, "" Lets ActiveRecord handle this
+ :greater_than :gt, :after col > value
+ :greater_than_or_equal_to :at_least, :least, :gte col >= value
+ :less_than :lt, :before col < value
+ :less_than_or_equal_to :at_most, :most, :lte col <= value
+ :keywords :kwords, :kw Splits into each word and omits meaningless words, a true keyword search
+ :like :contains, :has col LIKE '%value%'
+
+ :not_begin_with :not_bw, :not_sw, :not_start_with, :not_start, :beginning_is_not, :beginning_not
+ :not_end_with :not_ew, :not_end, :end_is_not, :end_not
+ :not_equal :does_not_equal, :is_not, :not
+ :not_have_keywords :not_have_keywords, :not_keywords, :not_have_kw, :not_kw, :not_have_kwwords, :not_kwwords
+ :not_like :not_contain, :not_have
- tree data structures (see above "searching trees")
- => :child_of, :sibling_of, :descendant_of, :inclusive_descendant_of
+=== Class level conditions
-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":
+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
- :equals => :is
- :does_not_equal => :is_not, :not
- :is_nil => :nil, :is_null, :null
- :is_blank => :blank
- :begins_with => :starts_with, :sw, :bw, :start
- :contains => :like, :has
- :ends_with => :ew, :ends, :end
- :greater_than => :gt, :after
- :greater_than_or_equal_to => :at_least, :gte
- :keywords => :kwords, :kw
- :less_than => :lt, :before
- :less_than_or_equal_to => :at_most, :lte
+ Name Description
+ :child_of Returns all children of value
+ :descendant_of Returns all descendants (children, grandchildren, grandgrandchildren, etc)
+ :inclusive_descendant_of Same as above but also includes the root
+ :sibling_of Returns all records that have the same parent
-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.
+== Modifiers
-=== Enhanced searching and blacklisted words
+=== What are modifiers?
-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.
+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:
-=== Roll your own conditions
+ MySQL: HOUR(created_at)
+ PostgreSQL: date_part('hour', created_at)
+ SQLite: strftime('%H', created_at)
-I didn't include this function because its MySQL specific, and it's probably rarely used, but MySQL supports a "SOUNDS LIKE" function.
+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.
-I want to use it, so let's add it:
+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.
- # config/initializers/searchgasm.rb
- # Actual function for MySQL databases only
- class SoundsLike < Searchgasm::Condition::Base
- class << self
- # I pass you the column, you tell me what you want the method to be called.
- # If you don't want to add this condition for that column, return nil
- # 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.
- def name_for_column(column)
- super
- end
-
- # Only do this if you want aliases for your condition
- def aliases_for_column(column)
- ["#{column.name}_sounds", "#{column.name}_similar_to"]
- end
- end
-
- # You can return an array or a string. NOT a hash, because all of these conditions
- # need to eventually get merged together. The array or string can be anything you would put in
- # the :conditions option for ActiveRecord::Base.find(). Also, for a list of methods / variables you can use check out
- # Searchgasm::Condition::Base.
- def to_conditions(value)
- ["#{quoted_table_name}.#{quoted_column_name} SOUNDS LIKE ?", value]
- end
- end
+Here are all of the available modifiers:
+
+=== Available modifiers
+
+ Name Aliases Description
+ :microsecond :microseconds, :microsecs, :microsec Extracts the microseconds
+ :millisecond :milliseconds, :millisecs, :millisec Extracts the milliseconds
+ :second :sec Extracts the seconds
+ :minute :min Extracts the minute
+ :hour Extracts the hour
+ :day_of_week :dow Extracts the day of week (1-7)
+ :day_of_month :dom Extracts the day of month (1-31)
+ :day_of_year :doy Extracts the day of year (1-366)
+ :week Extracts the week (1-53), 53rd week can be a "run-over" week to the next year
+ :month :mon Extracts the month (1-12)
+ :year Extracts the year
+
+ :md5 Converts to a MD5
+ :char_length :length The length of the string (integer)
- Searchgasm::Conditions::Base.register_condition(SoundsLike)
+ :absolute :abs The absolute value (-1 => 1)
+ :acos The arc cosine
+ :asin The arc sine
+ :atan The arc tangent
+ :ceil :round_up Rounds up to the nearest int
+ :cos :cosine The cosine
+ :cot :cotangent The cotangent
+ :degrees Converts radians to degrees
+ :exp :exponential Returns the value of e (the base of natural logarithms) raised to the power of X
+ :floor :round_down Rounds down to the nearest int
+ :hex Converts the number to a hex
+ :log :ln The natural logarithm
+ :log10 Returns the base-10 logarithm
+ :log2 Returns the base-2 logarithm
+ :octal :oct Return an octal representation of a decimal number
+ :radians Converts to radians
+ :round Rounds the number
+ :sign The sign of the number
+ :sin The sine of the number
+ :square_root :sqrt, :sq_rt The square root of the number
+ :tan :tangent The tangent of the number
+
+=== How to use modifiers
+
+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.
+
+ search.conditions.second_of_created_at = 2
+ search.conditions.sec_of_created_at_greater_than = 3
+ search.conditions.year_of_created_at_most = 5
+ search.conditions.year_of_created_at_lt = 2000
+ # ... any of the modifiers that apply to datetime columns
+
+Here's the cool part. Chaining modifiers:
-Now test it out:
+ search.conditions.ceil_of_cos_of_sec_of_created_at_greater_than = 3
+
+As long as the modifier chain makes sense the possibilities are endless.
- search = User.new_search
- search.conditions.first_name_sounds_like = "Ben"
- search.all # will return any user that has a first name that sounds like "Ben"
+== Roll your own conditions & modifiers
-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
+For more information on this please see Searchgasm::Conditions::Base
== Under the hood
@@ -339,7 +354,11 @@ I'm a big fan of understanding what I'm using, so here's a quick explanation: Th
ActiveRecord should never know about Searchgasm
-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.
+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.
+
+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.
+
+Between that and the extensive tests, this is a solid and fast plugin.
== Credits
View
4 TODO.rdoc
@@ -1,5 +1,3 @@
= To Do
-1. Add month condition for date and datetime columns. So you can specify a specific month, by name, number, etc.
-2. Add day condition for time and datetime columns. So you can specify that the time is during the day or the night.
-3. Add weekend condition for date and datetime columns. So you can specify if the date is on a friday, saturday, or sunday
+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.
View
34 lib/searchgasm.rb
@@ -2,6 +2,9 @@
require "active_record"
require "active_record/version"
+require "active_record/connection_adapters/mysql_adapter"
+require "active_record/connection_adapters/postgresql_adapter"
+require "active_record/connection_adapters/sqlite_adapter"
require "active_support"
# Core Ext
@@ -18,6 +21,9 @@
# ActiveRecord
require "searchgasm/active_record/base"
require "searchgasm/active_record/associations"
+require "searchgasm/active_record/connection_adapters/mysql_adapter"
+require "searchgasm/active_record/connection_adapters/postgresql_adapter"
+require "searchgasm/active_record/connection_adapters/sqlite_adapter"
# Search
require "searchgasm/search/ordering"
@@ -33,23 +39,14 @@
# Condition
require "searchgasm/condition/base"
-require "searchgasm/condition/begins_with"
-require "searchgasm/condition/blank"
-require "searchgasm/condition/contains"
-require "searchgasm/condition/does_not_equal"
-require "searchgasm/condition/ends_with"
-require "searchgasm/condition/equals"
-require "searchgasm/condition/greater_than"
-require "searchgasm/condition/greater_than_or_equal_to"
-require "searchgasm/condition/keywords"
-require "searchgasm/condition/less_than"
-require "searchgasm/condition/less_than_or_equal_to"
-require "searchgasm/condition/nil"
require "searchgasm/condition/tree"
-require "searchgasm/condition/child_of"
-require "searchgasm/condition/descendant_of"
-require "searchgasm/condition/inclusive_descendant_of"
-require "searchgasm/condition/sibling_of"
+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]
+SEARCHGASM_CONDITIONS.each { |condition| require "searchgasm/condition/#{condition}" }
+
+# Modifiers
+require "searchgasm/modifiers/base"
+SEARCHGASM_MODIFIERS = [:day_of_month, :day_of_week, :day_of_year, :hour, :microseconds, :milliseconds, :minute, :month, :second, :week, :year]
+SEARCHGASM_MODIFIERS.each { |modifier| require "searchgasm/modifiers/#{modifier}" }
# Helpers
require "searchgasm/helpers/utilities"
@@ -78,9 +75,8 @@ class Base
include Protection
end
- [: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|
- Base.register_condition("Searchgasm::Condition::#{condition.to_s.camelize}".constantize)
- end
+ SEARCHGASM_CONDITIONS.each { |condition| Base.register_condition("Searchgasm::Condition::#{condition.to_s.camelize}".constantize) }
+ SEARCHGASM_MODIFIERS.each { |modifier| Base.register_modifier("Searchgasm::Modifiers::#{modifier.to_s.camelize}".constantize) }
end
# The namespace I put all cached search classes.
View
149 lib/searchgasm/active_record/connection_adapters/mysql_adapter.rb
@@ -1,15 +1,152 @@
module Searchgasm
module ActiveRecord
- module ConnectionAdapters
+ module ConnectionAdapters # :nodoc: all
module MysqlAdapter
- def hour_sql
- "HOUR(?)"
+ # Date / time functions
+ def microseconds_sql(column_name)
+ "MICROSECOND(#{column_name})"
end
- def month_sql
- "MONTH(?)"
+ def milliseconds_sql(column_name)
+ "(MICROSECOND(#{column_name}) / 1000)"
+ end
+
+ def second_sql(column_name)
+ "SECOND(#{column_name})"
+ end
+
+ def minute_sql(column_name)
+ "MINUTE(#{column_name})"
+ end
+
+ def hour_sql(column_name)
+ "HOUR(#{column_name})"
+ end
+
+ def day_of_week_sql(column_name)
+ "DAYOFWEEK(#{column_name})"
+ end
+
+ def day_of_month_sql(column_name)
+ "DAYOFMONTH(#{column_name})"
+ end
+
+ def day_of_year_sql(column_name)
+ "DAYOFYEAR(#{column_name})"
+ end
+
+ def week_sql(column_name)
+ "WEEK(#{column_name}, 2)"
+ end
+
+ def month_sql(column_name)
+ "MONTH(#{column_name})"
+ end
+
+ def year_sql(column_name)
+ "MONTH(#{column_name})"
+ end
+
+ # String functions
+ def char_length_sql(column_name)
+ "CHAR_LENGTH(#{column_name})"
+ end
+
+ def md5_sql(column_name)
+ "MD5(#{column_name})"
+ end
+
+ # Number functions
+ def absolute_sql(column_name)
+ "ABS(#{column_name})"
+ end
+
+ def acos_sql(column_name)
+ "ACOS(#{column_name})"
+ end
+
+ def asin_sql(column_name)
+ "ASIN(#{column_name})"
+ end
+
+ def atan_sql(column_name)
+ "ATAN(#{column_name})"
+ end
+
+ def ceil_sql(column_name)
+ "CEIL(#{column_name})"
+ end
+
+ def cos_sql(column_name)
+ "COS(#{column_name})"
+ end
+
+ def cot_sql(column_name)
+ "COT(#{column_name})"
+ end
+
+ def degrees_sql(column_name)
+ "DEGREES(#{column_name})"
+ end
+
+ def exp_sql(column_name)
+ "EXP(#{column_name})"
+ end
+
+ def floor_sql(column_name)
+ "FLOOR(#{column_name})"
+ end
+
+ def hex_sql(column_name)
+ "HEX(#{column_name})"
+ end
+
+ def ln_sql(column_name)
+ "LN(#{column_name})"
+ end
+
+ def log_sql(column_name)
+ "LOG(#{column_name})"
+ end
+
+ def log2_sql(column_name)
+ "LOG2(#{column_name})"
+ end
+
+ def log10_sql(column_name)
+ "LOG10(#{column_name})"
+ end
+
+ def octal_sql(column_name)
+ "OCT(#{column_name})"
+ end
+
+ def radians_sql(column_name)
+ "RADIANS(#{column_name})"
+ end
+
+ def round_sql(column_name)
+ "ROUND(#{column_name})"
+ end
+
+ def sign_sql(column_name)
+ "SIGN(#{column_name})"
+ end
+
+ def sin_sql(column_name)
+ "SIN(#{column_name})"
+ end
+
+ def square_root_sql(column_name)
+ "SQRT(#{column_name})"
+ end
+
+ def tan_sql(column_name)
+ "TAN(#{column_name})"
end
end
end
end
-end
+end
+
+::ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include, Searchgasm::ActiveRecord::ConnectionAdapters::MysqlAdapter)
View
148 lib/searchgasm/active_record/connection_adapters/postgresql_adapter.rb
@@ -0,0 +1,148 @@
+module Searchgasm
+ module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQLAdapter
+ # Datetime functions
+ def microseconds_sql(column_name)
+ "date_part('microseconds', #{column_name})"
+ end
+
+ def milliseconds_sql(column_name)
+ "date_part('milliseconds', #{column_name})"
+ end
+
+ def second_sql(column_name)
+ "date_part('second', #{column_name})"
+ end
+
+ def minute_sql(column_name)
+ "date_part('minute', #{column_name})"
+ end
+
+ def hour_sql(column_name)
+ "date_part('hour', #{column_name})"
+ end
+
+ def day_of_week_sql(column_name)
+ "(date_part('dow', #{column_name}) + 1)"
+ end
+
+ def day_of_month_sql(column_name)
+ "date_part('day', #{column_name})"
+ end
+
+ def day_of_year_sql(column_name)
+ "date_part('doy', #{column_name})"
+ end
+
+ def week_sql(column_name)
+ "date_part('week', #{column_name})"
+ end
+
+ def month_sql(column_name)
+ "date_part('month', #{column_name})"
+ end
+
+ def year_sql(column_name)
+ "date_part('year', #{column_name})"
+ end
+
+ # String functions
+ def char_length_sql(column_name)
+ "length(#{column_name})"
+ end
+
+ def md5_sql(column_name)
+ "md5(#{column_name})"
+ end
+
+ # Number functions
+ def absolute_sql(column_name)
+ "abs(#{column_name})"
+ end
+
+ def acos_sql(column_name)
+ "acos(#{column_name})"
+ end
+
+ def asin_sql(column_name)
+ "asin(#{column_name})"
+ end
+
+ def atan_sql(column_name)
+ "atan(#{column_name})"
+ end
+
+ def ceil_sql(column_name)
+ "ceil(#{column_name})"
+ end
+
+ def cos_sql(column_name)
+ "cos(#{column_name})"
+ end
+
+ def cot_sql(column_name)
+ "cot(#{column_name})"
+ end
+
+ def degrees_sql(column_name)
+ "degrees(#{column_name})"
+ end
+
+ def exp_sql(column_name)
+ "exp(#{column_name})"
+ end
+
+ def floor_sql(column_name)
+ "floor(#{column_name})"
+ end
+
+ def hex_sql(column_name)
+ "to_hex(#{column_name})"
+ end
+
+ def ln_sql(column_name)
+ "ln(#{column_name})"
+ end
+
+ def log_sql(column_name)
+ "log(#{column_name})"
+ end
+
+ def log2_sql(column_name)
+ "log(2.0, #{column_name})"
+ end
+
+ def log10_sql(column_name)
+ "log(10.0, #{column_name})"
+ end
+
+ def radians_sql(column_name)
+ "radians(#{column_name})"
+ end
+
+ def round_sql(column_name)
+ "round(#{column_name})"
+ end
+
+ def sign_sql(column_name)
+ "sign(#{column_name})"
+ end
+
+ def sin_sql(column_name)
+ "sin(#{column_name})"
+ end
+
+ def square_root_sql(column_name)
+ "sqrt(#{column_name})"
+ end
+
+ def tan_sql(column_name)
+ "tan(#{column_name})"
+ end
+ end
+ end
+ end
+end
+
+::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:include, Searchgasm::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
View
54 lib/searchgasm/active_record/connection_adapters/sqlite_adapter.rb
@@ -0,0 +1,54 @@
+module Searchgasm
+ module ActiveRecord
+ module ConnectionAdapters
+ module SQLiteAdapter
+ # Date functions
+ def microseconds_sql(column_name)
+ "((strftime('%f', #{column_name}) % 1) * 1000000)"
+ end
+
+ def milliseconds_sql(column_name)
+ "((strftime('%f', #{column_name}) % 1) * 1000)"
+ end
+
+ def second_sql(column_name)
+ "strftime('%S', #{column_name})"
+ end
+
+ def minute_sql(column_name)
+ "strftime('%M', #{column_name})"
+ end
+
+ def hour_sql(column_name)
+ "strftime('%H', #{column_name})"
+ end
+
+ def day_of_week_sql(column_name)
+ "strftime('%w', #{column_name})"
+ end
+
+ def day_of_month_sql(column_name)
+ "strftime('%d', #{column_name})"
+ end
+
+ def day_of_year_sql(column_name)
+ "strftime('%j', #{column_name})"
+ end
+
+ def week_sql(column_name)
+ "strftime('%W', #{column_name})"
+ end
+
+ def month_sql(column_name)
+ "strftime('%m', #{column_name})"
+ end
+
+ def year_sql(column_name)
+ "strftime('%Y', #{column_name})"
+ end
+ end
+ end
+ end
+end
+
+::ActiveRecord::ConnectionAdapters::SQLiteAdapter.send(:include, Searchgasm::ActiveRecord::ConnectionAdapters::SQLiteAdapter)
View
145 lib/searchgasm/condition/base.rb
@@ -7,71 +7,47 @@ module Condition # :nodoc:
class Base
include Shared::Utilities
- attr_accessor :column, :klass
- class_inheritable_accessor :ignore_meaningless, :type_cast_sql_type
- self.ignore_meaningless = true
+ attr_accessor :column, :column_for_type_cast, :column_sql, :klass
+ class_inheritable_accessor :handle_array_value, :ignore_meaningless_value, :value_type
+ self.ignore_meaningless_value = true
class << self
- # Name of the condition inferred from the class name
- def condition_name
- name.split("::").last.gsub(/Condition$/, "").underscore
+ # Name of the condition type inferred from the class name
+ def condition_type_name
+ name.split("::").last.underscore
end
- # 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
- # just return nil
- def name_for_column(column)
- "#{column.name}_#{condition_name}"
+ def handle_array_value?
+ handle_array_value == true
end
- # Alias methods for the column condition.
- def aliases_for_column(column)
- []
- end
-
- def ignore_meaningless? # :nodoc:
- ignore_meaningless == true
- end
-
- # 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
- # specific columns. Any condition that applies to columns should probably return nil here.
- def name_for_klass(klass)
- nil
+ def ignore_meaningless_value? # :nodoc:
+ ignore_meaningless_value == true
end
- # Alias methods for the klass condition
- def aliases_for_klass(klass)
+ # Determines what to call the condition for the model
+ #
+ # 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
+ # a model it will just return nil and Searchgasm will skip over it.
+ def condition_names_for_model
[]
end
- # A utility method for using in name_for_column. Determines if a column contains a date.
- def date_column?(column)
- [:datetime, :date, :timestamp].include?(column.type)
- end
-
- # A utility method for using in name_for_column. Determines if a column contains a date and a time.
- def datetime_column?(column)
- [:datetime, :timestamp, :time, :date].include?(column.type)
- end
-
- # A utility method for using in name_for_column. For example the keywords condition only applied to string columns, the great than condition doesnt.
- def string_column?(column)
- [:string, :text].include?(column.type)
- end
-
- # 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.
- def comparable_column?(column)
- [:integer, :float, :decimal, :datetime, :timestamp, :time, :date].include?(column.type)
- end
-
- # A utility method for using in name_for_column. Determines if a column contains a time.
- def time_column?(column)
- [:datetime, :timestamp, :time].include?(column.type)
+ # Same as condition_name_for_model, but for a model's column obj
+ def condition_names_for_column
+ [condition_type_name]
end
end
- def initialize(klass, column = nil)
+ def initialize(klass, column_obj = nil, column_type = nil, column_sql = nil)
self.klass = klass
- self.column = column.is_a?(String) ? klass.columns_hash[column] : column
+
+ if column_obj
+ self.column = column_obj.class < ::ActiveRecord::ConnectionAdapters::Column ? column_obj : klass.columns_hash[column_obj.to_s]
+ column_type ||= column.type
+ 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)
+ self.column_sql = column_sql || "#{klass.connection.quote_table_name(klass.table_name)}.#{klass.connection.quote_column_name(column.name)}"
+ end
end
# Allows nils to be meaninful values
@@ -84,63 +60,60 @@ def explicitly_set_value?
@explicitly_set_value == true
end
- # A convenience method for the name of the method for that specific column or klass
- def name
- column ? self.class.name_for_column(column) : self.class.name_for_klass(klass)
- end
-
- # A convenience method for the name of this condition
- def condition_name
- self.class.condition_name
- end
-
- # Quotes a column name properly for sql.
- def quote_column_name(column_name)
- klass.connection.quote_column_name(column_name)
- end
-
- # 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
- def quoted_column_name
- quote_column_name(column.name)
- end
-
- # Quotes a table name properly for sql
- def quote_table_name(table_name)
- klass.connection.quote_table_name(table_name)
- end
-
- # 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
- def quoted_table_name
- quote_table_name(klass.table_name)
+ def meaningless_value?
+ !explicitly_set_value? || (self.class.ignore_meaningless_value? && meaningless?(@value))
end
# You should refrain from overwriting this method, it performs various tasks before callign your to_conditions method, allowing you to keep to_conditions simple.
def sanitize(alt_value = nil) # :nodoc:
- return unless explicitly_set_value?
+ return if meaningless_value?
v = alt_value || value
- if v.is_a?(Array) && !["equals", "does_not_equal"].include?(condition_name)
+ if v.is_a?(Array) && !self.class.handle_array_value?
merge_conditions(*v.collect { |i| sanitize(i) })
else
- 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)
+ 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)
to_conditions(v)
end
end
-
+
# The value for the condition
def value
- @value.is_a?(String) ? column_for_type_cast.type_cast(@value) : @value
+ return @casted_value if @casted_value
+
+ if !column_for_type_cast || meaningless_value?
+ @casted_value = @value
+ else
+ @casted_value = @value.is_a?(String) ? column_for_type_cast.type_cast(@value) : @value
+ end
end
# Sets the value for the condition
def value=(v)
- return if self.class.ignore_meaningless? && meaningless?(v)
self.explicitly_set_value = true
+ @casted_value = nil
@value = v
end
private
- def column_for_type_cast
- @column_for_type_cast ||= self.class.type_cast_sql_type ? self.column.class.new(column.name, column.default.to_s, self.class.type_cast_sql_type.to_s, column.null) : column
+ def meaningless?(v)
+ return false if v == false
+ v.blank?
+ end
+
+ def meaningful?(v)
+ !meaningless?(v)
+ end
+
+ def quote_column_name(column_name)
+ klass.connection.quote_column_name(column_name)
+ end
+
+ def quote_table_name(table_name)
+ klass.connection.quote_table_name(table_name)
+ end
+
+ def quoted_table_name
+ quote_table_name(klass.table_name)
end
end
end
View
11 lib/searchgasm/condition/begins_with.rb
@@ -2,18 +2,13 @@ module Searchgasm
module Condition
class BeginsWith < Base
class << self
- def name_for_column(column)
- return unless string_column?(column)
- super
- end
-
- def aliases_for_column(column)
- ["#{column.name}_bw", "#{column.name}_sw", "#{column.name}_starts_with", "#{column.name}_start"]
+ def condition_names_for_column
+ super + ["bw", "sw", "starts_with", "start"]
end
end
def to_conditions(value)
- ["#{quoted_table_name}.#{quoted_column_name} LIKE ?", "#{value}%"]
+ ["#{column_sql} LIKE ?", "#{value}%"]
end
end
end
View
10 lib/searchgasm/condition/blank.rb
@@ -1,20 +1,20 @@
module Searchgasm
module Condition
class Blank < Base
- self.type_cast_sql_type = "boolean"
+ self.value_type = :boolean
class << self
- def aliases_for_column(column)
- ["#{column.name}_is_blank"]
+ def condition_names_for_column
+ super + ["is_blank"]
end
end
def to_conditions(value)
# Some databases handle null values differently, let AR handle this
if value == true
- "#{quoted_table_name}.#{quoted_column_name} is NULL or #{quoted_table_name}.#{quoted_column_name} = ''"
+ "#{column_sql} is NULL or #{column_sql} = ''"
elsif value == false
- "#{quoted_table_name}.#{quoted_column_name} is NOT NULL and #{quoted_table_name}.#{quoted_column_name} != ''"
+ "#{column_sql} is NOT NULL and #{column_sql} != ''"
end
end
end
View
20 lib/searchgasm/condition/contains.rb
@@ -1,20 +0,0 @@
-module Searchgasm
- module Condition
- class Contains < Base
- class << self
- def name_for_column(column)
- return unless string_column?(column)
- super
- end
-
- def aliases_for_column(column)
- ["#{column.name}_like", "#{column.name}_has"]
- end
- end
-
- def to_conditions(value)
- ["#{quoted_table_name}.#{quoted_column_name} LIKE ?", "%#{value}%"]
- end
- end
- end
-end
View
32 lib/searchgasm/condition/during_evening.rb
@@ -1,32 +0,0 @@
-module Searchgasm
- module Condition
- class DuringEvening < Base
- class << self
- def name_for_column(column)
- return unless time_column?(column)
- super
- end
-
- def aliases_for_column(column)
- column_names = [column.name]
- column_names << column.name.gsub(/_(at|on)$/, "") if column.name =~ /_(at|on)$/
-
- aliases = []
- column_names.each { |column_name| aliases += ["#{column_name}_in_the_evening", "#{column_name}_in_evening", "#{column_name}_evening"] }
- aliases << "#{column_names.last}_during_evening" if column_names.size > 1
- aliases
- end
- end
-
- def to_conditions(value)
- evening_start = 17
- evening_end = 22
-
- # Need to set up a funcion in each adapter for dealing with dates. Mysql uses HOUR(), sqlite uses strftime(), postgres uses date_part('hour', date). Could potentially be a pain in the ass.
- # Also, you could set up an hour = condition, and leverage that to do this.
- if value == true
- ["#{quoted_table_name}.#{quoted_column_name} >= ? AND #{quoted_table_name}.#{quoted_column_name} <= ?", value]
- end
- end
- end
-end
View
11 lib/searchgasm/condition/ends_with.rb
@@ -2,18 +2,13 @@ module Searchgasm
module Condition
class EndsWith < Base
class << self
- def name_for_column(column)
- return unless string_column?(column)
- super
- end
-
- def aliases_for_column(column)
- ["#{column.name}_ew", "#{column.name}_ends", "#{column.name}_end"]
+ def condition_names_for_column
+ super + ["ew", "ends", "end"]
end
end
def to_conditions(value)
- ["#{quoted_table_name}.#{quoted_column_name} LIKE ?", "%#{value}"]
+ ["#{column_sql} LIKE ?", "%#{value}"]
end
end
end
View
7 lib/searchgasm/condition/equals.rb
@@ -1,11 +1,12 @@
module Searchgasm
module Condition
class Equals < Base
- self.ignore_meaningless = false
+ self.handle_array_value = true
+ self.ignore_meaningless_value = false
class << self
- def aliases_for_column(column)
- ["#{column.name}", "#{column.name}_is"]
+ def condition_names_for_column
+ super + ["", "is"]
end
end
View
17 lib/searchgasm/condition/greater_than.rb
@@ -2,24 +2,13 @@ module Searchgasm
module Condition
class GreaterThan < Base
class << self
- def name_for_column(column)
- return unless comparable_column?(column)
- super
- end
-
- def aliases_for_column(column)
- column_names = [column.name]
- column_names << column.name.gsub(/_(at|on)$/, "") if datetime_column?(column) && column.name =~ /_(at|on)$/
-
- aliases = []
- column_names.each { |column_name| aliases += ["#{column_name}_gt", "#{column_name}_after"] }
- aliases << "#{column_names.last}_greater_than" if column_names.size > 1
- aliases
+ def condition_names_for_column
+ super + ["gt", "after"]
end
end
def to_conditions(value)
- ["#{quoted_table_name}.#{quoted_column_name} > ?", value]
+ ["#{column_sql} > ?", value]
end
end
end
View
17 lib/searchgasm/condition/greater_than_or_equal_to.rb
@@ -2,24 +2,13 @@ module Searchgasm
module Condition
class GreaterThanOrEqualTo < Base
class << self
- def name_for_column(column)
- return unless comparable_column?(column)
- super
- end
-
- def aliases_for_column(column)
- column_names = [column.name]
- column_names << column.name.gsub(/_(at|on)$/, "") if datetime_column?(column) && column.name =~ /_(at|on)$/
-
- aliases = []
- column_names.each { |column_name| aliases += ["#{column_name}_gte", "#{column_name}_at_least"] }
- aliases << "#{column_names.last}_greater_than_or_equal_to" if column_names.size > 1
- aliases
+ def condition_names_for_column
+ super + ["gte", "at_least", "least"]
end
end
def to_conditions(value)
- ["#{quoted_table_name}.#{quoted_column_name} >= ?", value]
+ ["#{column_sql} >= ?", value]
end
end
end
View
11 lib/searchgasm/condition/keywords.rb
@@ -4,13 +4,8 @@ class Keywords < Base
BLACKLISTED_WORDS = ('a'..'z').to_a + ["about", "an", "are", "as", "at", "be", "by", "com", "de", "en", "for", "from", "how", "in", "is", "it", "la", "of", "on", "or", "that", "the", "the", "this", "to", "und", "was", "what", "when", "where", "who", "will", "with", "www"] # from ranks.nl
class << self
- def name_for_column(column)
- return unless string_column?(column)
- super
- end
-
- def aliases_for_column(column)
- ["#{column.name}_kwords", "#{column.name}_kw"]
+ def condition_names_for_column
+ super + ["kwords", "kw"]
end
end
@@ -22,7 +17,7 @@ def to_conditions(value)
return if search_parts.blank?
search_parts.each do |search_part|
- strs << "#{quoted_table_name}.#{quoted_column_name} LIKE ?"
+ strs << "#{column_sql} LIKE ?"
subs << "%#{search_part}%"
end
View
17 lib/searchgasm/condition/less_than.rb
@@ -2,24 +2,13 @@ module Searchgasm
module Condition
class LessThan < Base
class << self
- def name_for_column(column)
- return unless comparable_column?(column)
- super
- end
-
- def aliases_for_column(column)
- column_names = [column.name]
- column_names << column.name.gsub(/_(at|on)$/, "") if datetime_column?(column) && column.name =~ /_(at|on)$/
-
- aliases = []
- column_names.each { |column_name| aliases += ["#{column_name}_lt", "#{column_name}_before"] }
- aliases << "#{column_names.last}_less_than" if column_names.size > 1
- aliases
+ def condition_names_for_column
+ super + ["lt", "before"]
end
end
def to_conditions(value)
- ["#{quoted_table_name}.#{quoted_column_name} < ?", value]
+ ["#{column_sql} < ?", value]
end
end
end
View
17 lib/searchgasm/condition/less_than_or_equal_to.rb
@@ -2,24 +2,13 @@ module Searchgasm
module Condition
class LessThanOrEqualTo < Base
class << self
- def name_for_column(column)
- return unless comparable_column?(column)
- super
- end
-
- def aliases_for_column(column)
- column_names = [column.name]
- column_names << column.name.gsub(/_(at|on)$/, "") if datetime_column?(column) && column.name =~ /_(at|on)$/
-
- aliases = []
- column_names.each { |column_name| aliases += ["#{column_name}_lte", "#{column_name}_at_most"] }
- aliases << "#{column_names.last}_less_than_or_equal_to" if column_names.size > 1
- aliases
+ def condition_names_for_column
+ super + ["lte", "at_most", "most"]
end
end
def to_conditions(value)
- ["#{quoted_table_name}.#{quoted_column_name} <= ?", value]
+ ["#{column_sql} <= ?", value]
end
end
end
View
15 lib/searchgasm/condition/like.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Condition
+ class Like < Base
+ class << self
+ def condition_names_for_column
+ super + ["contains", "has"]
+ end
+ end
+
+ def to_conditions(value)
+ ["#{column_sql} LIKE ?", "%#{value}%"]
+ end
+ end
+ end
+end
View
10 lib/searchgasm/condition/nil.rb
@@ -1,19 +1,19 @@
module Searchgasm
module Condition
class Nil < Base
- self.type_cast_sql_type = "boolean"
+ self.value_type = :boolean
class << self
- def aliases_for_column(column)
- ["#{column.name}_is_nil", "#{column.name}_is_null", "#{column.name}_null"]
+ def condition_names_for_column
+ super + ["is_nil", "is_null", "null"]
end
end
def to_conditions(value)
if value == true
- "#{quoted_table_name}.#{quoted_column_name} is NULL"
+ "#{column_sql} is NULL"
elsif value == false
- "#{quoted_table_name}.#{quoted_column_name} is NOT NULL"
+ "#{column_sql} is NOT NULL"
end
end
end
View
17 lib/searchgasm/condition/not_begin_with.rb
@@ -0,0 +1,17 @@
+module Searchgasm
+ module Condition
+ class NotBeginWith < Base
+ class << self
+ def condition_names_for_column
+ super + ["not_bw", "not_sw", "not_start_with", "not_start", "beginning_is_not", "beginning_not"]
+ end
+ end
+
+ def to_conditions(value)
+ begin_with = BeginWith.new
+ begin_with.value = value
+ being_with.to_conditions.gsub(" LIKE ", " NOT LIKE ")
+ end
+ end
+ end
+end
View
17 lib/searchgasm/condition/not_end_with.rb
@@ -0,0 +1,17 @@
+module Searchgasm
+ module Condition
+ class NotEndWith < Base
+ class << self
+ def condition_names_for_column
+ super + ["not_ew", "not_end", "end_is_not", "end_not"]
+ end
+ end
+
+ def to_conditions(value)
+ ends_with = EndsWith.new
+ ends_with.value = value
+ ends_with.to_conditions.gsub(" LIKE ", " NOT LIKE ")
+ end
+ end
+ end
+end
View
9 lib/searchgasm/condition/does_not_equal.rb → lib/searchgasm/condition/not_equal.rb
@@ -1,11 +1,12 @@
module Searchgasm
module Condition
- class DoesNotEqual < Base
- self.ignore_meaningless = false
+ class NotEqual < Base
+ self.handle_array_value = true
+ self.ignore_meaningless_value = false
class << self
- def aliases_for_column(column)
- ["#{column.name}_is_not", "#{column.name}_not"]
+ def condition_names_for_column
+ super + ["does_not_equal", "not_equal", "is_not", "not"]
end
end
View
17 lib/searchgasm/condition/not_have_keywords.rb
@@ -0,0 +1,17 @@
+module Searchgasm
+ module Condition
+ class NotHaveKeywords < Base
+ class << self
+ def condition_names_for_column
+ super + ["not_have_keywords", "not_keywords", "not_have_kw", "not_kw", "not_have_kwwords", "not_kwwords"]
+ end
+ end
+
+ def to_conditions(value)
+ keywords = Keywords.new
+ keywords.value = value
+ keywords.to_conditions.gsub(" LIKE ", " NOT LIKE ")
+ end
+ end
+ end
+end
View
17 lib/searchgasm/condition/not_like.rb
@@ -0,0 +1,17 @@
+module Searchgasm
+ module Condition
+ class NotLike < Base
+ class << self
+ def condition_names_for_column
+ super + ["not_contain", "not_have"]
+ end
+ end
+
+ def to_conditions(value)
+ like = Like.new
+ like.value = value
+ like.to_conditions.gsub(" LIKE ", " NOT LIKE ")
+ end
+ end
+ end
+end
View
9 lib/searchgasm/condition/tree.rb
@@ -2,13 +2,12 @@ module Searchgasm
module Condition
class Tree < Base # :nodoc:
class << self
- def name_for_column(column)
- nil
+ def condition_names_for_column
+ []
end
- def name_for_klass(klass)
- return unless klass.reflect_on_association(:parent) && klass.reflect_on_association(:children)
- condition_name
+ def condition_names_for_model(model)
+ [condition_type_name]
end
end
end
View
290 lib/searchgasm/conditions/base.rb
@@ -13,36 +13,51 @@ class Base
class << self
attr_accessor :added_klass_conditions, :added_column_conditions, :added_associations
- # Registers a condition as an available condition for a column or a class.
+ def column_details # :nodoc:
+ return @column_details if @column_details
+
+ @column_details = []
+
+ klass.columns.each do |column|
+ column_detail = {:column => column}
+ column_detail[:aliases] = case column.type
+ when :datetime, :time, :timestamp
+ [column.name.gsub(/_at$/, "")]
+ when :date
+ [column.name.gsub(/_at$/, "")]
+ else
+ []
+ end
+
+ @column_details << column_detail
+ end
+
+ @column_details
+ end
+
+ # 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.
#
# === Example
#
- # config/initializers/searchgasm.rb
+ # # config/initializers/searchgasm.rb
# # Actual function for MySQL databases only
# class SoundsLike < Searchgasm::Condition::Base
- # class << self
- # # I pass you the column, you tell me what you want the method to be called.
- # # If you don't want to add this condition for that column, return nil
- # # 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.
- # def name_for_column(column)
- # super
- # end
- #
- # # Only do this if you want aliases for your condition
- # def aliases_for_column(column)
- # ["#{column.name}_sounds", "#{column.name}_similar_to"]
- # end
+ # # The name of the conditions. By default its the name of the class, if you want alternate or alias conditions just add them on.
+ # # If you don't want to add aliases you don't even need to define this method
+ # def self.name_for_column(column)
+ # super
# end
#
# # You can return an array or a string. NOT a hash, because all of these conditions
# # need to eventually get merged together. The array or string can be anything you would put in
- # # the :conditions option for ActiveRecord::Base.find(). Also, for a list of methods / variables you can use check out earchgasm::Condition::Base
+ # # the :conditions option for ActiveRecord::Base.find(). Also notice the column_sql variable. This is essentail
+ # # for applying modifiers and should be used in your conditions wherever you want the column.
# def to_conditions(value)
- # ["#{quoted_table_name}.#{quoted_column_name} SOUNDS LIKE ?", value]
+ # ["#{column_sql} SOUNDS LIKE ?", value]
# end
# end
#
- # Searchgasm::Seearch::Conditions.register_condition(SoundsLikeCondition)
+ # Searchgasm::Seearch::Conditions.register_condition(SoundsLike)
def register_condition(condition_class)
raise(ArgumentError, "You can only register conditions that extend Searchgasm::Condition::Base") unless condition_class.ancestors.include?(Searchgasm::Condition::Base)
conditions << condition_class unless conditions.include?(condition_class)
@@ -53,6 +68,57 @@ def conditions
@@conditions ||= []
end
+ # Registers a modifier as an available modifier for each column.
+ #
+ # === Example
+ #
+ # # config/initializers/searchgasm.rb
+ # class Ceil < Searchgasm::Modifiers::Base
+ # # The name of the modifier. By default its the name of the class, if you want alternate or alias modifiers just add them on.
+ # # If you don't want to add aliases you don't even need to define this method
+ # def self.modifier_names
+ # super + ["round_up"]
+ # end
+ #
+ # # The name of the method in the connection adapters (see below). By default its the name of your class suffixed with "_sql".
+ # # So in this example it would be "ceil_sql". Unless you want to change that you don't need to define this method.
+ # def self.adapter_method_name
+ # super
+ # end
+ #
+ # # This is the type of value returned from the modifier. This is neccessary for typcasting values for the modifier when
+ # # applied to a column
+ # def self.return_type
+ # :integer
+ # end
+ # end
+ #
+ # Searchgasm::Seearch::Conditions.register_modifiers(Ceil)
+ #
+ # Now here's the fun part, applying this modifier to each connection adapter. Some databases call modifiers differently. If they all apply them the same you can
+ # add in the function to ActiveRecord::ConnectionAdapters::AbstractAdapter, otherwise you need to add them to each
+ # individually: ActiveRecord::ConnectionAdapters::MysqlAdapter, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter, ActiveRecord::ConnectionAdapters::SQLiteAdapter
+ #
+ # Do this by includine a model with your method. The name of your method, by default, is: #{modifier_name}_sql. So in the example above it would be "ceil_sql"
+ #
+ # module CeilAdapterMethod
+ # def ceil_sql(column_name)
+ # "CEIL(#{column_name})"
+ # end
+ # end
+ #
+ # ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include, CeilAdapterMethod)
+ # # ... include for the rest of the adapters
+ def register_modifier(modifier_class)
+ raise(ArgumentError, "You can only register conditions that extend Searchgasm::Modifiers::Base") unless modifier_class.ancestors.include?(Searchgasm::Modifiers::Base)
+ modifiers << modifier_class unless modifiers.include?(modifier_class)
+ end
+
+ # A list of available modifier classes
+ def modifiers
+ @@modifiers ||= []
+ end
+
# A list of all associations created, used for caching and performance
def association_names
@association_names ||= []
@@ -82,8 +148,6 @@ def needed?(model_class, conditions) # :nodoc:
end
def initialize(init_conditions = {})
- add_klass_conditions!
- add_column_conditions!
add_associations!
self.conditions = init_conditions
end
@@ -95,7 +159,7 @@ def initialize(init_conditions = {})
# search.conditions.any = true # will join all conditions with "or", you can also set this to "true", "1", or "yes"
# search.conditions.any = false # will join all conditions with "and"
def any=(value)
- associations.each { |association| association.any = value }
+ associations.each { |name, association| association.any = value }
@any = value
end
@@ -111,7 +175,7 @@ def any?
# A list of joins to use when searching, includes relationships
def auto_joins
j = []
- associations.each do |association|
+ associations.each do |name, association|
next if association.conditions.blank?
association_joins = association.auto_joins
j << (association_joins.blank? ? association.relationship_name.to_sym : {association.relationship_name.to_sym => association_joins})
@@ -126,16 +190,15 @@ def inspect
# Sanitizes the conditions down into conditions that ActiveRecord::Base.find can understand.
def sanitize
return @conditions if @conditions
- merge_conditions(*(objects.collect { |object| object.sanitize } << {:any => any}))
+ merge_conditions(*(objects.collect { |name, object| object.sanitize } << {:any => any}))
end
# Allows you to set the conditions via a hash.
def conditions=(value)
case value
- when Hash
+ when Hash
assert_valid_conditions(value)
remove_conditions_from_protected_assignement(value).each do |condition, condition_value|
- next if meaningless?(condition_value) # ignore blanks on mass assignments
send("#{condition}=", condition_value)
end
else
@@ -150,14 +213,14 @@ def conditions
return if objects.blank?
conditions_hash = {}
- objects.each do |object|
+ objects.each do |name, object|
if object.class < Searchgasm::Conditions::Base
relationship_conditions = object.conditions
next if relationship_conditions.blank?
conditions_hash[object.relationship_name.to_sym] = relationship_conditions
else
- next unless object.explicitly_set_value?
- conditions_hash[object.name.to_sym] = object.value
+ next if object.meaningless_value?
+ conditions_hash[name] = object.value
end
end
conditions_hash
@@ -172,62 +235,150 @@ def add_associations!
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
def #{association.name}
- if @#{association.name}.nil?
- @#{association.name} = Searchgasm::Conditions::Base.create_virtual_class(#{association.class_name}).new
- @#{association.name}.relationship_name = "#{association.name}"
- @#{association.name}.protect = protect
- objects << @#{association.name}
+ if objects[:#{association.name}].nil?
+ objects[:#{association.name}] = Searchgasm::Conditions::Base.create_virtual_class(#{association.class_name}).new
+ objects[:#{association.name}].relationship_name = "#{association.name}"
+ objects[:#{association.name}].protect = protect
end
- @#{association.name}
+ objects[:#{association.name}]
end
def #{association.name}=(conditions); @conditions = nil; #{association.name}.conditions = conditions; end
- def reset_#{association.name}!; objects.delete(#{association.name}); @#{association.name} = nil; end
+ def reset_#{association.name}!; objects.delete(:#{association.name}); end
end_eval
end
self.class.added_associations = true
end
- def add_column_conditions!
- return true if self.class.added_column_conditions
+ def extract_column_and_condition_from_method_name(name)
+ name_parts = name.gsub("=", "").split("_")
- klass.columns.each do |column|
- self.class.conditions.each do |condition_klass|
- name = condition_klass.name_for_column(column)
- next if name.blank?
- add_condition!(condition_klass, name, column)
- condition_klass.aliases_for_column(column).each { |alias_name| add_condition_alias!(alias_name, name) }
+ condition_parts = []
+ column = nil
+ while column.nil? && name_parts.size > 0
+ possible_column_name = name_parts.join("_")
+
+ self.class.column_details.each do |column_detail|
+ if column_detail[:column].name == possible_column_name || column_detail[:aliases].include?(possible_column_name)
+ column = column_detail
+ break
+ end
end
+
+ condition_parts << name_parts.pop if !column
end
- self.class.added_column_conditions = true
+ return if column.nil?
+
+ condition_name = condition_parts.reverse.join("_")
+ condition = nil
+
+ # Find the real condition
+ self.class.conditions.each do |condition_klass|
+ if condition_klass.condition_names_for_column.include?(condition_name)
+ condition = condition_klass
+ break
+ end
+ end
+
+ [column, condition]
end
- def add_condition!(condition, name, column = nil)
+ def breakdown_method_name(name)
+ column_detail, condition_klass = extract_column_and_condition_from_method_name(name)
+ if !column_detail.nil? && !condition_klass.nil?
+ # There were no modifiers
+ return [[], column_detail, condition_klass]
+ else
+ # There might be modifiers
+ name_parts = name.split("_of_")
+ column_detail, condition_klass = extract_column_and_condition_from_method_name(name_parts.pop)
+ if !column_detail.nil? && !condition_klass.nil?
+ # There were modifiers, lets get their real names
+ modifier_klasses = []
+ name_parts.each do |modifier_name|
+ size_before = modifier_klasses.size
+ self.class.modifiers.each do |modifier_klass|
+ if modifier_klass.modifier_names.include?(modifier_name)
+ modifier_klasses << modifier_klass
+ break
+ end
+ end
+ return if modifier_klasses.size == size_before # there was an invalid modifer, return nil for everything and let it act as a nomethoderror
+ end
+
+ return [modifier_klasses, column_detail, condition_klass]
+ end
+ end
+
+ nil
+ end
+
+ def build_method_name(modifier_klasses, column_name, condition_name)
+ modifier_name_parts = []
+ modifier_klasses.each { |modifier_klass| modifier_name_parts << modifier_klass.modifier_names.first }
+ method_name_parts = []
+ method_name_parts << modifier_name_parts.join("_of_") unless modifier_name_parts.blank?
+ method_name_parts << column_name
+ method_name_parts << condition_name
+ method_name_parts.join("_")
+ end
+
+ def method_missing(name, *args, &block)
+ modifier_klasses, column_detail, condition_klass = breakdown_method_name(name.to_s)
+ if !column_detail.nil? && !condition_klass.nil?
+ method_name = build_method_name(modifier_klasses, column_detail[:column].name, condition_klass.condition_names_for_column.first)
+ column_type = column_sql = nil
+ if !modifier_klasses.blank?
+ # Find the column type
+ column_type = modifier_klasses.first.return_type
+
+ # Build the column sql
+ column_sql = "#{klass.connection.quote_table_name(klass.table_name)}.#{klass.connection.quote_column_name(column_detail[:column].name)}"
+ modifier_klasses.each do |modifier_klass|
+ next unless klass.connection.respond_to?(modifier_klass.adapter_method_name)
+ column_sql = klass.connection.send(modifier_klass.adapter_method_name, column_sql)
+ end
+ end
+
+ add_condition!(condition_klass, method_name, column_detail[:column], column_type, column_sql)
+
+ ([column_detail[:column].name] + column_detail[:aliases]).each do |column_name|
+ condition_klass.condition_names_for_column.each do |condition_name|
+ alias_method_name = build_method_name(modifier_klasses, column_name, condition_name)
+ add_condition_alias!(alias_method_name, method_name) unless alias_method_name == method_name
+ end
+ end
+
+ send(method_name + (name.to_s =~ /=$/ ? "=" : ""), *args, &block)
+ else
+ super
+ end
+ end
+
+ def add_condition!(condition, name, column = nil, column_type = nil, column_sql = nil)
self.class.condition_names << name
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
def #{name}_object
- if @#{name}.nil?
- @#{name} = #{condition.name}.new(klass#{column.nil? ? "" : ", \"#{column.name}\""})
- objects << @#{name}
+ if objects[:#{name}].nil?
+ objects[:#{name}] = #{condition.name}.new(klass, #{column.blank? ? "nil" : "klass.columns_hash['#{column.name}']"}, #{column_type.blank? ? "nil" : "\"#{column_type}\""}, #{column_sql.blank? ? "nil" : "\"#{column_sql.gsub('"', '\"')}\""})
end
- @#{name}
+ objects[:#{name}]
end
def #{name}; #{name}_object.value; end
def #{name}=(value)
- if meaningless?(value) && #{name}_object.class.ignore_meaningless?
- reset_#{name}!
- else
- @conditions = nil
- #{name}_object.value = value
- end
+ @conditions = nil
+
+ #{name}_object.value = value
+ reset_#{name}! if #{name}_object.meaningless_value?
+ value
end
- def reset_#{name}!; objects.delete(#{name}_object); @#{name} = nil; end
+ def reset_#{name}!; objects.delete(:#{name}); end
end_eval
end
@@ -235,38 +386,33 @@ def add_condition_alias!(alias_name, name)
self.class.condition_names << alias_name
self.class.class_eval do
+ alias_method "#{alias_name}_object", "#{name}_object"
alias_method alias_name, name
alias_method "#{alias_name}=", "#{name}="
+ alias_method "reset_#{alias_name}!", "reset_#{name}!"
end
end
- def add_klass_conditions!
- return true if self.class.added_klass_conditions
-
- self.class.conditions.each do |condition|
- name = condition.name_for_klass(klass)
- next if name.blank?
- add_condition!(condition, name)
- condition.aliases_for_klass(klass).each { |alias_name| add_condition_alias!(alias_name, name) }
- end
-
- self.class.added_klass_conditions = true
- end
-
def assert_valid_conditions(conditions)
- conditions.stringify_keys.fast_assert_valid_keys(self.class.condition_names + self.class.association_names + ["any"])
+ conditions.each do |condition, value|
+ raise(ArgumentError, "The #{condition} condition is not a valid condition") if !(self.class.condition_names + self.class.association_names + ["any"]).include?(condition.to_s) && respond_to?(condition)
+ end
end
def associations
- objects.select { |object| object.class < ::Searchgasm::Conditions::Base }
+ associations = {}
+ objects.each do |name, object|
+ associations[name] = object if object.class < ::Searchgasm::Conditions::Base
+ end
+ associations
end
def objects
- @objects ||= []
+ @objects ||= {}
end
def reset_objects!
- objects.each { |object| object.class < ::Searchgasm::Conditions::Base ? eval("@#{object.relationship_name} = nil") : eval("@#{object.name} = nil") }
+ objects.each { |name, object| object.class < ::Searchgasm::Conditions::Base ? eval("@#{object.relationship_name} = nil") : eval("@#{name} = nil") }
objects.clear
end
View
15 lib/searchgasm/modifiers/absolute.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class Absolute < Base
+ class << self
+ def modifier_names
+ super + ["abs"]
+ end
+
+ def return_type
+ :integer
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/acos.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Modifiers
+ class Acos < Base
+ class << self
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/asin.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Modifiers
+ class Asin < Base
+ class << self
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/atan.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Modifiers
+ class Atan < Base
+ class << self
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
27 lib/searchgasm/modifiers/base.rb
@@ -0,0 +1,27 @@
+module Searchgasm
+ module Modifiers
+ class Base
+ class << self
+ # A convenience method for the name of this modifier
+ def modifier_name
+ name.split("::").last.underscore
+ end
+
+ # The various names for the modifier. The first in the array is the "main" name, the rest are just aliases to the "main" name
+ def modifier_names
+ [modifier_name]
+ end
+
+ # The method in the connection adapter that creates the SQL for the modifier
+ def adapter_method_name
+ "#{modifier_name}_sql"
+ end
+
+ # The type of value returned from the SQL. A class the extends this MUST define this method.
+ def return_type
+ raise "You did not specify a return type for the #{modifier_name} modifier. Please specify if it is an :integer, :string, etc."
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/ceil.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class Ceil < Base
+ class << self
+ def modifier_names
+ super + ["round_up"]
+ end
+
+ def return_type
+ :integer
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/char_length.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class CharLength < Base
+ class << self
+ def modifier_names
+ super + ["length"]
+ end
+
+ def return_type
+ :integer
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/cos.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class Cos < Base
+ class << self
+ def modifier_names
+ super + ["cosine"]
+ end
+
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/cot.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class Cot < Base
+ class << self
+ def modifier_names
+ super + ["cotangent"]
+ end
+
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/day_of_month.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class DayOfMonth < Base
+ class << self
+ def modifier_names
+ super + ["dom"]
+ end
+
+ def return_type
+ :integer
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/day_of_week.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class DayOfWeek < Base
+ class << self
+ def modifier_names
+ super + ["dow"]
+ end
+
+ def return_type
+ :integer
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/day_of_year.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class DayOfYear < Base
+ class << self
+ def modifier_names
+ super + ["doy"]
+ end
+
+ def return_type
+ :integer
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/degrees.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Modifiers
+ class Degrees < Base
+ class << self
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/exp.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class Exp < Base
+ class << self
+ def modifier_names
+ super + ["exponential"]
+ end
+
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/floor.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class Floor < Base
+ class << self
+ def modifier_names
+ super + ["round_down"]
+ end
+
+ def return_type
+ :integer
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/hex.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Modifiers
+ class Hex < Base
+ class << self
+ def return_type
+ :string
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/hour.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Modifiers
+ class Hour < Base
+ class << self
+ def return_type
+ :integer
+ end
+ end
+ end
+ end
+end
View
15 lib/searchgasm/modifiers/log.rb
@@ -0,0 +1,15 @@
+module Searchgasm
+ module Modifiers
+ class Log < Base
+ class << self
+ def modifier_names
+ super + ["ln"]
+ end
+
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/log10.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Modifiers
+ class Log10 < Base
+ class << self
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/log2.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Modifiers
+ class Log2 < Base
+ class << self
+ def return_type
+ :float
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/md5.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Modifiers
+ class Md5 < Base
+ class << self
+ def return_type
+ :string
+ end
+ end
+ end
+ end
+end
View
11 lib/searchgasm/modifiers/microseconds.rb
@@ -0,0 +1,11 @@