Permalink
Browse files

First commit

  • Loading branch information...
0 parents commit aa5b90a3575cbbecb6226ab3c42a2cb8eb837e98 @binarylogic committed Aug 25, 2008
Showing with 824 additions and 0 deletions.
  1. +20 −0 MIT-LICENSE
  2. +107 −0 README.mdown
  3. +3 −0 init.rb
  4. +27 −0 lib/searchgasm/hash.rb
  5. +100 −0 lib/searchgasm/helpers.rb
  6. +567 −0 lib/searchgasm/searcher.rb
@@ -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.
@@ -0,0 +1,107 @@
+# Searchgasm
+
+## What is searchgasm?
+Have you ever wanted to search the database in the same manner that you use your models? This plugin does exactly that. By no means is this meant to perform complex or large text based searches, it aims to make setting up search forms and searching the database as simple as setting up model forms and performing CRUD tasks. Here's the quick and dirty of what this search utility can do:
+
+## Quick example
+
+ @searcher = UserSearcher.new
+ @searcher.first_name_contains = "Ben"
+ @searcher.created_at_after = Time.now
+ @searcher.per_page = 10 # set to 0 to return all
+ @searcher.page = 2
+ @searcher.order_by = "last_name"
+ @searcher.order_as = "ASC"
+ @searcher.search
+ => returns users matching ALL of the conditions above
+
+## Getting started
+
+### 1. Install the plugin
+
+ script/plugin install git://github.com/Squeegy/fleximage.git
+
+### 2. Set up your configuration (optional)
+
+I like to put all of my searcher files in app/searchers and in my environment.rb I add the following configuration:
+
+ config.load_paths += %W( #{RAILS_ROOT}/app/searchers )
+
+Then setup your searcher configuration by adding a file in config/initializers called search_logic.rb. The following is the default configuration, only add this file if you plan on changing this, or you want to enforce the defaults:
+
+ Searcher::Base.configure do |config|
+ config.per_page = 0 # defaults to 0
+ config.order_as = "DESC" # defaults to "DESC". the order_by configuration depends on the model, it defaults to the primary key, order_by is best suited in your actual searcher
+ config.ignore_blanks = true # defaults to true
+ end
+
+### 3. Set up your searchers
+
+Create a searcher for each model you want to search. For example if you want to search the User model you would create app/searchers/user_searcher.rb with the following content:
+
+ class UserSearcher < Searchgasm::Base
+ # The following configuration is optional will override what you set in your initializer
+ per_page 0
+ order_by "id"
+ order_as "DESC"
+ ignore_blanks true
+ end
+
+### 3. Test it out
+
+ @searcher = UserSearcher.new(:first_name_contains => "Ben")
+ @searcher.last_name_contains = "Johnson"
+ @searcher.search
+ => returns all users with the above conditions
+ @searcher.all
+ => same as above
+ @searcher.count
+ => returns the count of all users with the above conditions
+ @searcher.first
+ => returns the first user with the above conditions
+
+## Available conditions (by column type)
+Each column in the respective model come preloaded with conditions that can easily be applied by calling a method in the following format: searcher_object.#{column_name}_#{condition}=
+
+Here are the conditions available for each column type, they are pretty self explanitory:
+
+### string & text
+is, equals, is_not, does_not_equal, begins_with, starts_with, contains, keywords, ends_with
+
+### datetime, timestamp, time, date
+is, equals, is_not, does_not_equal, greater_than, gt, after, greater_than_or_equal_to, at_least, gte, less_than, lt, before, less_than_or_equal_to, lte, at_most
+
+### integer, float, decimal
+is, equals, is_not, does_not_equal, greater_than, gt, greater_than_or_equal_to, at_least, gte, less_than, lt, less_than_or_equal_to, lte, at_most
+
+## Advanced examples
+
+### 1. Advanced ordering
+coming soon
+
+### 2. Scoping
+coming soon
+
+### 3. Matching ANY of the conditions, not ALL
+coming soon
+
+### 4. Saving a search for later
+coming soon
+
+### 5. Roll your own special conditions using hooks
+coming soon
+
+### 6. Creating "smart" relationships, such as itunes smart playlists
+coming soon
+
+## Real world example
+
+Note to self: show restful searching via index method with a data table that can be ordered and paginated.
+
+## Disclaimer
+
+As selfish as it sounds, I made this plugin for my own personal needs. Obviously there is still some work to do, such as writing tests and better documentation. I am not an ActiveRecord expert and I don't know exactly how it's built, but I tried my best to mimic a lot of its features and the quality of it's code. I am always open to suggestions, if you have any please send them my way. You can get ahold of me through my website www.binarylogic.com
+
+The bottom line is that this plugin has cleaned up my code quite a bit and made my life much easier, especially around the tedious / repetitive tasks such as creating search forms or data tables. I hope it can do the same for you.
+
+Copyright (c) 2007 Ben Johnson of [Binary Logic](http://www.binarylogic.com), released under the MIT license
@@ -0,0 +1,3 @@
+require "searchgasm/hash"
+require "searchgasm/helpers"
+require "searchgasm/searcher"
@@ -0,0 +1,27 @@
+module BinaryLogic
+ module SearchGasm
+ module Hash
+ def merge_find_options!(new_options)
+ if new_options.has_key?(:conditions)
+ new_options[:conditions] = new_options[:conditions].to_a
+ self[:conditions] = self[:conditions].to_a
+ self[:conditions][0] = (self[:conditions].first.blank? ? "" : "(#{self[:conditions].first}) and ") + "(#{new_options[:conditions].shift})"
+ self[:conditions] += new_options.delete(:conditions)
+ end
+
+ if new_options.has_key?(:include)
+ new_options[:include] = new_options[:include].is_a?(Hash) ? [new_options[:include]] : new_options[:include].to_a
+ self[:include] = self[:include].is_a?(Hash) ? [self[:include]] : self[:include].to_a
+ new_include = new_options.delete(:include)
+ self[:include] += new_include unless self[:include].include?(new_include)
+ end
+
+ new_options[:order] << ", #{self[:order]}" if new_options.has_key?(:order) && !self[:order].blank?
+
+ merge!(new_options)
+ end
+ end
+ end
+end
+
+Hash.send(:include, BinaryLogic::SearchGasm::Hash)
@@ -0,0 +1,100 @@
+require "base64"
+
+module BinaryLogic
+ module SearchGasm
+ module Helpers
+ def order_by_link(text, searcher, options = {})
+ options[:order_by] ||= text.underscore.gsub(/ /, "_")
+ options[:order_as] ||= "ASC"
+ options[:form_prefix] ||= determine_form_prefix(searcher)
+
+ if searcher.order_by == options[:order_by]
+ options[:order_as] = searcher.order_as == "ASC" ? "DESC" : "ASC"
+
+ text = content_tag("span", text + (searcher.order_as == "ASC" ? "&nbsp;&#9650;" : "&nbsp;&#9660;"), :class => searcher.order_as == "ASC" ? "ordering asc" : "ordering desc")
+ end
+
+ options[:order_by] = Base64.encode64(Marshal.dump(options[:order_by])) if !options[:order_by].nil? && !options[:order_by].is_a?(String) && !options[:order_by].is_a?(Symbol)
+
+ link_to_function(text, "submit_form({form_prefix: '#{options[:form_prefix]}', dont_reset: true, fields: {order_by: '#{escape_javascript(options[:order_by])}', order_as: '#{options[:order_as]}'}});")
+ end
+
+ # tag methods
+ #------------------------------------------------------------------------------
+ def page_select_tag(name, items_count, searcher, options = {})
+ options = options.dup
+ form_prefix = options.delete(:form_prefix) || determine_form_prefix(searcher)
+ options[:id] ||= "select_tag_#{unique_id}"
+ options[:onchange] ||= "submit_form({form_prefix: '#{form_prefix}', dont_reset: true, fields: {page: this.value}});"
+ items_count = items_count.to_i
+ per_page = searcher.per_page.to_i
+ page = searcher.page.to_i
+ page = 1 if page < 1
+ page_options = page_options_for_select(items_count, per_page)
+
+ html = ""
+ if page_options.size > 0
+ html << (button_to_function("Prev", "sel = $('#{options[:id]}'); sel.value = '#{page-1}'; sel.onchange();", :class => "prev_page") + "&nbsp;") if page > 1
+ html << select_tag(name, options_for_select(page_options, page), options)
+ html << ("&nbsp;" + button_to_function("Next", "sel = $('#{options[:id]}'); sel.value = '#{page+1}'; sel.onchange();", :class => "next_page")) if page < page_options.size
+ end
+
+ html
+ end
+
+ def per_page_select_tag(name, items_count, searcher, options = {})
+ options = options.dup
+ form_prefix = options.delete(:form_prefix) || determine_form_prefix(searcher)
+ options[:onchange] ||= "submit_form({form_prefix: '#{form_prefix}', dont_reset: true, fields: {per_page: this.value}});"
+ items_count = items_count.to_i
+ per_page = searcher.per_page.to_i
+ per_page = 0 if items_count <= per_page
+
+ # set up per page options
+ per_page_options = per_page_options_for_select(items_count)
+
+ return select_tag(name, options_for_select(per_page_options, per_page), options) if per_page_options.size > 0
+
+ ""
+ end
+
+ # utility methods
+ #------------------------------------------------------------------------------
+ def page_options_for_select(items_count, per_page)
+ page_count = per_page > 0 ? (items_count.to_f / per_page.to_f).ceil : 1
+ page_options = []
+ if page_count > 1
+ page_count.times do |page|
+ page_number = page + 1
+ page_options << ["Page #{page_number} of #{page_count}", page_number]
+ end
+ end
+ page_options
+ end
+
+ def per_page_options_for_select(items_count)
+ per_page_options = []
+ per_page_values = [10, 20, 30, 40, 50, 75, 100, 125, 150, 175, 200, 300, 400, 500, 1000, 1500, 2000]
+ per_page_values.each do |per_page_num|
+ if items_count > per_page_num
+ per_page_options << ["#{per_page_num} per page", per_page_num] if per_page_num > 0
+ else
+ break
+ end
+ end
+
+ if per_page_options.size > 0
+ per_page_options << ["Show all #{items_count}", 0]
+ end
+
+ per_page_options
+ end
+
+ def determine_form_prefix(searcher)
+ "#{searcher.class.name.underscore.gsub(/_searcher/, "").pluralize}_search_"
+ end
+ end
+ end
+end
+
+ActionController::Base.helper BinaryLogic::SearchGasm::Helpers
Oops, something went wrong.

0 comments on commit aa5b90a

Please sign in to comment.