Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added in searchgasm gem

  • Loading branch information...
commit 379b5546892197e5fe247732d78f3eb38d8f156f 1 parent afb04fd
@binarylogic authored
Showing with 3,732 additions and 5 deletions.
  1. +1 −0  .gitignore
  2. +3 −0  Capfile
  3. +1 −1  app/views/layouts/application.html.erb
  4. +0 −1  app/views/users/index.html.erb
  5. +0 −2  config/environment.rb
  6. +4 −1 config/environments/development.rb
  7. +15 −0 vendor/gems/searchgasm-0.9.7/CHANGELOG
  8. +20 −0 vendor/gems/searchgasm-0.9.7/MIT-LICENSE
  9. +53 −0 vendor/gems/searchgasm-0.9.7/Manifest
  10. +334 −0 vendor/gems/searchgasm-0.9.7/README.rdoc
  11. +17 −0 vendor/gems/searchgasm-0.9.7/Rakefile
  12. +4 −0 vendor/gems/searchgasm-0.9.7/examples/README.rdoc
  13. +1 −0  vendor/gems/searchgasm-0.9.7/init.rb
  14. +67 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm.rb
  15. +64 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/active_record/associations.rb
  16. +103 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/active_record/base.rb
  17. +127 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/base.rb
  18. +20 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/begins_with.rb
  19. +11 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/child_of.rb
  20. +20 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/contains.rb
  21. +24 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/descendant_of.rb
  22. +28 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/does_not_equal.rb
  23. +20 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/ends_with.rb
  24. +20 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/equals.rb
  25. +25 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/greater_than.rb
  26. +20 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/greater_than_or_equal_to.rb
  27. +13 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/inclusive_descendant_of.rb
  28. +33 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/keywords.rb
  29. +25 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/less_than.rb
  30. +20 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/less_than_or_equal_to.rb
  31. +16 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/sibling_of.rb
  32. +16 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/tree.rb
  33. +221 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/conditions/base.rb
  34. +30 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/conditions/protection.rb
  35. +137 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/config.rb
  36. +159 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/helpers/form_helper.rb
  37. +178 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/helpers/search_helper.rb
  38. +125 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/helpers/utilities_helper.rb
  39. +78 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/search/base.rb
  40. +48 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/search/conditions.rb
  41. +149 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/search/ordering.rb
  42. +69 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/search/pagination.rb
  43. +61 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/search/protection.rb
  44. +30 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/utilities.rb
  45. +79 −0 vendor/gems/searchgasm-0.9.7/lib/searchgasm/version.rb
  46. +183 −0 vendor/gems/searchgasm-0.9.7/searchgasm.gemspec
  47. +15 −0 vendor/gems/searchgasm-0.9.7/test/fixtures/accounts.yml
  48. +14 −0 vendor/gems/searchgasm-0.9.7/test/fixtures/orders.yml
  49. +27 −0 vendor/gems/searchgasm-0.9.7/test/fixtures/users.yml
  50. +98 −0 vendor/gems/searchgasm-0.9.7/test/libs/acts_as_tree.rb
  51. +14 −0 vendor/gems/searchgasm-0.9.7/test/libs/rexml_fix.rb
  52. +38 −0 vendor/gems/searchgasm-0.9.7/test/test_active_record_associations.rb
  53. +83 −0 vendor/gems/searchgasm-0.9.7/test/test_active_record_base.rb
  54. +143 −0 vendor/gems/searchgasm-0.9.7/test/test_condition.rb
  55. +178 −0 vendor/gems/searchgasm-0.9.7/test/test_conditions_base.rb
  56. +79 −0 vendor/gems/searchgasm-0.9.7/test/test_helper.rb
  57. +189 −0 vendor/gems/searchgasm-0.9.7/test/test_search_base.rb
  58. +91 −0 vendor/gems/searchgasm-0.9.7/test/test_search_ordering.rb
  59. +56 −0 vendor/gems/searchgasm-0.9.7/test/test_search_pagination.rb
  60. +35 −0 vendor/gems/searchgasm-0.9.7/test/test_search_protection.rb
