searchgasm 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG +2 -0
  2. data/Manifest +34 -21
  3. data/{README.mdown → README.rdoc} +96 -63
  4. data/Rakefile +1 -1
  5. data/examples/README.rdoc +4 -0
  6. data/lib/searchgasm/active_record/associations.rb +40 -42
  7. data/lib/searchgasm/active_record/base.rb +75 -61
  8. data/lib/searchgasm/condition/base.rb +127 -0
  9. data/lib/searchgasm/condition/begins_with.rb +20 -0
  10. data/lib/searchgasm/condition/child_of.rb +11 -0
  11. data/lib/searchgasm/condition/contains.rb +20 -0
  12. data/lib/searchgasm/condition/descendant_of.rb +24 -0
  13. data/lib/searchgasm/condition/does_not_equal.rb +28 -0
  14. data/lib/searchgasm/condition/ends_with.rb +20 -0
  15. data/lib/searchgasm/condition/equals.rb +20 -0
  16. data/lib/searchgasm/condition/greater_than.rb +25 -0
  17. data/lib/searchgasm/condition/greater_than_or_equal_to.rb +20 -0
  18. data/lib/searchgasm/condition/inclusive_descendant_of.rb +13 -0
  19. data/lib/searchgasm/condition/keywords.rb +33 -0
  20. data/lib/searchgasm/condition/less_than.rb +25 -0
  21. data/lib/searchgasm/condition/less_than_or_equal_to.rb +20 -0
  22. data/lib/searchgasm/condition/sibling_of.rb +16 -0
  23. data/lib/searchgasm/condition/tree.rb +16 -0
  24. data/lib/searchgasm/conditions/base.rb +221 -0
  25. data/lib/searchgasm/conditions/protection.rb +30 -0
  26. data/lib/searchgasm/config.rb +137 -0
  27. data/lib/searchgasm/helpers/form_helper.rb +159 -0
  28. data/lib/searchgasm/helpers/search_helper.rb +178 -0
  29. data/lib/searchgasm/helpers/utilities_helper.rb +125 -0
  30. data/lib/searchgasm/search/base.rb +73 -179
  31. data/lib/searchgasm/search/conditions.rb +42 -166
  32. data/lib/searchgasm/search/ordering.rb +149 -0
  33. data/lib/searchgasm/search/pagination.rb +69 -0
  34. data/lib/searchgasm/search/protection.rb +61 -0
  35. data/lib/searchgasm/utilities.rb +30 -0
  36. data/lib/searchgasm/version.rb +44 -47
  37. data/lib/searchgasm.rb +57 -21
  38. data/searchgasm.gemspec +71 -46
  39. data/test/test_active_record_associations.rb +1 -1
  40. data/test/test_active_record_base.rb +4 -4
  41. data/test/test_condition.rb +143 -0
  42. data/test/{test_searchgasm_conditions.rb → test_conditions_base.rb} +43 -33
  43. data/test/test_search_base.rb +189 -0
  44. data/test/test_search_ordering.rb +91 -0
  45. data/test/test_search_pagination.rb +56 -0
  46. data/test/test_search_protection.rb +35 -0
  47. metadata +70 -45
  48. data/lib/searchgasm/search/condition.rb +0 -105
  49. data/lib/searchgasm/search/condition_types/begins_with_condition.rb +0 -26
  50. data/lib/searchgasm/search/condition_types/child_of_condition.rb +0 -17
  51. data/lib/searchgasm/search/condition_types/contains_condition.rb +0 -26
  52. data/lib/searchgasm/search/condition_types/descendant_of_condition.rb +0 -30
  53. data/lib/searchgasm/search/condition_types/does_not_equal_condition.rb +0 -34
  54. data/lib/searchgasm/search/condition_types/ends_with_condition.rb +0 -26
  55. data/lib/searchgasm/search/condition_types/equals_condition.rb +0 -26
  56. data/lib/searchgasm/search/condition_types/greater_than_condition.rb +0 -31
  57. data/lib/searchgasm/search/condition_types/greater_than_or_equal_to_condition.rb +0 -26
  58. data/lib/searchgasm/search/condition_types/inclusive_descendant_of_condition.rb +0 -19
  59. data/lib/searchgasm/search/condition_types/keywords_condition.rb +0 -39
  60. data/lib/searchgasm/search/condition_types/less_than_condition.rb +0 -31
  61. data/lib/searchgasm/search/condition_types/less_than_or_equal_to_condition.rb +0 -26
  62. data/lib/searchgasm/search/condition_types/sibling_of_condition.rb +0 -22
  63. data/lib/searchgasm/search/condition_types/tree_condition.rb +0 -20
  64. data/lib/searchgasm/search/utilities.rb +0 -34
  65. data/test/test_searchgasm_base.rb +0 -185
  66. data/test/test_searchgasm_condition_types.rb +0 -143
