searchgasm 0.9.6 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|