Permalink
Browse files

Released v1.3.0, added modifiers

  • Loading branch information...
1 parent 20fe2a8 commit 015f74898e79281e6b270dd053d6cda4910e7a1b @binarylogic binarylogic committed Oct 2, 2008
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
@@ -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
@@ -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
@@ -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,90 +239,126 @@ 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
I'm a big fan of understanding what I'm using, so here's a quick explanation: The design behind this plugin is pretty simple and I had 1 main rule when developing this:
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
@@ -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.
Oops, something went wrong.

0 comments on commit 015f748

Please sign in to comment.