View
1  .gitignore
@@ -3,3 +3,4 @@ config/database.yml
tmp/*
log/*
.DS_Store
+config/deploy.rb
View
3  Capfile
@@ -0,0 +1,3 @@
+load 'deploy' if respond_to?(:namespace) # cap2 differentiator
+Dir['vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
+load 'config/deploy'
View
2  app/views/layouts/application.html.erb
@@ -8,7 +8,7 @@
<h1 style="font-size: 1.5em">Searchgasm example</h1>
This is a simple / plain example of how to implement Searchgasm into a rails application. As you can see it supports searching, ordering, and pagination all in one plugin. Here are some helpful links:<br />
<br />
- <strong><a href="http://github.com/binarylogic/searchgasm_example">Source code for this example</a>&nbsp;&nbsp;|&nbsp;&nbsp;Tutorial on this example&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://github.com/binarylogic/searchgasm">Searchgasm source / repository</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://searchgasm.rubyforge.org">Searchgasm documentation</a></strong><br />
+ <strong><a href="http://github.com/binarylogic/searchgasm_example.binarylogic.com">Source code for this example</a>&nbsp;&nbsp;|&nbsp;&nbsp;Tutorial on this example&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://github.com/binarylogic/searchgasm">Searchgasm source / repository</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="http://searchgasm.rubyforge.org">Searchgasm documentation</a></strong><br />
<br />
<% if controller_name == "users" %>
Non AJAX Example
View
1  app/views/users/index.html.erb
@@ -126,7 +126,6 @@ For more information please see Searchgasmm::Helpers::FormHelper
</tr>
<% end %>
</table>
-
<br />
<br />
<!--
View
2  config/environment.rb
@@ -26,8 +26,6 @@
# config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
# config.gem "aws-s3", :lib => "aws/s3"
#config.gem :searchgasm
- config.plugin_paths += ["../../Ruby Libs"]
- config.plugins = [:calendar_date_select, :searchgasm]
# Only load the plugins named here, in the order given. By default, all plugins
# in vendor/plugins are loaded in alphabetical order.
View
5 config/environments/development.rb
@@ -14,4 +14,7 @@
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
-config.action_mailer.raise_delivery_errors = false
+config.action_mailer.raise_delivery_errors = false
+
+config.plugin_paths += ["../../Ruby Libs"]
+config.plugins = [:calendar_date_select, :searchgasm]
View
15 vendor/gems/searchgasm-0.9.7/CHANGELOG
@@ -0,0 +1,15 @@
+v0.9.7. Complete class restructure, much more organized, added in documentation, added in helpers for using searchgasm in a rails app, updated readme with link to documentation as well as a live example, some bug fixes, more tests
+
+v0.9.6. Fix bug when instantiating with nil options
+
+v0.9.5. Enhanced searching with conditions only. Updated read me to include example on adding your own conditions.
+
+v0.9.4. Cleaned up search methods, removed reset! method for base and conditions.
+
+v0.9.3. Changed structure of conditions to have their own class. Making it easier to add your own conditions.
+
+v0.9.2. Enhanced protection
+
+v0.9.1. Added aliases for datetime, date, time, and timestamp attrs. You could call created_at_after, mow you can also call created_after.
+
+v0.9.0. First release
View
20 vendor/gems/searchgasm-0.9.7/MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2007 Ben Johnson of Binary Logic (binarylogic.com)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
53 vendor/gems/searchgasm-0.9.7/Manifest
@@ -0,0 +1,53 @@
+CHANGELOG
+examples/README.rdoc
+init.rb
+lib/searchgasm/active_record/associations.rb
+lib/searchgasm/active_record/base.rb
+lib/searchgasm/condition/base.rb
+lib/searchgasm/condition/begins_with.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/ends_with.rb
+lib/searchgasm/condition/equals.rb
+lib/searchgasm/condition/greater_than.rb
+lib/searchgasm/condition/greater_than_or_equal_to.rb
+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/sibling_of.rb
+lib/searchgasm/condition/tree.rb
+lib/searchgasm/conditions/base.rb
+lib/searchgasm/conditions/protection.rb
+lib/searchgasm/config.rb
+lib/searchgasm/helpers/form_helper.rb
+lib/searchgasm/helpers/search_helper.rb
+lib/searchgasm/helpers/utilities_helper.rb
+lib/searchgasm/search/base.rb
+lib/searchgasm/search/conditions.rb
+lib/searchgasm/search/ordering.rb
+lib/searchgasm/search/pagination.rb
+lib/searchgasm/search/protection.rb
+lib/searchgasm/utilities.rb
+lib/searchgasm/version.rb
+lib/searchgasm.rb
+Manifest
+MIT-LICENSE
+Rakefile
+README.rdoc
+test/fixtures/accounts.yml
+test/fixtures/orders.yml
+test/fixtures/users.yml
+test/libs/acts_as_tree.rb
+test/libs/rexml_fix.rb
+test/test_active_record_associations.rb
+test/test_active_record_base.rb
+test/test_condition.rb
+test/test_conditions_base.rb
+test/test_helper.rb
+test/test_search_base.rb
+test/test_search_ordering.rb
+test/test_search_pagination.rb
+test/test_search_protection.rb
View
334 vendor/gems/searchgasm-0.9.7/README.rdoc
@@ -0,0 +1,334 @@
+= Searchgasm
+
+Searchgasm is orgasmic. Maybe not orgasmic, but you will get aroused. So go grab a towel and let's dive in.
+
+<b>Searchgasm's inspiration comes right from ActiveRecord. ActiveRecord lets you create objects that represent a record in the database, so why can't you create objects that represent searching the database? Now you can!</b>
+
+== 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. The search object "sanitizes" down into the options passed into ActiveRecord::Base.find(). It serves as a transparent filter between you and ActiveRecord::Base.find(). This filter provides "enhancements" that get translated into options that ActiveRecord::Base.find() can understand. 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 lets ActiveRecord do all of the work. Between that and the extensive tests, this is a solid and fast plugin.
+
+== Quicklinks
+
+* <b>Documentation:</b> http://searchgasm.rubyforge.org
+* <b>Live rails example:</b> http://searchgasm_example.binarylogic.com
+
+== Install and use
+
+ sudo gem install searchgasm
+
+For rails > 2.1
+
+ # environment.rb
+ config.gem "searchgasm"
+
+For rails < 2.1
+
+ # environment.rb
+ require "searchgasm"
+
+Or as a plugin
+
+ script/plugin install git://github.com/binarylogic/searchgasm.git
+
+Now try out some of the examples below:
+
+<b>For all examples, let's assume the following relationships: User => Orders => Line Items</b>
+
+== The beauty of searchgasm
+
+Using Searchgasm in rails is the best part, because rails has all kinds of nifty methods to make dealing with ActiveRecord objects quick and easy, especially with forms. So let's take advantage of them! That's the idea behind this plugin. Searchgasm is searching, ordering, and pagination all rolled into one simple plugin. Take all of that pagination and searching cruft out of your models and let Searchgasm handle it. Check it out:
+
+ # app/controllers/users_controller.rb
+ def index
+ @search = User.new_search(params[:search])
+ @users, @users_count = @search.all, @search.count
+ end
+
+Now your view. Notice you can use your search object <b>just like</b> an ActiveRecord object.
+
+ # app/views/users/index.html.haml
+ - form_for @search do |f|
+ - f.fields_for @search.conditions do |users|
+ = users.text_field :first_name_contains
+ = users.calendar_date_select :created_after # nice rails plugin for replacing date_select
+ - users.fields_for users.object.orders do |orders|
+ = orders.select :total_gt, (1..100)
+ = f.submit "Search"
+
+ %table
+ %tr
+ %th= order_by :first_name
+ %th= order_by :last_name
+ %th= order_by :email
+ - @users.each do |user|
+ %tr
+ %td= user.first_name
+ %td= user.last_name
+ %td= user.email
+
+ Per page:
+ = per_page
+ Page:
+ = page
+
+Nice and simple. 2 lines in your controller, no "cruft" in your models, pagination as simple as calling the "page" and "per_page" helpers, ordering as simple as calling "order_by", and all searching conditions in 1 spot: your form. For documentation on helpers see Searchgasm::Helpers::FormHelper and Searchgasm::Helpers::SearchHelper.
+
+<b>See this example live: http://searchgasm_example.binarylogic.com</b>
+
+== Simple Searching Example
+
+ User.all(
+ :conditions => {
+ :first_name_contains => "Ben", # first_name like '%Ben%'
+ :email_ends_with => "binarylogic.com" # email like '%binarylogic.com'
+ },
+ :per_page => 20 # limit 20
+ :page => 3 # offset 40, which starts us on page 3
+ )
+
+Instead of using the "all" method you could use any search method: first, find(:all), find(:first), count, sum, average, etc, just like ActiveRecord
+
+== Exhaustive Example w/ Object Based Searching (great for form_for or fields_for)
+
+ # Instantiate
+ @search = User.new_search(
+ :conditions => {
+ :first_name_contains => "Ben",
+ :age_gt => 18,
+ :orders => {:total_lt => 100}
+ },
+ :per_page => 20,
+ :page => 2,
+ :order_by => {:orders => :total},
+ :order_as => "DESC"
+ )
+
+ # Set local conditions
+ @search.conditions.email_ends_with = "binarylogic.com"
+
+ # Set conditions on relationships
+ @search.conditions.oders.line_items.created_after = Time.now # can traverse through all relationships
+
+ # Set options
+ @search.per_page = 50 # overrides the 20 set above
+ @search.order_by = [:first_name, {:user_group => :name}] # order by first name and then by the user group's name it belongs to
+ @search.order_as = "ASC"
+
+ # Set ANY of the ActiveRecord options
+ @search.group = "last_name"
+ @search.readonly = true
+ # ... see ActiveRecord documentation
+
+ # Return results just like ActiveRecord
+ @search.all
+ @search.first
+
+Take the @search object and pass it right into form\_for or fields\_for (see above).
+
+== Calculations
+
+Using the object from above:
+
+ @search.average('id')
+ @search.count
+ @search.maximum('id')
+ @search.minimum('id')
+ @search.sum('id')
+ @search.calculate(:sum, 'id')
+ # ...any of the above calculations, see ActiveRecord documentation on calculations
+
+Or do it from your model:
+
+ User.count(:conditions => {:first_name_contains => "Ben"})
+ User.sum('id', :conditions => {:first_name_contains => "Ben"})
+ # ... all other calcualtions, etc.
+
+== Different ways to search, take your pick
+
+Any of the options used in the above example can be used in these, but for the sake of brevity I am only using a few:
+
+ User.all(:conditions => {:age_gt => 18}, :per_page => 20)
+
+ User.first(:conditions => {:age_gt => 18}, :per_page => 20)
+
+ User.find(:all, :conditions => {::age_gt => 18}, :per_page => 20)
+
+ User.find(:first, :conditions => {::age_gt => 18}, :per_page => 20)
+
+ search = User.new_search(:conditions => {:age_gt => 18}) # build_search is an alias
+ search.conditions.first_name_contains = "Ben"
+ search.per_page = 20
+ search.all
+
+If you want to use Searchgasm directly:
+
+ search = Searchgasm::Search::Base.new(User, :conditions => {:age_gt => 18})
+ search.conditions.first_name_contains = "Ben"
+ search.per_page = 20
+ search.all
+
+== Search with conditions only
+
+ conditions = User.new_conditions(:age_gt => 18)
+ conditions.first_name_contains = "Ben"
+ conditions.all
+ # ... all operations above are available
+
+Pass a conditions object right into ActiveRecord:
+
+ User.all(:conditions => conditions)
+
+Again, if you want to use Searchgasm directly:
+
+ conditions = Searchgasm::Conditions::Base.new(User, :age_gt => 18)
+ conditions.first_name_contains = "Ben"
+ conditions.all
+
+Now pass the conditions object right into form\_for or fields\_for (see above for example).
+
+== Scoped searching
+
+ @current_user.orders.find(:all, :conditions => {:total_lte => 500})
+ @current_user.orders.count(:conditions => {:total_lte => 500})
+ @current_user.orders.sum('total', :conditions => {:total_lte => 500})
+
+ search = @current_user.orders.build_search('total', :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})
+
+
+== Available anywhere (relationships & scopes)
+
+Not only can you use searchgasm when searching, but you can use it when setting up relationships or scopes. Anywhere you specify conditions in ActiveRecord.
+
+ class User < ActiveRecord::Base
+ has_many :expensive_pending_orders, :conditions => {:total_greater_than => 1_000_000, :state => :pending}, :per_page => 20
+ named_scope :sexy, :conditions => {:first_name => "Ben", email_ends_with => "binarylogic.com"}, :per_page => 20
+ end
+
+== Always use protection...against SQL injections
+
+If there is one thing we all know, it's to always use protection against SQL injections. That's why searchgasm protects you by default. The new\_search and new\_conditions methods are protected by default. This means that various checks are done to ensure it is not possible to perform any type of SQL injection. But this also limits how you can search, meaning you can't write raw SQL. If you want to be daring and search without protection, all that you have to do is add ! to the end of the methods: new\_search! and new\_conditions!.
+
+=== Protected from SQL injections
+
+ search = Account.new_search(params[:search])
+ conditions = Account.new_conditions(params[:conditions])
+
+=== *NOT* protected from SQL injections
+
+ accounts = Account.find(params[:search])
+ accounts = Account.all(params[:search])
+ account = Account.first(params[:search])
+ search = Account.new_search!(params[:search])
+ conditions = Account.new_conditions!(params[:conditions])
+
+Lesson learned: use new\_search and new\_conditions 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:
+
+ all columns
+ => :equals, :does_not_equal
+
+ :string, :text
+ => :begins_with, :contains, :keywords, :ends_with
+
+ :integer, :float, :decimal,:datetime, :timestamp, :time, :date
+ => :greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to
+
+ tree data structures (see above "searching trees")
+ => :child_of, :sibling_of, :descendant_of, :inclusive_descendant_of
+
+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":
+
+ :equals; => :is
+ :does_not_equal => :is_not, :not
+ :begins_with => :starts_with, :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
+
+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.
+
+=== Enhanced searching and blacklisted words
+
+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.
+
+=== Roll your own conditions
+
+I didn't include this function because its MySQL specific, and it's probably rarely used, but MySQL supports a "SOUNDS LIKE" function.
+
+I want to use it, so let's add it:
+
+ # 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". 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()
+ def to_conditions(value)
+ ["#{quoted_table_name}.#{quoted_column_name} SOUNDS LIKE ?", value]
+ end
+ end
+
+ Searchgasm::Conditions::Base.register_condition(SoundsLike)
+
+Now test it out:
+
+ 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"
+
+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
+
+== Reporting problems / bugs
+
+http://binarylogic.lighthouseapp.com/projects/16601-searchgasm
+
+== Credits
+
+Author: {Ben Johnson}[http://github.com/binarylogic] of {Binary Logic}[http://www.binarylogic.com]
+
+Credit to {Zack Ham}[http://github.com/zackham] and {Robert Malko}[http://github.com/malkomalko/] for helping with feature suggestions.
+
+
+Copyright (c) 2008 {Ben Johnson}[http://github.com/binarylogic] of {Binary Logic}[http://www.binarylogic.com], released under the MIT license
View
17 vendor/gems/searchgasm-0.9.7/Rakefile
@@ -0,0 +1,17 @@
+require 'rubygems'
+require 'echoe'
+
+require File.dirname(__FILE__) << "/lib/searchgasm/version"
+
+Echoe.new 'searchgasm' do |p|
+ p.version = Searchgasm::Version::STRING
+ p.author = "Ben Johnson of Binary Logic"
+ p.email = 'bjohnson@binarylogic.com'
+ p.project = 'searchgasm'
+ p.summary = "Orgasmic ActiveRecord searching"
+ p.description = "Makes ActiveRecord searching easier, robust, and powerful. Automatic conditions, pagination support, object based searching, and more."
+ p.url = "http://github.com/binarylogic/searchgasm"
+ p.dependencies = %w(activerecord activesupport)
+ p.include_rakefile = true
+end
+
View
4 vendor/gems/searchgasm-0.9.7/examples/README.rdoc
@@ -0,0 +1,4 @@
+=== Live example
+
+* Please see the live example: http://searchgasm_example.binarylogic.com
+* Source / Github project for the example: http://github.com/binarylogic/searchgasm_example.binarylogic.com
View
1  vendor/gems/searchgasm-0.9.7/init.rb
@@ -0,0 +1 @@
+require File.dirname(__FILE__) << "/lib/searchgasm"
View
67 vendor/gems/searchgasm-0.9.7/lib/searchgasm.rb
@@ -0,0 +1,67 @@
+require "active_record"
+require "active_support"
+
+# Utilties
+require "searchgasm/version"
+require "searchgasm/config"
+require "searchgasm/utilities"
+
+# ActiveRecord
+require "searchgasm/active_record/base"
+require "searchgasm/active_record/associations"
+
+# Search
+require "searchgasm/search/ordering"
+require "searchgasm/search/pagination"
+require "searchgasm/search/conditions"
+require "searchgasm/search/base"
+require "searchgasm/search/protection"
+
+# Conditions
+require "searchgasm/conditions/protection"
+require "searchgasm/conditions/base"
+
+# Condition
+require "searchgasm/condition/base"
+require "searchgasm/condition/begins_with"
+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/tree"
+require "searchgasm/condition/child_of"
+require "searchgasm/condition/descendant_of"
+require "searchgasm/condition/inclusive_descendant_of"
+require "searchgasm/condition/sibling_of"
+
+# Helpers
+require "searchgasm/helpers/utilities_helper"
+require "searchgasm/helpers/form_helper"
+require "searchgasm/helpers/search_helper"
+
+# Lets do it!
+module Searchgasm
+ module Search
+ class Base
+ include Conditions
+ include Ordering
+ include Pagination
+ include Protection
+ end
+ end
+
+ module Conditions
+ class Base
+ include Protection
+ end
+
+ [:begins_with, :child_of, :contains, :descendant_of, :does_not_equal, :ends_with, :equals, :greater_than, :greater_than_or_equal_to, :inclusive_descendant_of, :keywords, :less_than, :less_than_or_equal_to, :sibling_of].each do |condition|
+ Base.register_condition("Searchgasm::Condition::#{condition.to_s.camelize}".constantize)
+ end
+ end
+end
View
64 vendor/gems/searchgasm-0.9.7/lib/searchgasm/active_record/associations.rb
@@ -0,0 +1,64 @@
+module Searchgasm
+ module ActiveRecord
+ module Associations
+ module AssociationCollection
+ def find_with_searchgasm(*args)
+ options = args.extract_options!
+ args << sanitize_options_with_searchgasm(options)
+ find_without_searchgasm(*args)
+ end
+
+ def build_conditions(options = {}, &block)
+ conditions = @reflection.klass.build_conditions(options, &block)
+ conditions.scope = scope(:find)[:conditions]
+ conditions
+ end
+
+ def build_conditions!(options = {}, &block)
+ conditions = @reflection.klass.build_conditions!(options, &block)
+ conditions.scope = scope(:find)[:conditions]
+ conditions
+ end
+
+ def build_search(options = {}, &block)
+ conditions = @reflection.klass.build_search(options, &block)
+ conditions.scope = scope(:find)[:conditions]
+ conditions
+ end
+
+ def build_search!(options = {}, &block)
+ conditions = @reflection.klass.build_search!(options, &block)
+ conditions.scope = scope(:find)[:conditions]
+ conditions
+ end
+ end
+
+ module HasManyAssociation
+ def count_with_searchgasm(*args)
+ column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
+ count_without_searchgasm(column_name, sanitize_options_with_searchgasm(options))
+ end
+ end
+ end
+ end
+end
+
+ActiveRecord::Associations::AssociationCollection.send(:include, Searchgasm::ActiveRecord::Associations::AssociationCollection)
+
+module ActiveRecord
+ module Associations
+ class AssociationCollection
+ alias_method_chain :find, :searchgasm
+ end
+ end
+end
+
+ActiveRecord::Associations::HasManyAssociation.send(:include, Searchgasm::ActiveRecord::Associations::HasManyAssociation)
+
+module ActiveRecord
+ module Associations
+ class HasManyAssociation
+ alias_method_chain :count, :searchgasm
+ end
+ end
+end
View
103 vendor/gems/searchgasm-0.9.7/lib/searchgasm/active_record/base.rb
@@ -0,0 +1,103 @@
+module Searchgasm
+ module ActiveRecord #:nodoc: all
+ module Base
+ def calculate_with_searchgasm(*args)
+ options = args.extract_options!
+ options = sanitize_options_with_searchgasm(options)
+ args << options
+ calculate_without_searchgasm(*args)
+ end
+
+ def find_with_searchgasm(*args)
+ options = args.extract_options!
+ options = sanitize_options_with_searchgasm(options)
+ args << options
+ find_without_searchgasm(*args)
+ end
+
+ def scope_with_searchgasm(method, key = nil)
+ scope = scope_without_searchgasm(method, key)
+ return sanitize_options_with_searchgasm(scope) if key.nil? && method == :find && !scope.blank?
+ scope
+ end
+
+ def build_conditions(values = {}, &block)
+ conditions = searchgasm_conditions
+ conditions.protect = true
+ conditions.conditions = values
+ yield conditions if block_given?
+ conditions
+ end
+
+ def build_conditions!(values = {}, &block)
+ conditions = searchgasm_conditions(values)
+ yield conditions if block_given?
+ conditions
+ end
+
+ def build_search(options = {}, &block)
+ search = searchgasm_searcher
+ search.protect = true
+ search.options = options
+ yield search if block_given?
+ search
+ end
+
+ def build_search!(options = {}, &block)
+ search = searchgasm_searcher(options)
+ yield search if block_given?
+ search
+ end
+
+ def conditions_protected(*conditions)
+ write_inheritable_attribute(:conditions_protected, Set.new(conditions.map(&:to_s)) + (protected_conditions || []))
+ end
+
+ def protected_conditions
+ read_inheritable_attribute(:conditions_protected)
+ end
+
+ def conditions_accessible(*conditions)
+ write_inheritable_attribute(:conditions_accessible, Set.new(conditions.map(&:to_s)) + (protected_conditions || []))
+ end
+
+ def accessible_conditions
+ read_inheritable_attribute(:conditions_accessible)
+ end
+
+ private
+ def sanitize_options_with_searchgasm(options = {})
+ return options unless Searchgasm::Search::Base.needed?(self, options)
+ searchgasm_searcher(options).sanitize
+ end
+
+ def searchgasm_conditions(options = {})
+ Searchgasm::Conditions::Base.new(self, options)
+ end
+
+ def searchgasm_searcher(options = {})
+ Searchgasm::Search::Base.new(self, options)
+ end
+ end
+ end
+end
+
+ActiveRecord::Base.send(:extend, Searchgasm::ActiveRecord::Base)
+
+module ActiveRecord #:nodoc: all
+ class Base
+ class << self
+ alias_method_chain :calculate, :searchgasm
+ alias_method_chain :find, :searchgasm
+ alias_method_chain :scope, :searchgasm
+ alias_method :new_conditions, :build_conditions
+ alias_method :new_conditions!, :build_conditions!
+ alias_method :new_search, :build_search
+ alias_method :new_search!, :build_search!
+
+ def valid_find_options
+ VALID_FIND_OPTIONS
+ end
+ end
+ end
+end
View
127 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/base.rb
@@ -0,0 +1,127 @@
+module Searchgasm
+ module Condition # :nodoc:
+ # = Conditions condition
+ #
+ # The base class for creating a condition. Your custom conditions should extend this class.
+ # See Searchgasm::Conditions::Base.register_condition on how to write your own condition.
+ class Base
+ include Utilities
+
+ attr_accessor :column, :klass
+ attr_reader :value
+
+ class << self
+ # Name of the condition inferred from the class name
+ def condition_name
+ name.split("::").last.gsub(/Condition$/, "").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}"
+ end
+
+ # Alias methods for the column condition.
+ def aliases_for_column(column)
+ []
+ 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
+ end
+
+ # Alias methods for the klass condition
+ def aliases_for_klass(klass)
+ []
+ 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
+ end
+
+ def initialize(klass, column = nil)
+ self.klass = klass
+ self.column = column.is_a?(String) ? klass.columns_hash[column] : column
+ end
+
+ # Allows nils to be meaninful values
+ def explicitly_set_value=(value)
+ @explicitly_set_value = value
+ end
+
+ # Need this if someone wants to actually use nil in a meaningful way
+ def explicitly_set_value?
+ @explicitly_set_value == true
+ end
+
+ # In most cases a blank value should be ignored, except for conditions like equals. A blank value is meaningful there, but a blank value for they keyswords condition is not.
+ def ignore_blanks?
+ 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)
+ 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?
+ v = alt_value || value
+ if v.is_a?(Array) && !["equals", "does_not_equal"].include?(condition_name)
+ 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)
+ to_conditions(v)
+ end
+ end
+
+ # The value for the condition
+ def value
+ @value.is_a?(String) ? column.type_cast(@value) : @value
+ end
+
+ # Sets the value for the condition, will ignore place if ignore_blanks?
+ def value=(v)
+ return if ignore_blanks? && v.blank?
+ self.explicitly_set_value = true
+ @value = v
+ end
+ end
+ end
+end
View
20 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/begins_with.rb
@@ -0,0 +1,20 @@
+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}_starts_with", "#{column.name}_start"]
+ end
+ end
+
+ def to_conditions(value)
+ ["#{quoted_table_name}.#{quoted_column_name} LIKE ?", "#{value}%"]
+ end
+ end
+ end
+end
View
11 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/child_of.rb
@@ -0,0 +1,11 @@
+module Searchgasm
+ module Condition
+ class ChildOf < Tree
+ def to_conditions(value)
+ parent_association = klass.reflect_on_association(:parent)
+ foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
+ ["#{quoted_table_name}.#{quote_column_name(foreign_key_name)} = ?", (value.is_a?(klass) ? value.send(klass.primary_key) : value)]
+ end
+ end
+ end
+end
View
20 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/contains.rb
@@ -0,0 +1,20 @@
+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
24 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/descendant_of.rb
@@ -0,0 +1,24 @@
+module Searchgasm
+ module Condition
+ class DescendantOf < Tree
+ def to_conditions(value)
+ # Wish I knew how to do this in SQL
+ root = (value.is_a?(klass) ? value : klass.find(value)) rescue return
+ strs = []
+ subs = []
+ all_children_ids(root).each do |child_id|
+ strs << "#{quoted_table_name}.#{quote_column_name(klass.primary_key)} = ?"
+ subs << child_id
+ end
+ [strs.join(" OR "), *subs]
+ end
+
+ private
+ def all_children_ids(record)
+ ids = record.children.collect { |child| child.send(klass.primary_key) }
+ record.children.each { |child| ids += all_children_ids(child) }
+ ids
+ end
+ end
+ end
+end
View
28 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/does_not_equal.rb
@@ -0,0 +1,28 @@
+module Searchgasm
+ module Condition
+ class DoesNotEqual < Base
+ class << self
+ def aliases_for_column(column)
+ ["#{column.name}_is_not", "#{column.name}_not"]
+ end
+ end
+
+ def ignore_blanks?
+ false
+ end
+
+ def to_conditions(value)
+ # Delegate to equals and then change
+ condition = Equals.new(klass, column)
+ condition.value = value
+
+ sql = condition.sanitize
+ sql.gsub!(/ IS /, " IS NOT ")
+ sql.gsub!(/ BETWEEN /, " NOT BETWEEN ")
+ sql.gsub!(/ IN /, " NOT IN ")
+ sql.gsub!(/=/, "!=")
+ sql
+ end
+ end
+ end
+end
View
20 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/ends_with.rb
@@ -0,0 +1,20 @@
+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"]
+ end
+ end
+
+ def to_conditions(value)
+ ["#{quoted_table_name}.#{quoted_column_name} LIKE ?", "%#{value}"]
+ end
+ end
+ end
+end
View
20 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/equals.rb
@@ -0,0 +1,20 @@
+module Searchgasm
+ module Condition
+ class Equals < Base
+ class << self
+ def aliases_for_column(column)
+ ["#{column.name}", "#{column.name}_is"]
+ end
+ end
+
+ def ignore_blanks?
+ false
+ end
+
+ def to_conditions(value)
+ # Let ActiveRecord handle this
+ klass.send(:sanitize_sql_hash_for_conditions, {column.name => value})
+ end
+ end
+ end
+end
View
25 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/greater_than.rb
@@ -0,0 +1,25 @@
+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$/, "") if [:datetime, :timestamp, :time, :date].include?(column.type) && column.name =~ /_at$/
+
+ aliases = []
+ column_names.each { |column_name| aliases += ["#{column_name}_gt", "#{column_name}_after"] }
+ aliases
+ end
+ end
+
+ def to_conditions(value)
+ ["#{quoted_table_name}.#{quoted_column_name} > ?", value]
+ end
+ end
+ end
+end
View
20 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/greater_than_or_equal_to.rb
@@ -0,0 +1,20 @@
+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.name}_gte", "#{column.name}_at_least"]
+ end
+ end
+
+ def to_conditions(value)
+ ["#{quoted_table_name}.#{quoted_column_name} >= ?", value]
+ end
+ end
+ end
+end
View
13 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/inclusive_descendant_of.rb
@@ -0,0 +1,13 @@
+module Searchgasm
+ module Condition
+ class InclusiveDescendantOf < Tree
+ include Searchgasm::Utilities
+
+ def to_conditions(value)
+ condition = DescendantOf.new(klass, column)
+ condition.value = value
+ merge_conditions(["#{quoted_table_name}.#{quote_column_name(klass.primary_key)} = ?", (value.is_a?(klass) ? value.send(klass.primary_key) : value)], condition.sanitize, :any => true)
+ end
+ end
+ end
+end
View
33 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/keywords.rb
@@ -0,0 +1,33 @@
+module Searchgasm
+ module Condition
+ 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"]
+ end
+ end
+
+ def to_conditions(value)
+ strs = []
+ subs = []
+
+ search_parts = value.gsub(/,/, " ").split(/ /).collect { |word| word.downcase.gsub(/[^[:alnum:]]/, ''); }.uniq.select { |word| !BLACKLISTED_WORDS.include?(word.downcase) && !word.blank? }
+ return if search_parts.blank?
+
+ search_parts.each do |search_part|
+ strs << "#{quoted_table_name}.#{quoted_column_name} LIKE ?"
+ subs << "%#{search_part}%"
+ end
+
+ [strs.join(" AND "), *subs]
+ end
+ end
+ end
+end
View
25 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/less_than.rb
@@ -0,0 +1,25 @@
+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$/, "") if [:datetime, :timestamp, :time, :date].include?(column.type) && column.name =~ /_at$/
+
+ aliases = []
+ column_names.each { |column_name| aliases += ["#{column_name}_lt", "#{column_name}_before"] }
+ aliases
+ end
+ end
+
+ def to_conditions(value)
+ ["#{quoted_table_name}.#{quoted_column_name} < ?", value]
+ end
+ end
+ end
+end
View
20 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/less_than_or_equal_to.rb
@@ -0,0 +1,20 @@
+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.name}_lte", "#{column.name}_at_most"]
+ end
+ end
+
+ def to_conditions(value)
+ ["#{quoted_table_name}.#{quoted_column_name} <= ?", value]
+ end
+ end
+ end
+end
View
16 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/sibling_of.rb
@@ -0,0 +1,16 @@
+module Searchgasm
+ module Condition
+ class SiblingOf < Tree
+ include Searchgasm::Utilities
+
+ def to_conditions(value)
+ parent_association = klass.reflect_on_association(:parent)
+ foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
+ parent_id = (value.is_a?(klass) ? value : klass.find(value)).send(foreign_key_name)
+ condition = ChildOf.new(klass, column)
+ condition.value = parent_id
+ merge_conditions(["#{quoted_table_name}.#{quote_column_name(klass.primary_key)} != ?", (value.is_a?(klass) ? value.send(klass.primary_key) : value)], condition.sanitize)
+ end
+ end
+ end
+end
View
16 vendor/gems/searchgasm-0.9.7/lib/searchgasm/condition/tree.rb
@@ -0,0 +1,16 @@
+module Searchgasm
+ module Condition
+ class Tree < Base # :nodoc:
+ class << self
+ def name_for_column(column)
+ nil
+ end
+
+ def name_for_klass(klass)
+ return unless klass.reflect_on_association(:parent) && klass.reflect_on_association(:children)
+ condition_name
+ end
+ end
+ end
+ end
+end
View
221 vendor/gems/searchgasm-0.9.7/lib/searchgasm/conditions/base.rb
@@ -0,0 +1,221 @@
+module Searchgasm
+ module Conditions # :nodoc:
+ # = Conditions
+ #
+ # Represents a collection of conditions and performs various tasks on that collection. For information on each condition see Searchgasm::Condition.
+ # Each condition has its own file and class and the source for each condition is pretty self explanatory.
+ class Base
+ include Utilities
+
+ attr_accessor :klass, :relationship_name, :scope
+
+ class << self
+ # Registers a condition as an available condition for a column or a class.
+ #
+ # === Example
+ #
+ # 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". 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()
+ # def to_conditions(value)
+ # ["#{quoted_table_name}.#{quoted_column_name} SOUNDS LIKE ?", value]
+ # end
+ # end
+ #
+ # Searchgasm::Seearch::Conditions.register_condition(SoundsLikeCondition)
+ def register_condition(klass)
+ raise(ArgumentError, "You can only register conditions that extend Searchgasm::Condition::Base") unless klass.ancestors.include?(Searchgasm::Condition::Base)
+ conditions << klass unless conditions.include?(klass)
+ end
+
+ # A list of available condition type classes
+ def conditions
+ @@conditions ||= []
+ end
+
+ def needed?(klass, conditions) # :nodoc:
+ if conditions.is_a?(Hash)
+ conditions.stringify_keys.keys.each do |condition|
+ return true unless klass.column_names.include?(condition)
+ end
+ end
+
+ false
+ end
+ end
+
+ def initialize(klass, init_conditions = {})
+ self.klass = klass
+ add_klass_conditions!
+ add_column_conditions!
+ add_associations!
+ self.conditions = init_conditions
+ end
+
+ # Setup methods for searching
+ [:all, :average, :calculate, :count, :find, :first, :maximum, :minimum, :sum].each do |method|
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def #{method}(*args)
+ self.conditions = args.extract_options!
+ args << {:conditions => sanitize}
+ klass.#{method}(*args)
+ end
+ end_eval
+ end
+
+ # A list of includes to use when searching, includes relationships
+ def includes
+ i = []
+ associations.each do |association|
+ association_includes = association.includes
+ i << (association_includes.blank? ? association.relationship_name.to_sym : {association.relationship_name.to_sym => association_includes})
+ end
+ i.blank? ? nil : (i.size == 1 ? i.first : i)
+ end
+
+ # Sanitizes the conditions down into conditions that ActiveRecord::Base.find can understand.
+ def sanitize
+ conditions = merge_conditions(*objects.collect { |object| object.sanitize })
+ return scope if conditions.blank?
+ merge_conditions(conditions, scope)
+ end
+
+ # Allows you to set the conditions via a hash. If you do not pass a hash it will set scope instead, so that you can continue to add conditions and ultimately
+ # merge it all together at the end.
+ def conditions=(conditions)
+ case conditions
+ when Hash
+ assert_valid_conditions(conditions)
+ remove_conditions_from_protected_assignement(conditions).each { |condition, value| send("#{condition}=", value) }
+ else
+ self.scope = conditions
+ end
+ end
+
+ # All of the active conditions (conditions that have been set)
+ def conditions
+ conditions_hash = {}
+ objects.each do |object|
+ case object
+ when self.class
+ 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
+ end
+ end
+ conditions_hash
+ end
+
+ private
+ def add_associations!
+ klass.reflect_on_all_associations.each do |association|
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
+ def #{association.name}
+ if @#{association.name}.nil?
+ @#{association.name} = self.class.new(#{association.class_name})
+ @#{association.name}.relationship_name = "#{association.name}"
+ objects << @#{association.name}
+ end
+ @#{association.name}
+ end
+
+ def #{association.name}=(conditions); #{association.name}.conditions = conditions; end
+ def reset_#{association.name}!; objects.delete(#{association.name}); @#{association.name} = nil; end
+ end_eval
+ end
+ end
+
+ def add_column_conditions!
+ 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) }
+ end
+ end
+ end
+
+ def add_condition!(condition, name, column = nil)
+ 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}
+ end
+ @#{name}
+ end
+
+ def #{name}; #{name}_object.value; end
+ def #{name}=(value); #{name}_object.value = value; end
+ def reset_#{name}!; objects.delete(#{name}_object); @#{name} = nil; end
+ end_eval
+ end
+
+ def add_condition_alias!(alias_name, name)
+ condition_names << alias_name
+ self.class.class_eval do
+ alias_method alias_name, name
+ alias_method "#{alias_name}=", "#{name}="
+ end
+ end
+
+ def add_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
+ end
+
+ def assert_valid_conditions(conditions)
+ keys = condition_names.collect { |condition_name| condition_name.to_sym }
+ keys += klass.reflect_on_all_associations.collect { |association| association.name }
+ conditions.symbolize_keys.assert_valid_keys(keys)
+ end
+
+ def associations
+ objects.select { |object| object.is_a?(self.class) }
+ end
+
+ def condition_names
+ @condition_names ||= []
+ end
+
+ def objects
+ @objects ||= []
+ end
+
+ def remove_conditions_from_protected_assignement(conditions)
+ return conditions if klass.accessible_conditions.nil? && klass.protected_conditions.nil?
+ if klass.accessible_conditions
+ conditions.reject { |condition, value| !klass.accessible_conditions.include?(condition.to_s) }
+ elsif klass.protected_conditions
+ conditions.reject { |condition, value| klass.protected_conditions.include?(condition.to_s) }
+ end
+ end
+ end
+ end
+end
View
30 vendor/gems/searchgasm-0.9.7/lib/searchgasm/conditions/protection.rb
@@ -0,0 +1,30 @@
+module Searchgasm
+ module Conditions
+ # = Conditions Protection
+ #
+ # Adds protection from SQL injections. Just set protect = true and it will limit what kind of conditions it will accept.
+ module Protection
+ def self.included(klass)
+ klass.class_eval do
+ attr_accessor :protect
+ alias_method_chain :conditions=, :protection
+ end
+ end
+
+ def conditions_with_protection=(conditions)
+ unless conditions.is_a?(Hash)
+ if protect?
+ return if conditions.blank?
+ raise(ArgumentError, "You can not set a scope or pass SQL while the search is being protected")
+ end
+ end
+
+ self.conditions_without_protection = conditions
+ end
+
+ def protect?
+ protect == true
+ end
+ end
+ end
+end
View
137 vendor/gems/searchgasm-0.9.7/lib/searchgasm/config.rb
@@ -0,0 +1,137 @@
+module Searchgasm
+ # = Config
+ # Adds default configuration for all of searchgasm. Just make sure you set your config before you use Searchgasm.
+ # For rails the best place to do this is in config/initializers. Create a file in there called searchgasm.rb with the following content:
+ #
+ # === Example
+ #
+ # # config/iniitializers/searchgasm.rb
+ # Searchgasm::Config.configure do |config|
+ # config.you_option_here = your_value # see methods below
+ # end
+ class Config
+ class << self
+ # Convenience method for setting configuration
+ # See example at top of class.
+ def configure
+ yield self
+ end
+
+ def asc_indicator # :nodoc:
+ @asc_indicator ||= "&nbsp;&#9650;"
+ end
+
+ # The indicator that is used when the sort of a column is ascending
+ #
+ # * <tt>Default:</tt> &nbsp;&#9650;
+ # * <tt>Accepts:</tt> String or a Proc.
+ #
+ # === Examples
+ #
+ # config.asc_indicator = "(ASC)"
+ # config.asc_indicator = Proc.new { |template| template.image_tag("asc.jpg") }
+ def asc_indicator=(value)
+ @asc_indicator = value
+ end
+
+ def desc_indicator # :nodoc:
+ @desc_indicator ||= "&nbsp;&#9660;"
+ end
+
+ # See asc_indicator=
+ def desc_indicator=(value)
+ @desc_indicator = value
+ end
+
+ def pages_type # :nodoc:
+ @pages_type ||= :select
+ end
+
+ # The default value for the :type option in the pages helper.
+ #
+ # * <tt>Default:</tt> :select
+ # * <tt>Accepts:</tt> :select, :links
+ def pages_type=(value)
+ @pages_type = value.to_sym
+ end
+
+ def per_page_choices # :nodoc:
+ @per_page_choices ||= [10, 25, 50, 100, 150, 200, nil]
+ end
+
+ # The choices used in the per_page helper
+ #
+ # * <tt>Default:</tt> [10, 25, 50, 100, 150, 200, nil]
+ # * <tt>Accepts:</tt> Array
+ #
+ # nil means "Show all"
+ def per_page_choices=(value)
+ @per_page_choices = value
+ end
+
+ def per_page_type # :nodoc:
+ @per_page_type ||= :select
+ end
+
+ # The default value for the :type option in the per_page helper.
+ #
+ # * <tt>Default:</tt> :select
+ # * <tt>Accepts:</tt> :select, :links
+ def per_page_type=(value)
+ @per_page_type = value.to_sym
+ end
+
+ def hidden_fields # :nodoc:
+ @hidden_fields ||= (Search::Base::SPECIAL_FIND_OPTIONS - [:page])
+ end
+
+ # Which hidden fields to automatically include when creating a form with a Searchgasm object. See Searchgasm::Helpers::FormHelper for more info.
+ #
+ # * <tt>Default:</tt> [:order_by, :order_as, :per_page]
+ # * <tt>Accepts:</tt> Array, nil, false
+ def hidden_fields=(value)
+ @hidden_fields = value
+ end
+
+ def remote_helpers # :nodoc:
+ @remote_helpers ||= false
+ end
+
+ # Tells all helpers to default to using remote links (AJAX) instead of normal links.
+ #
+ # * <tt>Default:</tt> false
+ # * <tt>Accepts:</tt> Boolean
+ #
+ # nil means "Show all"
+ def remote_helpers=(value)
+ @remote_helpers = value
+ end
+
+ def remote_helpers? # :nodoc:
+ remote_helpers == true
+ end
+
+ def search_scope # :nodoc:
+
+ end
+
+ def search_obj_name # :nodoc:
+ @search_obj_name ||= :@search
+ end
+
+ # The instance variable name you use to assign your search to. This allows the helpers to grab your Searchgasm object without having
+ # to specify it everywhere.
+ #
+ # * <tt>Default:</tt> :@search
+ # * <tt>Accepts:</tt> String or Symbol.
+ #
+ # === Examples
+ #
+ # config.search_obj_name = :@search
+ # config.search_obj_name = "@search"
+ def search_obj_name=(value)
+ @search_obj_name = value
+ end
+ end
+ end
+end
View
159 vendor/gems/searchgasm-0.9.7/lib/searchgasm/helpers/form_helper.rb
@@ -0,0 +1,159 @@
+module Searchgasm
+ module Helpers
+ # = Form Helper
+ #
+ # Enables you to use form_for and fields_for just like you do with an ActiveRecord object.
+ #
+ # === Examples
+ #
+ # Let's assume @search is searching Address
+ #
+ # form_for(@search) # is equivalent to form_for(:search, @search, :url => addresses_path)
+ # form_for([@current_user, @search]) # is equivalent to form_for(:search, @search, :url => user_addresses_path(@current_user))
+ # form_for([:admin, @search]) # is equivalent to form_for(:search, @search, :url => admin_addresses_path)
+ # form_for(:search, @search, :url => whatever_path)
+ #
+ # The goal was to mimic ActiveRecord. You can also pass a Searchgasm::Conditions::Base object as well and it will function the same way.
+ #
+ # === Automatic hidden fields generation
+ #
+ # If you pass a Searchgasm::Search::Base object it automatically adds the :order_by, :order_as, and :per_page hidden fields. This is done so that when someone
+ # creates a new search, their options are remembered. It keeps the search consisten and is much more user friendly. If you want to override this you can pass the
+ # following options. Or you can set this up in your configuration, see Searchgasm::Config for more details.
+ #
+ # === Options
+ #
+ # * <tt>:hidden_fields</tt> --- Array, a list of hidden fields to include. Defaults to [:order_by, :order_as, :per_page]. Pass false, nil, or a blank array to not include any.
+ module FormHelper
+ module Shared # :nodoc:
+ private
+ def searchgasm_object?(object)
+ object.is_a?(Search::Base) || object.is_a?(Conditions::Base)
+ end
+
+ def find_searchgasm_object(args)
+ case args.first
+ when String, Symbol
+ search_object = searchgasm_object?(args[1]) ? args[1] : instance_variable_get("@#{args.first}")
+ when Array
+ search_object = args.first.last
+ else
+ search_object = args.first
+ end
+
+ searchgasm_object?(search_object) ? search_object : nil
+ end
+
+ def searchgasm_args(args, search_object, for_helper = nil)
+ args = args.dup
+ first = args.shift
+
+ # Setup args
+ case first
+ when String, Symbol
+ args.unshift(search_object).unshift(first)
+ else
+ name = search_object.is_a?(Conditions::Base) ? (search_object.relationship_name || :conditions) : :search
+ args.unshift(search_object).unshift(name)
+ end
+
+ if for_helper != :fields_for
+ options = args.extract_options!
+ options[:html] ||= {}
+ options[:html][:method] ||= :get
+ options[:method] ||= options[:html][:method] if for_helper == :remote_form_for
+ options[:html][:id] ||= searchgasm_form_id(search_object)
+
+ # Setup options
+ case first
+ when Array
+ first.pop
+ first << search_object.klass.new
+ options[:url] ||= polymorphic_path(first)
+ else
+ options[:url] ||= polymorphic_path(search_object.klass.new)
+ end
+
+ args << options
+ end
+
+ args
+ end
+
+ def insert_searchgasm_fields(args, search_object)
+ return unless search_object.is_a?(Search::Base)
+ name = args.first
+ options = args.extract_options!
+ (options.delete(:hidden_fields) || Config.hidden_fields).each do |option|
+ concat(hidden_field(name, option, :object => search_object, :value => (option == :order_by ? searchgasm_order_by_value(search_object.order_by) : search_object.send(option))))
+ end
+ args << options
+ end
+ end
+
+ module Base # :nodoc:
+ include Shared
+
+ def fields_for_with_searchgasm(*args, &block)
+ search_object = find_searchgasm_object(args)
+ if search_object
+ new_args = searchgasm_args(args, search_object, :fields_for)
+ insert_searchgasm_fields(new_args, search_object)
+ fields_for_without_searchgasm(*new_args, &block)
+ else
+ fields_for_without_searchgasm(*args, &block)
+ end
+ end
+
+ def form_for_with_searchgasm(*args, &block)
+ search_object = find_searchgasm_object(args)
+ if search_object
+ form_for_without_searchgasm(*searchgasm_args(args, search_object, :form_for), &block)
+ else
+ form_for_without_searchgasm(*args, &block)
+ end
+ end
+
+ def remote_form_for_with_searchgasm(*args, &block)
+ search_object = find_searchgasm_object(args)
+ if search_object
+ remote_form_for_without_searchgasm(*searchgasm_args(args, search_object, :remote_form_for), &block)
+ else
+ remote_form_for_without_searchgasm(*args, &block)
+ end
+ end
+ end
+
+ module FormBuilder # :nodoc:
+ include Shared
+
+ def fields_for_with_searchgasm(*args, &block)
+ search_object = find_searchgasm_object(args)
+ if search_object
+ new_args = searchgasm_args(args, search_object, :fields_for)
+ insert_searchgasm_fields(new_args, search_object)
+ fields_for_without_searchgasm(*new_args, &block)
+ else
+ fields_for_without_searchgasm(*args, &block)
+ end
+ end
+ end
+ end
+ end
+end
+
+if defined?(ActionView)
+ ActionView::Base.send(:include, Searchgasm::Helpers::FormHelper::Base)
+
+ ActionView::Base.class_eval do
+ alias_method_chain :fields_for, :searchgasm
+ alias_method_chain :form_for, :searchgasm
+ alias_method_chain :remote_form_for, :searchgasm
+ end
+
+ ActionView::Helpers::FormBuilder.send(:include, Searchgasm::Helpers::FormHelper::FormBuilder)
+
+ ActionView::Helpers::FormBuilder.class_eval do
+ alias_method_chain :fields_for, :searchgasm
+ end
+end
View
178 vendor/gems/searchgasm-0.9.7/lib/searchgasm/helpers/search_helper.rb
@@ -0,0 +1,178 @@
+module Searchgasm
+ module Helpers
+ # = Search Helper
+ #
+ # Helper methods for paginating and ordering through a search.
+ module SearchHelper
+ # Creates a link for ordering data in a certain way. See Searchgasm::Config for setting default configuration.
+ #
+ # === Example uses for a User class that has many orders
+ # order_by(:first_name)
+ # order_by([:first_name, :last_name])
+ # order_by({:orders => :total})
+ # order_bt([{:orders => :total}, :first_name])
+ #
+ # If the output just isn't cutting it for you, then you can pass it a block and it will spit out the result of the block. The block is passed "options" which is all of the information you should need
+ # for doing whatever you need to do. It's the options you are allowed to pass, but with their proper values.
+ #
+ # <%= order_by(:id) { |options| link_to(options[:text], options[:url], options[:html]) } %>
+ #
+ # or
+ #
+ # <% order_by(:id) do |options| %><%= link_to(options[:text], options[:url]) %><% end %>
+ #
+ # Another thing to keep in mind is that the value gets "serialized", if it is not a string or a simple, so that it can be passed via a param in the url. Searchgasm will automatically try to "unserializes" this value and use it. This allows you
+ # to pass complex objects besides strings and symbols, such as arrays and hashes.
+ #
+ # === Options
+ # * <tt>:text</tt> -- default: column_name.to_s.humanize, text for the link
+ # * <tt>:desc_indicator</tt> -- default: &nbsp;&#9660;, the indicator that this column is descending
+ # * <tt>:asc_indicator</tt> -- default: &nbsp;&#9650;, the indicator that this column is ascending
+ #
+ # === Advanced Options
+ # * <tt>:action</tt> -- this is automatically determined for you based on the type. For a :select type, its :onchange. For a :links type, its :onclick. You shouldn't have to use this option unless you are doing something out of the norm. The point of this option is to return a URL that will include this.value or not
+ # * <tt>:url</tt> -- default: uses url_for to preserve your params and search, I can not think of a reason to pass this, but its there just incase
+ # * <tt>:html</tt> -- if type is :links then these will apply to the outermost <div>, if type is :select then these will apply to the select tag
+ # * <tt>:search_obj</tt> -- default: @search, this is your search object, if it is in an instance variable other than @search please pass it here. Ex: :@my_search, or :my_search
+ # * <tt>:params_scope</tt> -- default: :search, this is the scope in which your search params will be preserved (params[:search]). If you don't want a scope and want your options to be at base leve in params such as params[:page], params[:per_page], etc, then set this to nil.
+ def order_by(column_name, options = {}, &block)
+ add_searchgasm_helper_defaults!(options, :order_by, column_name)
+ column_name = stringify_everything(column_name)
+ options[:text] = determine_order_by_text(column_name) unless options.has_key?(:text)
+ options[:asc_indicator] ||= Config.asc_indicator
+ options[:desc_indicator] ||= Config.desc_indicator
+ options[:text] += options[:search_obj].desc? ? options[:desc_indicator] : options[:asc_indicator] if options[:search_obj].order_by == column_name
+
+ if block_given?
+ yield options
+ else
+ if options[:remote]
+ link_to_function(options[:text], options[:url], options[:html])
+ else
+ link_to(options[:text], options[:url], options[:html])
+ end
+ end
+ end
+
+ # Creates navigation for paginating through a search. See Searchgasm::Config for setting default configuration.
+ #
+ # === Examples
+ # pages
+ # pages(:search => @my_search)
+ # pages(:html => {:id => "my_id"})
+ #
+ # If the output just isn't cutting it for you, then you can pass it a block and it will spit out the result of the block. The block is passed "options" which is all of the information you should need
+ # for doing whatever you need to do. It's the options you are allowed to pass, but with their proper values.
+ #
+ # <%= pages { |options| select(:search, :page, (1..options[:search_obj].page_count), {}, options[:html]) } %>
+ #
+ # or
+ #
+ # <% pages do |options| %><%= select(:search, :page, (1..options[:search_obj].page_count), {}, options[:html]) %><% end %>
+ #
+ # === Options
+ # * <tt>:type</tt> -- default: :select, pass :links as an alternative to have flickr like pagination
+ # * <tt>:remote</tt> -- default: false, if true requests will be AJAX
+ #
+ # === Advanced Options
+ # * <tt>:action</tt> -- this is automatically determined for you based on the type. For a :select type, its :onchange. For a :links type, its :onclick. You shouldn't have to use this option unless you are doing something out of the norm. The point of this option is to return a URL that will include this.value or not
+ # * <tt>:url</tt> -- default: uses url_for to preserve your params and search, I can not think of a reason to pass this, but its there just incase
+ # * <tt>:html</tt> -- if type is :links then these will apply to the outermost <div>, if type is :select then these will apply to the select tag
+ # * <tt>:search_obj</tt> -- default: @search, this is your search object, if it is in an instance variable other than @search please pass it here. Ex: :@my_search, or :my_search
+ # * <tt>:params_scope</tt> -- default: :search, this is the scope in which your search params will be preserved (params[:search]). If you don't want a scope and want your options to be at base leve in params such as params[:page], params[:per_page], etc, then set this to nil.
+ def pages(options = {})
+ options[:type] ||= Config.pages_type
+ add_searchgasm_helper_defaults!(options, :page)
+ return "" if options[:search_obj].page_count <= 1
+
+ if block_given?
+ yield options
+ else
+ case options[:type]
+ when :select
+ options[:html] ||= {}
+ options[:html][options[:action]] ||= ""
+ options[:html][options[:action]] += ";"
+ options[:html][options[:action]] += options[:url]
+ select(:search, :page, (1..options[:search_obj].page_count), {}, options[:html])
+ else
+ # HTML for links
+ end
+ end
+ end
+
+ # Creates navigation for setting how many items per page. See Searchgasm::Config for setting default configuration.
+ #
+ # === Examples
+ # per_page
+ # per_page(:search => @my_search)
+ # per_page(:choices => [50, 100])
+ # per_page(:html => {:id => "my_id"})
+ #
+ # If the output just isn't cutting it for you, then you can pass it a block and it will spit out the result of the block. The block is passed "options" which is all of the information you should need
+ # for doing whatever you need to do. It's the options you are allowed to pass, but with their proper values.
+ #
+ # <%= per_page { |options| select(:search, :per_page, options[:choices], {}, options[:html]) } %>
+ #
+ # or
+ #
+ # <% per_page do |options| %><%= select(:search, :per_page, options[:choices], {}, options[:html]) %><% end %>
+ #
+ # === Options
+ # * <tt>:type</tt> -- default: :select, pass :links as an alternative to links like: 10 | 25 | 50 | 100, etc
+ # * <tt>:remote</tt> -- default: false, if true requests will be AJAX
+ # * <tt>:choices</tt> -- default: [10, 25, 50, 100, 150, 200, nil], nil means "show all"
+ #
+ # === Advanced Options
+ # * <tt>:choices</tt> -- default: [10, 25, 50, 100, 150, 200, nil], nil means "show all"
+ # * <tt>:action</tt> -- this is automatically determined for you based on the type. For a :select type, its :onchange. For a :links type, its :onclick. You shouldn't have to use this option unless you are doing something out of the norm. The point of this option is to return a URL that will include this.value or not
+ # * <tt>:url</tt> -- default: uses url_for to preserve your params and search, I can not think of a reason to pass this, but its there just incase
+ # * <tt>:html</tt> -- if type is :links then these will apply to the outermost <div>, if type is :select then these will apply to the select tag
+ # * <tt>:search_obj</tt> -- default: @search, this is your search object, if it is in an instance variable other than @search please pass it here. Ex: :@my_search, or :my_search
+ # * <tt>:params_scope</tt> -- default: :search, this is the scope in which your search params will be preserved (params[:search]). If you don't want a scope and want your options to be at base leve in params such as params[:page], params[:per_page], etc, then set this to nil.
+ def per_page(options = {})
+ options[:type] ||= Config.per_page_type
+ add_searchgasm_helper_defaults!(options, :per_page)
+
+ options[:choices] ||= Config.per_page_choices
+ if !options[:search_obj].per_page.blank? && !options[:choices].include?(options[:search_obj].per_page)
+ options[:choices] << options[:search_obj].per_page
+ has_nil = options[:choices].include?(nil)
+ options[:choices].delete(nil) if has_nil
+ options[:choices].sort!
+ options[:choices] << nil if has_nil
+ end
+ options[:choices] = options[:choices].collect { |choice| [choice == nil ? "Show all" : "#{choice} per page", choice] }
+
+ if block_given?
+ yield options
+ else
+ case options[:type]
+ when :select
+ options[:html] ||= {}
+ options[:html][options[:action]] ||= ""
+ options[:html][options[:action]] += ";"
+ options[:html][options[:action]] += options[:url]
+ select(:search, :per_page, options[:choices], {}, options[:html])
+ end
+ end
+ end
+
+ private
+ def determine_order_by_text(column_name, relationship_name = nil)
+ case column_name
+ when String, Symbol
+ relationship_name.blank? ? column_name.titleize : "#{relationship_name.titleize} #{column_name.titleize}"
+ when Array
+ determine_order_by_text(column_name.last)
+ when Hash
+ k = column_name.keys.first
+ v = column_name.values.first
+ determine_order_by_text(v, k)
+ end
+ end
+ end
+ end
+end
+
+ActionController::Base.helper Searchgasm::Helpers::SearchHelper if defined?(ActionController)
View
125 vendor/gems/searchgasm-0.9.7/lib/searchgasm/helpers/utilities_helper.rb
@@ -0,0 +1,125 @@
+module Searchgasm
+ module Helpers #:nodoc:
+ module UtilitiesHelper # :nodoc:
+ private
+ # Adds default options for all helper methods.
+ def add_searchgasm_helper_defaults!(options, method_name, method_value = nil)
+ options[:search_obj] ||= instance_variable_get(Config.search_obj_name)
+ raise(ArgumentError, "@search object could not be inferred, please specify: :search_obj => @search)") unless options[:search_obj].is_a?(Searchgasm::Search::Base)
+ method_value = stringify_everything(method_value) unless method_value.nil?
+ options[:params_scope] = :search unless options.has_key?(:params_scope)
+ options[:remote] = Config.remote_helpers? unless options.has_key?(:remote)
+
+ if !options.has_key?(:action)
+ if options[:type] == :select
+ options[:action] = :onchange
+ elsif options[:remote]
+ options[:action] = :onclick
+ end
+ end
+
+ options[:url] = searchgasm_url(options, method_name, method_value) unless options.has_key?(:url)
+
+ options
+ end
+
+ def searchgasm_url(options, method_name, method_value = nil)
+ params = (params || {}).dup
+ params.delete(:commit)
+
+ # Extract search params from params
+ search_params = options[:params_scope].blank? ? params : params[options[:params_scope]] ||= {}
+
+ # Rewrite :order_by and :per_page with what's in our search obj
+ ([:order_by, :per_page] - [method_name]).each { |search_option| search_params[search_option] = options[:search_obj].send(search_option) }
+
+ # Rewrite :conditions, separated due to unique call
+ conditions = options[:search_obj].conditions.conditions
+ search_params[:conditions] = conditions unless conditions.blank?
+
+ # Never want to keep page or the option we are trying to set
+ [:page, method_name].each { |option| search_params.delete(option) }
+
+ # Alternate :order_by if we are ordering
+ if method_name == :order_by
+ search_params[:order_as] = (options[:search_obj].order_by == method_value && options[:search_obj].asc?) ? "DESC" : "ASC"
+ else
+ search_params[:order_as] = options[:search_obj].order_as
+ end
+
+ # Determine if this.value should be included or not, and set up url
+ url = nil
+ case options[:action]
+ when :onchange
+ # Include this.value
+ url = url_for(params)
+ url_option = CGI.escape((options[:params_scope].blank? ? "#{method_name}" : "#{options[:params_scope]}[#{method_name}]")) + "='+this.value"
+ url += (url.last == "?" ? "" : (url.include?("?") ? "&amp;" : "?")) + url_option
+ else
+ # Build the plain URL
+ search_params[method_name] = method_name == :order_by ? searchgasm_order_by_value(method_value) : method_value
+ url = url_for(params)
+ end
+
+ # Now update options if remote
+ if options[:remote]
+ url = remote_function(:url => url, :method => :get).gsub(/\\'\+this.value'/, "'+this.value") + ";"
+
+ update_fields = {method_name => method_value}
+ update_fields[:order_as] = search_params[:order_as] if method_name == :order_by
+ update_fields.each { |field, value| url += ";" + searchgasm_update_search_field_javascript(field, value, options) }
+ elsif !options[:action].blank?
+ # Add some javascript if its onclick
+ url = "window.location = '" + url + ";"
+ end
+
+ url
+ end
+
+ def searchgasm_update_search_field_javascript(field, value, options)
+ field_value = nil
+
+ case options[:action]
+ when :onchange
+ field_value = "this.value";
+ else
+ field_value = field == :order_by ? searchgasm_order_by_value(value) : value
+ field_value = "'#{CGI.escape(field_value)}'"
+ end
+
+ field_name = options[:params_scope] ? "#{options[:params_scope]}[#{field}]" : "#{field}"
+ "#{field} = $('#{searchgasm_form_id(options[:search_obj])}').getInputs('hidden', '#{field_name}'); if(#{field}.length > 0) { #{field}[0].value = #{field_value}; }"
+ end
+
+ def searchgasm_form_id(search_obj)
+ "#{search_obj.klass.name.pluralize.underscore}_search_form"
+ end
+
+ def searchgasm_order_by_value(order_by)
+ case order_by
+ when String
+ order_by
+ when Array, Hash
+ [Marshal.dump(order_by)].pack("m")
+ end
+ end
+
+ def stringify_everything(obj)
+ case obj
+ when String
+ obj
+ when Symbol
+ obj = obj.to_s
+ when Array
+ obj = obj.collect { |item| stringify_everything(item) }
+ when Hash
+ new_obj = {}
+ obj.each { |key, value| new_obj[key.to_s] = stringify_everything(value) }
+ new_obj
+ end
+ end
+ end
+ end
+end
+
+ActionController::Base.helper(Searchgasm::Helpers::UtilitiesHelper) if defined?(ActionController)
View
78 vendor/gems/searchgasm-0.9.7/lib/searchgasm/search/base.rb
@@ -0,0 +1,78 @@
+module Searchgasm #:nodoc:
+ module Search #:nodoc:
+ # = Searchgasm
+ #
+ # Please refer the README.rdoc for usage, examples, and installation.
+
+ class Base
+ include Searchgasm::Utilities
+
+ # Options that ActiveRecord doesn't suppport, but Searchgasm does
+ SPECIAL_FIND_OPTIONS = [:order_by, :order_as, :page, :per_page]
+
+ # Valid options you can use when searching
+ VALID_FIND_OPTIONS = ::ActiveRecord::Base.valid_find_options + SPECIAL_FIND_OPTIONS
+
+ # Use these methods just like you would in ActiveRecord
+ SEARCH_METHODS = [:all, :average, :calculate, :count, :find, :first, :maximum, :minimum, :sum]
+
+ attr_accessor :klass, *::ActiveRecord::Base.valid_find_options
+
+ # Used in the ActiveRecord methods to determine if Searchgasm should get involved or not.
+ # This keeps Searchgasm out of the way unless it is needed.
+ def self.needed?(klass, options)
+ SPECIAL_FIND_OPTIONS.each do |option|
+ return true if options.symbolize_keys.keys.include?(option)
+ end
+
+ Searchgasm::Conditions::Base.needed?(klass, options[:conditions])
+ end
+
+ def initialize(klass, init_options = {})
+ self.klass = klass
+ self.options = init_options
+ end
+
+ # Setup methods for searching
+ SEARCH_METHODS.each do |method|
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def #{method}(*args)
+ self.options = args.extract_options!
+ args << sanitize(:#{method})
+ klass.#{method}(*args)
+ end
+ end_eval
+ end
+
+ def inspect
+ options_as_nice_string = ::ActiveRecord::Base.valid_find_options.collect { |name| "#{name}: #{send(name)}" }.join(", ")
+ "#<#{klass} #{options_as_nice_string}>"
+ end
+
+ def limit=(value)
+ @limit = value.blank? || value == 0 ? nil : value.to_i
+ end
+
+ def offset=(value)
+ @offset = value.to_i
+ end
+
+ def options=(values)
+ return unless values.is_a?(Hash)
+ values.symbolize_keys.assert_valid_keys(VALID_FIND_OPTIONS)
+ values.each { |option, value| send("#{option}=", value) }
+ end
+
+ # Sanitizes everything down into options ActiveRecord::Base.find can understand
+ def sanitize(for_method = nil)
+ find_options = {}
+ ::ActiveRecord::Base.valid_find_options.each do |find_option|
+ value = send(find_option)
+ next if value.blank? || (for_method == :count && [:limit, :offset].include?(find_option))
+ find_options[find_option] = value
+ end
+ find_options
+ end
+ end
+ end
+end
View
48 vendor/gems/searchgasm-0.9.7/lib/searchgasm/search/conditions.rb
@@ -0,0 +1,48 @@
+module Searchgasm
+ module Search
+ module Conditions
+ def self.included(klass)
+ klass.class_eval do
+ alias_method_chain :initialize, :conditions
+ alias_method_chain :conditions=, :conditions
+ alias_method_chain :include, :conditions
+ alias_method_chain :sanitize, :conditions
+ end
+ end
+
+ def initialize_with_conditions(klass, init_options = {})
+ self.conditions = Searchgasm::Conditions::Base.new(klass)
+ initialize_without_conditions(klass, init_options)
+ end
+
+ # Sets conditions on the search. Accepts a hash or a Searchgasm::Conditions::Base object.
+ def conditions_with_conditions=(values)
+ case values
+ when Searchgasm::Conditions::Base
+ @conditions = values
+ else
+ @conditions.conditions = values
+ end
+ end
+
+ def include_with_conditions
+ includes = [include_without_conditions, conditions.includes].flatten.compact.uniq
+ includes.blank? ? nil : (includes.size == 1 ? includes.first : includes)
+ end
+
+ def sanitize_with_conditions(for_method = nil)
+ find_options = sanitize_without_conditions(for_method)
+ find_options[:conditions] = find_options[:conditions].sanitize if find_options[:conditions]
+ find_options
+ end
+
+ def scope
+ conditions.scope
+ end
+
+ def scope=(value)
+ conditions.scope = value
+ end
+ end
+ end