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.
- data/CHANGELOG +2 -0
- data/Manifest +34 -21
- data/{README.mdown → README.rdoc} +96 -63
- data/Rakefile +1 -1
- data/examples/README.rdoc +4 -0
- data/lib/searchgasm/active_record/associations.rb +40 -42
- data/lib/searchgasm/active_record/base.rb +75 -61
- data/lib/searchgasm/condition/base.rb +127 -0
- data/lib/searchgasm/condition/begins_with.rb +20 -0
- data/lib/searchgasm/condition/child_of.rb +11 -0
- data/lib/searchgasm/condition/contains.rb +20 -0
- data/lib/searchgasm/condition/descendant_of.rb +24 -0
- data/lib/searchgasm/condition/does_not_equal.rb +28 -0
- data/lib/searchgasm/condition/ends_with.rb +20 -0
- data/lib/searchgasm/condition/equals.rb +20 -0
- data/lib/searchgasm/condition/greater_than.rb +25 -0
- data/lib/searchgasm/condition/greater_than_or_equal_to.rb +20 -0
- data/lib/searchgasm/condition/inclusive_descendant_of.rb +13 -0
- data/lib/searchgasm/condition/keywords.rb +33 -0
- data/lib/searchgasm/condition/less_than.rb +25 -0
- data/lib/searchgasm/condition/less_than_or_equal_to.rb +20 -0
- data/lib/searchgasm/condition/sibling_of.rb +16 -0
- data/lib/searchgasm/condition/tree.rb +16 -0
- data/lib/searchgasm/conditions/base.rb +221 -0
- data/lib/searchgasm/conditions/protection.rb +30 -0
- data/lib/searchgasm/config.rb +137 -0
- data/lib/searchgasm/helpers/form_helper.rb +159 -0
- data/lib/searchgasm/helpers/search_helper.rb +178 -0
- data/lib/searchgasm/helpers/utilities_helper.rb +125 -0
- data/lib/searchgasm/search/base.rb +73 -179
- data/lib/searchgasm/search/conditions.rb +42 -166
- data/lib/searchgasm/search/ordering.rb +149 -0
- data/lib/searchgasm/search/pagination.rb +69 -0
- data/lib/searchgasm/search/protection.rb +61 -0
- data/lib/searchgasm/utilities.rb +30 -0
- data/lib/searchgasm/version.rb +44 -47
- data/lib/searchgasm.rb +57 -21
- data/searchgasm.gemspec +71 -46
- data/test/test_active_record_associations.rb +1 -1
- data/test/test_active_record_base.rb +4 -4
- data/test/test_condition.rb +143 -0
- data/test/{test_searchgasm_conditions.rb → test_conditions_base.rb} +43 -33
- data/test/test_search_base.rb +189 -0
- data/test/test_search_ordering.rb +91 -0
- data/test/test_search_pagination.rb +56 -0
- data/test/test_search_protection.rb +35 -0
- metadata +70 -45
- data/lib/searchgasm/search/condition.rb +0 -105
- data/lib/searchgasm/search/condition_types/begins_with_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/child_of_condition.rb +0 -17
- data/lib/searchgasm/search/condition_types/contains_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/descendant_of_condition.rb +0 -30
- data/lib/searchgasm/search/condition_types/does_not_equal_condition.rb +0 -34
- data/lib/searchgasm/search/condition_types/ends_with_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/equals_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/greater_than_condition.rb +0 -31
- data/lib/searchgasm/search/condition_types/greater_than_or_equal_to_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/inclusive_descendant_of_condition.rb +0 -19
- data/lib/searchgasm/search/condition_types/keywords_condition.rb +0 -39
- data/lib/searchgasm/search/condition_types/less_than_condition.rb +0 -31
- data/lib/searchgasm/search/condition_types/less_than_or_equal_to_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/sibling_of_condition.rb +0 -22
- data/lib/searchgasm/search/condition_types/tree_condition.rb +0 -20
- data/lib/searchgasm/search/utilities.rb +0 -34
- data/test/test_searchgasm_base.rb +0 -185
- 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: ▼, the indicator that this column is descending
|
30
|
+
# * <tt>:asc_indicator</tt> -- default: ▲, 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?("?") ? "&" : "?")) + 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
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|