@@ -0,0 +1,178 @@
1
+ module Searchgasm
2
+ module Helpers
3
+ # = Search Helper
4
+ #
5
+ # Helper methods for paginating and ordering through a search.
6
+ module SearchHelper
7
+ # Creates a link for ordering data in a certain way. See Searchgasm::Config for setting default configuration.
8
+ #
9
+ # === Example uses for a User class that has many orders
10
+ # order_by(:first_name)
11
+ # order_by([:first_name, :last_name])
12
+ # order_by({:orders => :total})
13
+ # order_bt([{:orders => :total}, :first_name])
14
+ #
15
+ # 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
16
+ # for doing whatever you need to do. It's the options you are allowed to pass, but with their proper values.
17
+ #
18
+ # <%= order_by(:id) { |options| link_to(options[:text], options[:url], options[:html]) } %>
19
+ #
20
+ # or
21
+ #
22
+ # <% order_by(:id) do |options| %><%= link_to(options[:text], options[:url]) %><% end %>
23
+ #
24
+ # 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
25
+ # to pass complex objects besides strings and symbols, such as arrays and hashes.
26
+ #
27
+ # === Options
28
+ # * <tt>:text</tt> -- default: column_name.to_s.humanize, text for the link
29
+ # * <tt>:desc_indicator</tt> -- default: &nbsp;&#9660;, the indicator that this column is descending
30
+ # * <tt>:asc_indicator</tt> -- default: &nbsp;&#9650;, the indicator that this column is ascending
31
+ #
32
+ # === Advanced Options
33
+ # * <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
34
+ # * <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
35
+ # * <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
36
+ # * <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
37
+ # * <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.
38
+ def order_by(column_name, options = {}, &block)
39
+ add_searchgasm_helper_defaults!(options, :order_by, column_name)
40
+ column_name = stringify_everything(column_name)
41
+ options[:text] = determine_order_by_text(column_name) unless options.has_key?(:text)
42
+ options[:asc_indicator] ||= Config.asc_indicator
43
+ options[:desc_indicator] ||= Config.desc_indicator
44
+ options[:text] += options[:search_obj].desc? ? options[:desc_indicator] : options[:asc_indicator] if options[:search_obj].order_by == column_name
45
+
46
+ if block_given?
47
+ yield options
48
+ else
49
+ if options[:remote]
50
+ link_to_function(options[:text], options[:url], options[:html])
51
+ else
52
+ link_to(options[:text], options[:url], options[:html])
53
+ end
54
+ end
55
+ end
56
+
57
+ # Creates navigation for paginating through a search. See Searchgasm::Config for setting default configuration.
58
+ #
59
+ # === Examples
60
+ # pages
61
+ # pages(:search => @my_search)
62
+ # pages(:html => {:id => "my_id"})
63
+ #
64
+ # 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
65
+ # for doing whatever you need to do. It's the options you are allowed to pass, but with their proper values.
66
+ #
67
+ # <%= pages { |options| select(:search, :page, (1..options[:search_obj].page_count), {}, options[:html]) } %>
68
+ #
69
+ # or
70
+ #
71
+ # <% pages do |options| %><%= select(:search, :page, (1..options[:search_obj].page_count), {}, options[:html]) %><% end %>
72
+ #
73
+ # === Options
74
+ # * <tt>:type</tt> -- default: :select, pass :links as an alternative to have flickr like pagination
75
+ # * <tt>:remote</tt> -- default: false, if true requests will be AJAX
76
+ #
77
+ # === Advanced Options
78
+ # * <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
79
+ # * <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
80
+ # * <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
81
+ # * <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
82
+ # * <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.
83
+ def pages(options = {})
84
+ options[:type] ||= Config.pages_type
85
+ add_searchgasm_helper_defaults!(options, :page)
86
+ return "" if options[:search_obj].page_count <= 1
87
+
88
+ if block_given?
89
+ yield options
90
+ else
91
+ case options[:type]
92
+ when :select
93
+ options[:html] ||= {}
94
+ options[:html][options[:action]] ||= ""
95
+ options[:html][options[:action]] += ";"
96
+ options[:html][options[:action]] += options[:url]
97
+ select(:search, :page, (1..options[:search_obj].page_count), {}, options[:html])
98
+ else
99
+ # HTML for links
100
+ end
101
+ end
102
+ end
103
+
104
+ # Creates navigation for setting how many items per page. See Searchgasm::Config for setting default configuration.
105
+ #
106
+ # === Examples
107
+ # per_page
108
+ # per_page(:search => @my_search)
109
+ # per_page(:choices => [50, 100])
110
+ # per_page(:html => {:id => "my_id"})
111
+ #
112
+ # 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
113
+ # for doing whatever you need to do. It's the options you are allowed to pass, but with their proper values.
114
+ #
115
+ # <%= per_page { |options| select(:search, :per_page, options[:choices], {}, options[:html]) } %>
116
+ #
117
+ # or
118
+ #
119
+ # <% per_page do |options| %><%= select(:search, :per_page, options[:choices], {}, options[:html]) %><% end %>
120
+ #
121
+ # === Options
122
+ # * <tt>:type</tt> -- default: :select, pass :links as an alternative to links like: 10 | 25 | 50 | 100, etc
123
+ # * <tt>:remote</tt> -- default: false, if true requests will be AJAX
124
+ # * <tt>:choices</tt> -- default: [10, 25, 50, 100, 150, 200, nil], nil means "show all"
125
+ #
126
+ # === Advanced Options
127
+ # * <tt>:choices</tt> -- default: [10, 25, 50, 100, 150, 200, nil], nil means "show all"
128
+ # * <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
129
+ # * <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
130
+ # * <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
131
+ # * <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
132
+ # * <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.
133
+ def per_page(options = {})
134
+ options[:type] ||= Config.per_page_type
135
+ add_searchgasm_helper_defaults!(options, :per_page)
136
+
137
+ options[:choices] ||= Config.per_page_choices
138
+ if !options[:search_obj].per_page.blank? && !options[:choices].include?(options[:search_obj].per_page)
139
+ options[:choices] << options[:search_obj].per_page
140
+ has_nil = options[:choices].include?(nil)
141
+ options[:choices].delete(nil) if has_nil
142
+ options[:choices].sort!
143
+ options[:choices] << nil if has_nil
144
+ end
145
+ options[:choices] = options[:choices].collect { |choice| [choice == nil ? "Show all" : "#{choice} per page", choice] }
146
+
147
+ if block_given?
148
+ yield options
149
+ else
150
+ case options[:type]
151
+ when :select
152
+ options[:html] ||= {}
153
+ options[:html][options[:action]] ||= ""
154
+ options[:html][options[:action]] += ";"
155
+ options[:html][options[:action]] += options[:url]
156
+ select(:search, :per_page, options[:choices], {}, options[:html])
157
+ end
158
+ end
159
+ end
160
+
161
+ private
162
+ def determine_order_by_text(column_name, relationship_name = nil)
163
+ case column_name
164
+ when String, Symbol
165
+ relationship_name.blank? ? column_name.titleize : "#{relationship_name.titleize} #{column_name.titleize}"
166
+ when Array
167
+ determine_order_by_text(column_name.last)
168
+ when Hash
169
+ k = column_name.keys.first
170
+ v = column_name.values.first
171
+ determine_order_by_text(v, k)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ ActionController::Base.helper Searchgasm::Helpers::SearchHelper if defined?(ActionController)
@@ -0,0 +1,125 @@
1
+ module Searchgasm
2
+ module Helpers #:nodoc:
3
+ module UtilitiesHelper # :nodoc:
4
+ private
5
+ # Adds default options for all helper methods.
6
+ def add_searchgasm_helper_defaults!(options, method_name, method_value = nil)
7
+ options[:search_obj] ||= instance_variable_get(Config.search_obj_name)
8
+ raise(ArgumentError, "@search object could not be inferred, please specify: :search_obj => @search)") unless options[:search_obj].is_a?(Searchgasm::Search::Base)
9
+ method_value = stringify_everything(method_value) unless method_value.nil?
10
+ options[:params_scope] = :search unless options.has_key?(:params_scope)
11
+ options[:remote] = Config.remote_helpers? unless options.has_key?(:remote)
12
+
13
+ if !options.has_key?(:action)
14
+ if options[:type] == :select
15
+ options[:action] = :onchange
16
+ elsif options[:remote]
17
+ options[:action] = :onclick
18
+ end
19
+ end
20
+
21
+ options[:url] = searchgasm_url(options, method_name, method_value) unless options.has_key?(:url)
22
+
23
+ options
24
+ end
25
+
26
+ def searchgasm_url(options, method_name, method_value = nil)
27
+ params = (params || {}).dup
28
+ params.delete(:commit)
29
+
30
+ # Extract search params from params
31
+ search_params = options[:params_scope].blank? ? params : params[options[:params_scope]] ||= {}
32
+
33
+ # Rewrite :order_by and :per_page with what's in our search obj
34
+ ([:order_by, :per_page] - [method_name]).each { |search_option| search_params[search_option] = options[:search_obj].send(search_option) }
35
+
36
+ # Rewrite :conditions, separated due to unique call
37
+ conditions = options[:search_obj].conditions.conditions
38
+ search_params[:conditions] = conditions unless conditions.blank?
39
+
40
+ # Never want to keep page or the option we are trying to set
41
+ [:page, method_name].each { |option| search_params.delete(option) }
42
+
43
+ # Alternate :order_by if we are ordering
44
+ if method_name == :order_by
45
+ search_params[:order_as] = (options[:search_obj].order_by == method_value && options[:search_obj].asc?) ? "DESC" : "ASC"
46
+ else
47
+ search_params[:order_as] = options[:search_obj].order_as
48
+ end
49
+
50
+ # Determine if this.value should be included or not, and set up url
51
+ url = nil
52
+ case options[:action]
53
+ when :onchange
54
+ # Include this.value
55
+ url = url_for(params)
56
+ url_option = CGI.escape((options[:params_scope].blank? ? "#{method_name}" : "#{options[:params_scope]}[#{method_name}]")) + "='+this.value"
57
+ url += (url.last == "?" ? "" : (url.include?("?") ? "&amp;" : "?")) + url_option
58
+ else
59
+ # Build the plain URL
60
+ search_params[method_name] = method_name == :order_by ? searchgasm_order_by_value(method_value) : method_value
61
+ url = url_for(params)
62
+ end
63
+
64
+ # Now update options if remote
65
+ if options[:remote]
66
+ url = remote_function(:url => url, :method => :get).gsub(/\\'\+this.value'/, "'+this.value") + ";"
67
+
68
+ update_fields = {method_name => method_value}
69
+ update_fields[:order_as] = search_params[:order_as] if method_name == :order_by
70
+ update_fields.each { |field, value| url += ";" + searchgasm_update_search_field_javascript(field, value, options) }
71
+ elsif !options[:action].blank?
72
+ # Add some javascript if its onclick
73
+ url = "window.location = '" + url + ";"
74
+ end
75
+
76
+ url
77
+ end
78
+
79
+ def searchgasm_update_search_field_javascript(field, value, options)
80
+ field_value = nil
81
+
82
+ case options[:action]
83
+ when :onchange
84
+ field_value = "this.value";
85
+ else
86
+ field_value = field == :order_by ? searchgasm_order_by_value(value) : value
87
+ field_value = "'#{CGI.escape(field_value)}'"
88
+ end
89
+
90
+ field_name = options[:params_scope] ? "#{options[:params_scope]}[#{field}]" : "#{field}"
91
+ "#{field} = $('#{searchgasm_form_id(options[:search_obj])}').getInputs('hidden', '#{field_name}'); if(#{field}.length > 0) { #{field}[0].value = #{field_value}; }"
92
+ end
93
+
94
+ def searchgasm_form_id(search_obj)
95
+ "#{search_obj.klass.name.pluralize.underscore}_search_form"
96
+ end
97
+
98
+ def searchgasm_order_by_value(order_by)
99
+ case order_by
100
+ when String
101
+ order_by
102
+ when Array, Hash
103
+ [Marshal.dump(order_by)].pack("m")
104
+ end
105
+ end
106
+
107
+ def stringify_everything(obj)
108
+ case obj
109
+ when String
110
+ obj
111
+ when Symbol
112
+ obj = obj.to_s
113
+ when Array
114
+ obj = obj.collect { |item| stringify_everything(item) }
115
+ when Hash
116
+ new_obj = {}
117
+ obj.each { |key, value| new_obj[key.to_s] = stringify_everything(value) }
118
+ new_obj
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ ActionController::Base.helper(Searchgasm::Helpers::UtilitiesHelper) if defined?(ActionController)
@@ -1,183 +1,77 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- class Base
5
- include Utilities
6
-
7
- SPECIAL_FIND_OPTIONS = [:order_by, :order_as, :page, :per_page]
8
- VALID_FIND_OPTIONS = ::ActiveRecord::Base.valid_find_options + SPECIAL_FIND_OPTIONS
9
- SAFE_OPTIONS = SPECIAL_FIND_OPTIONS + [:conditions, :limit, :offset]
10
- VULNERABLE_OPTIONS = VALID_FIND_OPTIONS - SAFE_OPTIONS
11
-
12
- attr_accessor :klass
13
- attr_reader :conditions, :protect
14
- attr_writer :options
15
-
16
- def self.needed?(klass, options)
17
- SPECIAL_FIND_OPTIONS.each do |option|
18
- return true if options.symbolize_keys.keys.include?(option)
19
- end
20
-
21
- Conditions.needed?(klass, options[:conditions])
22
- end
23
-
24
- def initialize(klass, options = {})
25
- self.klass = klass
26
- self.conditions = Conditions.new(klass)
27
- self.options = options
28
- end
29
-
30
- # Setup methods for all options for finding
31
- (::ActiveRecord::Base.valid_find_options - [:conditions]).each do |option|
32
- class_eval <<-end_eval
33
- def #{option}(sanitize = false); options[:#{option}]; end
34
- def #{option}=(value); self.options[:#{option}] = value; end
35
- end_eval
36
- end
37
-
38
- alias_method :per_page, :limit
39
-
40
- # Setup methods for searching
41
- [:all, :average, :calculate, :count, :find, :first, :maximum, :minimum, :sum].each do |method|
42
- class_eval <<-end_eval
43
- def #{method}(*args)
44
- self.options = args.extract_options!
45
- args << sanitize
46
- klass.#{method}(*args)
47
- end
48
- end_eval
49
- end
50
-
51
- def conditions=(value)
52
- case value
53
- when Conditions
54
- @conditions = value
55
- else
56
- @conditions.value = value
57
- end
58
- end
59
-
60
- def conditions(sanitize = false)
61
- sanitize ? @conditions.sanitize : @conditions
62
- end
63
-
64
- def include(sanitize = false)
65
- includes = [self.options[:include], conditions.includes].flatten.compact
66
- includes.blank? ? nil : (includes.size == 1 ? includes.first : includes)
67
- end
68
-
69
- def inspect
70
- options_as_nice_string = ::ActiveRecord::Base.valid_find_options.collect { |name| "#{name}: #{send(name)}" }.join(", ")
71
- "#<#{klass} #{options_as_nice_string}>"
72
- end
73
-
74
- def limit=(value)
75
- return options[:limit] = nil if value.nil? || value == 0
76
-
77
- old_limit = options[:limit]
78
- options[:limit] = value
79
- self.page = @page if !@page.blank? # retry page now that limit is set
80
- value
81
- end
82
- alias_method :per_page=, :limit=
83
-
84
- def options
85
- @options ||= {}
86
- end
87
-
88
- def options=(values)
89
- return unless values.is_a?(Hash)
90
- self.protect = values.delete(:protect) if values.has_key?(:protect) # make sure we do this first
91
- values.symbolize_keys.assert_valid_keys(VALID_FIND_OPTIONS)
92
- frisk!(values) if protect?
93
- values.each { |option, value| send("#{option}=", value) }
94
- end
95
-
96
- def order_as
97
- return "DESC" if order.blank?
98
- order =~ /ASC$/i ? "ASC" : "DESC"
99
- end
100
-
101
- def order_as=(value)
102
- # reset order
103
- end
104
-
105
- def order_by
106
- # need to return a cached value of order_by, not smart to figure it out from order
107
- end
108
-
109
- def order_by=(value)
110
- # do your magic here and set order approperiately
111
- end
112
-
113
- def page
114
- return 1 if offset.blank?
115
- (offset.to_f / limit).ceil
116
- end
117
-
118
- def page=(value)
119
- return self.offset = nil if value.nil?
120
-
121
- if limit.blank?
122
- @page = value
123
- else
124
- @page = nil
125
- self.offset = value * limit
126
- end
127
- value
128
- end
129
-
130
- def protect=(value)
131
- conditions.protect = value
132
- @protect = value
133
- end
134
-
135
- def protect?
136
- protect == true
137
- end
138
-
139
- def sanitize
140
- find_options = {}
141
- ::ActiveRecord::Base.valid_find_options.each do |find_option|
142
- value = send(find_option, true)
143
- next if value.blank?
144
- find_options[find_option] = value
145
- end
146
- find_options
147
- end
148
-
149
- def scope
150
- conditions.scope
151
- end
152
-
153
- def scope=(value)
154
- conditions.scope = value
155
- end
156
-
157
- private
158
- def order_by_safe?(order_by)
159
- return true if order_by.blank?
160
-
161
- column_names = klass.column_names
162
-
163
- [order_by].flatten.each do |column|
164
- case column
165
- when Hash
166
- return false unless order_by_safe?(column.to_a)
167
- when Array
168
- return false unless order_by_safe?(column)
169
- else
170
- return false unless column_names.include?(column)
171
- end
172
- end
173
-
174
- true
175
- end
176
-
177
- def frisk!(options)
178
- options.symbolize_keys.assert_valid_keys(SAFE_OPTIONS)
179
- raise(ArgumentError, ":order_by can only contain colum names in the string, hash, or array format") unless order_by_safe?(options[:order_by])
1
+ module Searchgasm #:nodoc:
2
+ module Search #:nodoc:
3
+ # = Searchgasm
4
+ #
5
+ # Please refer the README.rdoc for usage, examples, and installation.
6
+
7
+ class Base
8
+ include Searchgasm::Utilities
9
+
10
+ # Options that ActiveRecord doesn't suppport, but Searchgasm does
11
+ SPECIAL_FIND_OPTIONS = [:order_by, :order_as, :page, :per_page]
12
+
13
+ # Valid options you can use when searching
14
+ VALID_FIND_OPTIONS = ::ActiveRecord::Base.valid_find_options + SPECIAL_FIND_OPTIONS
15
+
16
+ # Use these methods just like you would in ActiveRecord
17
+ SEARCH_METHODS = [:all, :average, :calculate, :count, :find, :first, :maximum, :minimum, :sum]
18
+
19
+ attr_accessor :klass, *::ActiveRecord::Base.valid_find_options
20
+
21
+ # Used in the ActiveRecord methods to determine if Searchgasm should get involved or not.
22
+ # This keeps Searchgasm out of the way unless it is needed.
23
+ def self.needed?(klass, options)
24
+ SPECIAL_FIND_OPTIONS.each do |option|
25
+ return true if options.symbolize_keys.keys.include?(option)
26
+ end
27
+
28
+ Searchgasm::Conditions::Base.needed?(klass, options[:conditions])
29
+ end
30
+
31
+ def initialize(klass, init_options = {})
32
+ self.klass = klass
33
+ self.options = init_options
34
+ end
35
+
36
+ # Setup methods for searching
37
+ SEARCH_METHODS.each do |method|
38
+ class_eval <<-"end_eval", __FILE__, __LINE__
39
+ def #{method}(*args)
40
+ self.options = args.extract_options!
41
+ args << sanitize(:#{method})
42
+ klass.#{method}(*args)
180
43
  end
44
+ end_eval
45
+ end
46
+
47
+ def inspect
48
+ options_as_nice_string = ::ActiveRecord::Base.valid_find_options.collect { |name| "#{name}: #{send(name)}" }.join(", ")
49
+ "#<#{klass} #{options_as_nice_string}>"
50
+ end
51
+
52
+ def limit=(value)
53
+ @limit = value.blank? || value == 0 ? nil : value.to_i
54
+ end
55
+
56
+ def offset=(value)
57
+ @offset = value.to_i
58
+ end
59
+
60
+ def options=(values)
61
+ return unless values.is_a?(Hash)
62
+ values.symbolize_keys.assert_valid_keys(VALID_FIND_OPTIONS)
63
+ values.each { |option, value| send("#{option}=", value) }
64
+ end
65
+
66
+ # Sanitizes everything down into options ActiveRecord::Base.find can understand
67
+ def sanitize(for_method = nil)
68
+ find_options = {}
69
+ ::ActiveRecord::Base.valid_find_options.each do |find_option|
70
+ value = send(find_option)
71
+ next if value.blank? || (for_method == :count && [:limit, :offset].include?(find_option))
72
+ find_options[find_option] = value
73
+ end
74
+ find_options
181
75
  end
182
76
  end
183
77
  end