searchgasm 0.9.6 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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