searchgasm 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +64 -0
- data/Manifest +3 -1
- data/README.rdoc +19 -29
- data/lib/searchgasm/active_record/associations.rb +10 -1
- data/lib/searchgasm/active_record/base.rb +60 -10
- data/lib/searchgasm/active_record.rb +8 -0
- data/lib/searchgasm/conditions/base.rb +66 -19
- data/lib/searchgasm/core_ext/hash.rb +22 -0
- data/lib/searchgasm/helpers/control_types/links.rb +65 -31
- data/lib/searchgasm/helpers/control_types/select.rb +1 -1
- data/lib/searchgasm/helpers/control_types.rb +18 -2
- data/lib/searchgasm/helpers/form.rb +10 -4
- data/lib/searchgasm/helpers/utilities.rb +27 -2
- data/lib/searchgasm/helpers.rb +7 -1
- data/lib/searchgasm/search/base.rb +38 -9
- data/lib/searchgasm/search/conditions.rb +26 -6
- data/lib/searchgasm/search/ordering.rb +14 -3
- data/lib/searchgasm/search/pagination.rb +52 -7
- data/lib/searchgasm/search/protection.rb +25 -3
- data/lib/searchgasm/version.rb +1 -1
- data/lib/searchgasm.rb +3 -0
- data/searchgasm.gemspec +9 -5
- data/test/test_condition_base.rb +4 -0
- data/test/test_conditions_base.rb +10 -10
- data/test/test_search_base.rb +13 -9
- data/test/test_search_conditions.rb +2 -2
- data/test/test_search_ordering.rb +2 -2
- data/test/test_search_pagination.rb +3 -3
- metadata +8 -4
- data/CHANGELOG +0 -23
@@ -16,10 +16,10 @@ module Searchgasm
|
|
16
16
|
# * <tt>:choices</tt> -- default: the models column names, the choices to loop through when calling order_by_link
|
17
17
|
def order_by_links(options = {})
|
18
18
|
add_order_by_links_defaults!(options)
|
19
|
-
link_options = options.
|
19
|
+
link_options = options.deep_dup
|
20
20
|
link_options.delete(:choices)
|
21
21
|
html = ""
|
22
|
-
options[:choices].each { |choice| html += order_by_link(choice, link_options.
|
22
|
+
options[:choices].each { |choice| html += order_by_link(choice, link_options.deep_dup) }
|
23
23
|
html
|
24
24
|
end
|
25
25
|
|
@@ -37,10 +37,10 @@ module Searchgasm
|
|
37
37
|
# * <tt>:choices</tt> -- default: ["asc", "desc"], the choices to loop through when calling order_as_link
|
38
38
|
def order_as_links(options = {})
|
39
39
|
add_order_as_links_defaults!(options)
|
40
|
-
link_options = options.
|
40
|
+
link_options = options.deep_dup
|
41
41
|
link_options.delete(:choices)
|
42
42
|
html = ""
|
43
|
-
options[:choices].each { |choice| html += order_as_link(choice, link_options.
|
43
|
+
options[:choices].each { |choice| html += order_as_link(choice, link_options.deep_dup) }
|
44
44
|
html
|
45
45
|
end
|
46
46
|
|
@@ -58,10 +58,10 @@ module Searchgasm
|
|
58
58
|
# * <tt>:choices</tt> -- default: [10, 25, 50, 100, 150, 200, nil], the choices to loop through when calling per_page_link.
|
59
59
|
def per_page_links(options = {})
|
60
60
|
add_per_page_links_defaults!(options)
|
61
|
-
link_options = options.
|
61
|
+
link_options = options.deep_dup
|
62
62
|
link_options.delete(:choices)
|
63
63
|
html = ""
|
64
|
-
options[:choices].each { |choice| html += per_page_link(choice, link_options.
|
64
|
+
options[:choices].each { |choice| html += per_page_link(choice, link_options.deep_dup) }
|
65
65
|
html
|
66
66
|
end
|
67
67
|
|
@@ -70,38 +70,53 @@ module Searchgasm
|
|
70
70
|
# === Examples
|
71
71
|
#
|
72
72
|
# page_links
|
73
|
-
# page_links(:
|
73
|
+
# page_links(:first => "<< First", :last => "Last >>")
|
74
|
+
#
|
75
|
+
# === Classes and tag
|
76
|
+
#
|
77
|
+
# If the user is on the current page they will get a <span> tag, not an <a> tag. If they are on the first page the "first" and "prev" options will be a <span> also. The same goes
|
78
|
+
# for "next" and "last" if the user is on the last page. Other than that each element will come with a CSS class so you can style it to your liking. Somtimes the easiest way to understand this
|
79
|
+
# Is to either look at the example (linked in the README) or try it out and view the HTML source. It's pretty simple, but here are the explanations:
|
80
|
+
#
|
81
|
+
# * <tt>page</tt> - This is in *every* element, span or a.
|
82
|
+
# * <tt>first_page</tt> - This is for the "first page" element only.
|
83
|
+
# * <tt>preve_page</tt> - This is for the "prev page" element only.
|
84
|
+
# * <tt>current_page</tt> - This is for the current page element
|
85
|
+
# * <tt>next_page</tt> - This is for the "next page" element only.
|
86
|
+
# * <tt>last_page</tt> - This is for the "last page" element only.
|
87
|
+
# * <tt>disabled_page</tt> - Any element that is a span instead of an a tag.
|
74
88
|
#
|
75
89
|
# === Options
|
76
90
|
#
|
77
91
|
# Please look at per_page_link. All options there are applicable here and are passed onto each option.
|
78
92
|
#
|
79
|
-
# * <tt>:spread</tt> -- default: 3, how many choices available on each side of the current page
|
80
|
-
# * <tt>:prev</tt> -- default:
|
81
|
-
# * <tt>:next</tt> -- default: >, set to nil to omit. This is an extra link on the right side of the page links that will go to the next page
|
82
|
-
# * <tt>:first</tt> -- default:
|
83
|
-
# * <tt>:last</tt> -- default:
|
93
|
+
# * <tt>:spread</tt> -- default: 3, set to nil to show all page, this represents how many choices available on each side of the current page
|
94
|
+
# * <tt>:prev</tt> -- default: < Prev, set to nil to omit. This is an extra link on the left side of the page links that will go to the previous page
|
95
|
+
# * <tt>:next</tt> -- default: Next >, set to nil to omit. This is an extra link on the right side of the page links that will go to the next page
|
96
|
+
# * <tt>:first</tt> -- default: nil, set to nil to omit. This is an extra link on thefar left side of the page links that will go to the first page
|
97
|
+
# * <tt>:last</tt> -- default: nil, set to nil to omit. This is an extra link on the far right side of the page links that will go to the last page
|
84
98
|
def page_links(options = {})
|
85
99
|
add_page_links_defaults!(options)
|
100
|
+
return if options[:last_page] <= 1
|
86
101
|
|
87
|
-
|
88
|
-
|
89
|
-
page_end = 0
|
102
|
+
first_page = 0
|
103
|
+
last_page = 0
|
90
104
|
if !options[:spread].blank?
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
105
|
+
first_page = options[:current_page] - options[:spread]
|
106
|
+
first_page = options[:first_page] if first_page < options[:first_page]
|
107
|
+
last_page = options[:current_page] + options[:spread]
|
108
|
+
last_page = options[:last_page] if last_page > options[:last_page]
|
95
109
|
else
|
96
|
-
|
97
|
-
|
110
|
+
first_page = options[:first_page]
|
111
|
+
last_page = options[:last_page]
|
98
112
|
end
|
99
113
|
|
100
|
-
|
101
|
-
link_options = options.dup
|
102
|
-
[:choices, :spread, :prev, :next, :first, :last].each { |option| link_options.delete(option) }
|
103
114
|
html = ""
|
104
|
-
|
115
|
+
html += span_or_page_link(:first, options.deep_dup, options[:current_page] == options[:first_page]) if options[:first]
|
116
|
+
html += span_or_page_link(:prev, options.deep_dup, options[:current_page] == options[:first_page]) if options[:prev]
|
117
|
+
(first_page..last_page).each { |page| html += span_or_page_link(page, options.deep_dup, page == options[:current_page]) }
|
118
|
+
html += span_or_page_link(:next, options.deep_dup, options[:current_page] == options[:last_page]) if options[:next]
|
119
|
+
html += span_or_page_link(:last, options.deep_dup, options[:current_page] == options[:last_page]) if options[:last]
|
105
120
|
html
|
106
121
|
end
|
107
122
|
|
@@ -133,14 +148,33 @@ module Searchgasm
|
|
133
148
|
|
134
149
|
def add_page_links_defaults!(options)
|
135
150
|
add_searchgasm_helper_defaults!(:page, options)
|
136
|
-
options[:
|
137
|
-
options[:
|
138
|
-
options[:
|
139
|
-
options[:
|
140
|
-
options[:
|
141
|
-
options[:
|
151
|
+
options[:first_page] ||= 1
|
152
|
+
options[:last_page] ||= options[:search_obj].page_count
|
153
|
+
options[:current_page] ||= options[:search_obj].page
|
154
|
+
options[:spread] = 3 unless options.has_key?(:spread)
|
155
|
+
options[:prev] = "< Prev" unless options.has_key?(:prev)
|
156
|
+
options[:next] = "Next >" unless options.has_key?(:next)
|
142
157
|
options
|
143
158
|
end
|
159
|
+
|
160
|
+
def span_or_page_link(name, options, span)
|
161
|
+
text = ""
|
162
|
+
page = 0
|
163
|
+
case name
|
164
|
+
when Fixnum
|
165
|
+
text = name
|
166
|
+
page = name
|
167
|
+
searchgasm_add_class!(options[:html], "current_page") if span
|
168
|
+
else
|
169
|
+
text = options[name]
|
170
|
+
page = options[:search_obj].send("#{name}_page")
|
171
|
+
searchgasm_add_class!(options[:html], "#{name}_page")
|
172
|
+
end
|
173
|
+
|
174
|
+
searchgasm_add_class!(options[:html], "disabled_page") if span
|
175
|
+
options[:text] = text
|
176
|
+
span ? content_tag(:span, text, options[:html]) : page_link(page, options)
|
177
|
+
end
|
144
178
|
end
|
145
179
|
end
|
146
180
|
end
|
@@ -23,7 +23,7 @@ module Searchgasm
|
|
23
23
|
# Please see page_links. All options are the same and applicable here, excep the :prev, :next, :first, and :last options. The only difference is that instead of a group of links, this gets returned as a select form element that will perform the same function when the value is changed.
|
24
24
|
def page_select(options = {})
|
25
25
|
add_page_select_defaults!(options)
|
26
|
-
searchgasm_state_for(:page, options) + select(options[:params_scope], :page, options[:
|
26
|
+
searchgasm_state_for(:page, options) + select(options[:params_scope], :page, (options[:first_page]..options[:last_page]), options[:tag] || {}, options[:html])
|
27
27
|
end
|
28
28
|
|
29
29
|
private
|
@@ -4,9 +4,13 @@ module Searchgasm
|
|
4
4
|
#
|
5
5
|
# The purpose of these helpers is to make ordering and paginating data, in your view, a breeze. Everyone has their own flavor of displaying data, so I made these helpers extra flexible, just for you.
|
6
6
|
#
|
7
|
+
# === Tutorial
|
8
|
+
#
|
9
|
+
# Check out my tutorial on how to implement searchgasm into a rails app: http://www.binarylogic.com/2008/9/7/tutorial-pagination-ordering-and-searching-with-searchgasm
|
10
|
+
#
|
7
11
|
# === How it's organized
|
8
12
|
#
|
9
|
-
#
|
13
|
+
# If we break it down, you can do 4 different things with your data in your view:
|
10
14
|
#
|
11
15
|
# 1. Order your data by a single column or an array of columns
|
12
16
|
# 2. Descend or ascend your data
|
@@ -16,8 +20,9 @@ module Searchgasm
|
|
16
20
|
# Each one of these actions comes with 3 different types of helpers:
|
17
21
|
#
|
18
22
|
# 1. Link - A single link for a single value. Requires that you pass a value as the first parameter.
|
19
|
-
# 2.
|
23
|
+
# 2. Links - A group of single links.
|
20
24
|
# 3. Select - A select with choices that perform an action once selected. Basically the same thing as a group of links, but just as a select form element
|
25
|
+
# 4. Remote - lets you prefix any of these helpers with "remote_" and it will use the built in rails ajax helpers. I highly recommend unobstrusive javascript though, using jQuery.
|
21
26
|
#
|
22
27
|
# === Examples
|
23
28
|
#
|
@@ -33,6 +38,17 @@ module Searchgasm
|
|
33
38
|
# => produces a select form element with all of the user's columns as choices, when the value is change (onchange) it will act as if they clicked a link.
|
34
39
|
# => This is just order_by_links as a select form element, nothing fancy
|
35
40
|
#
|
41
|
+
# What about paginating? I got you covered:
|
42
|
+
#
|
43
|
+
# page_link(2)
|
44
|
+
# => creates a link to page 2
|
45
|
+
#
|
46
|
+
# page_links
|
47
|
+
# => creates a group of links for pages, similar to a flickr style of pagination
|
48
|
+
#
|
49
|
+
# page_select
|
50
|
+
# => creates a drop down instead of a group of links. The user can select the page in the drop down and it will be as if they clicked a link for that page.
|
51
|
+
#
|
36
52
|
# You can apply the _link, _links, or _select to any of the following: order_by, order_as, per_page, page. You have your choice on how you want to set up the interface. For more information and options on these individual
|
37
53
|
# helpers check out their source files. Look at the sub modules under this one (Ex: Searchgasm::Helpers::ControlTypes::Select)
|
38
54
|
module ControlTypes
|
@@ -19,13 +19,14 @@ module Searchgasm
|
|
19
19
|
#
|
20
20
|
# If you pass a Searchgasm::Search::Base object it automatically adds the :order_by, :order_as, and :per_page hidden fields. This is done so that when someone
|
21
21
|
# creates a new search, their options are remembered. It keeps the search consisten and is much more user friendly. If you want to override this you can pass the
|
22
|
-
# following options
|
22
|
+
# following options or you can set this up in your configuration, see Searchgasm::Config for more details.
|
23
|
+
#
|
24
|
+
# Lastly some light javascript is added to the "onsubmit" action. You will notice the order_by, per_page, and page helpers also add in a single hidden tag in the page. The form
|
25
|
+
# finds these elements, gets their values and updates its hidden fields so that the correct values will be submitted during the search. The end result is having the "ordering" and "per page" options remembered.
|
23
26
|
#
|
24
27
|
# === Options
|
25
28
|
#
|
26
29
|
# * <tt>:hidden_fields</tt> --- Array, a list of hidden fields to include. Defaults to [:order_by, :order_as, :per_page]. Pass false, nil, or a blank array to not include any.
|
27
|
-
# * <tt>:js_lib</tt> --- Accepts :prototype, :jquery, or nil. Javascript is written to keep the :hidden_fields in sync with the other fields on the page. nil will turn javascript off, you will be on your own.
|
28
|
-
# Keeping these fields in sync allows search to remember their values when they are changed, making search much more user friendly.
|
29
30
|
module Form
|
30
31
|
module Shared # :nodoc:
|
31
32
|
private
|
@@ -34,9 +35,14 @@ module Searchgasm
|
|
34
35
|
end
|
35
36
|
|
36
37
|
def find_searchgasm_object(args)
|
38
|
+
search_object = nil
|
39
|
+
|
37
40
|
case args.first
|
38
41
|
when String, Symbol
|
39
|
-
|
42
|
+
begin
|
43
|
+
search_object = searchgasm_object?(args[1]) ? args[1] : instance_variable_get("@#{args.first}")
|
44
|
+
rescue Exception
|
45
|
+
end
|
40
46
|
when Array
|
41
47
|
search_object = args.first.last
|
42
48
|
else
|
@@ -8,7 +8,8 @@ module Searchgasm
|
|
8
8
|
options[:search_obj] ||= instance_variable_get("@#{options[:params_scope]}")
|
9
9
|
raise(ArgumentError, "@search object could not be inferred, please specify: :search_obj => @search") unless options[:search_obj].is_a?(Searchgasm::Search::Base)
|
10
10
|
options[:html] ||= {}
|
11
|
-
options[:html][:class]
|
11
|
+
options[:html][:class] ||= ""
|
12
|
+
searchgasm_add_class!(options[:html], option)
|
12
13
|
options
|
13
14
|
end
|
14
15
|
|
@@ -17,12 +18,13 @@ module Searchgasm
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def searchgasm_url_hash(option, value, options)
|
20
|
-
params_copy = params.
|
21
|
+
params_copy = params.deep_dup.with_indifferent_access
|
21
22
|
params_copy.delete(:commit)
|
22
23
|
|
23
24
|
# Extract search params from params
|
24
25
|
search_params = options[:params_scope].blank? ? params_copy : params_copy[options[:params_scope]]
|
25
26
|
search_params ||= {}
|
27
|
+
search_params = search_params.with_indifferent_access
|
26
28
|
|
27
29
|
# Never want to keep page
|
28
30
|
search_params.delete(:page)
|
@@ -33,6 +35,13 @@ module Searchgasm
|
|
33
35
|
search_params
|
34
36
|
end
|
35
37
|
|
38
|
+
def searchgasm_add_class!(html_options, new_class)
|
39
|
+
new_class = new_class.to_s
|
40
|
+
classes = html_options[:class].split(" ")
|
41
|
+
classes << new_class unless classes.include?(new_class)
|
42
|
+
html_options[:class] = classes.join(" ")
|
43
|
+
end
|
44
|
+
|
36
45
|
def searchgasm_order_by_value(order_by)
|
37
46
|
case order_by
|
38
47
|
when String
|
@@ -52,6 +61,22 @@ module Searchgasm
|
|
52
61
|
end
|
53
62
|
html
|
54
63
|
end
|
64
|
+
|
65
|
+
# Need to deep dup a hash otherwise the "child" hashes get modified as its passed around
|
66
|
+
def searchgasm_deep_dup(hash)
|
67
|
+
new_hash = {}
|
68
|
+
|
69
|
+
hash.each do |k, v|
|
70
|
+
case v
|
71
|
+
when Hash
|
72
|
+
hash[k] = searchgasm_deep_dup(v)
|
73
|
+
else
|
74
|
+
hew_hash[k] = v
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
new_hash
|
79
|
+
end
|
55
80
|
end
|
56
81
|
end
|
57
82
|
end
|
data/lib/searchgasm/helpers.rb
CHANGED
@@ -1,3 +1,9 @@
|
|
1
1
|
module Searchgasm
|
2
|
-
|
2
|
+
# = Searchgasm Helpers
|
3
|
+
#
|
4
|
+
# Provides helpers for rails applications that make using Searchgasm extremely easy. Please see Searchgasm::Helpers::ControlTypes and Searchgasm::Helpers::Form for more information on how to use these helpers.
|
5
|
+
#
|
6
|
+
# Also, I am always looking to improve these. I feel confident that I made these flexible enough for just about any need, but there is always that one crazy instance. If these helpers restrict you
|
7
|
+
# in any way please contact me and let me know. You can do some on my website: www.binarylogic.com. I will more than likely add in your functionality that week.
|
8
|
+
module Helpers
|
3
9
|
module UtilitiesHelper # :nodoc:
|
@@ -16,20 +16,43 @@ module Searchgasm #:nodoc:
|
|
16
16
|
# Use these methods just like you would in ActiveRecord
|
17
17
|
SEARCH_METHODS = [:all, :average, :calculate, :count, :find, :first, :maximum, :minimum, :sum]
|
18
18
|
|
19
|
-
attr_accessor
|
19
|
+
attr_accessor *::ActiveRecord::Base.valid_find_options
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
class << self
|
22
|
+
# Used in the ActiveRecord methods to determine if Searchgasm should get involved or not.
|
23
|
+
# This keeps Searchgasm out of the way unless it is needed.
|
24
|
+
def needed?(model_class, options)
|
25
|
+
SPECIAL_FIND_OPTIONS.each do |option|
|
26
|
+
return true if options.symbolize_keys.keys.include?(option)
|
27
|
+
end
|
28
|
+
|
29
|
+
Searchgasm::Conditions::Base.needed?(model_class, options[:conditions])
|
30
|
+
end
|
31
|
+
|
32
|
+
# Creates virtual classes for the class passed to it. This is a neccesity for keeping dynamically created method
|
33
|
+
# names specific to models. It provides caching and helps a lot with performance.
|
34
|
+
def create_virtual_class(model_class)
|
35
|
+
class_search_name = "::#{model_class.name}Search"
|
36
|
+
|
37
|
+
begin
|
38
|
+
class_search_name.constantize
|
39
|
+
rescue NameError
|
40
|
+
eval <<-end_eval
|
41
|
+
class #{class_search_name} < ::Searchgasm::Search::Base; end;
|
42
|
+
end_eval
|
43
|
+
|
44
|
+
class_search_name.constantize
|
45
|
+
end
|
26
46
|
end
|
27
47
|
|
28
|
-
|
48
|
+
# The class / model we are searching
|
49
|
+
def klass
|
50
|
+
# Can't cache this because thin and mongrel don't play nice with caching constants
|
51
|
+
name.split("::").last.gsub(/Search$/, "").constantize
|
52
|
+
end
|
29
53
|
end
|
30
54
|
|
31
|
-
def initialize(
|
32
|
-
self.klass = klass
|
55
|
+
def initialize(init_options = {})
|
33
56
|
self.options = init_options
|
34
57
|
end
|
35
58
|
|
@@ -44,11 +67,17 @@ module Searchgasm #:nodoc:
|
|
44
67
|
end_eval
|
45
68
|
end
|
46
69
|
|
70
|
+
# Makes using searchgasm in the console less annoying and keeps the output meaningful and useful
|
47
71
|
def inspect
|
48
72
|
options_as_nice_string = ::ActiveRecord::Base.valid_find_options.collect { |name| "#{name}: #{send(name)}" }.join(", ")
|
49
73
|
"#<#{klass} #{options_as_nice_string}>"
|
50
74
|
end
|
51
75
|
|
76
|
+
# Convenience method for self.class.klass
|
77
|
+
def klass
|
78
|
+
self.class.klass
|
79
|
+
end
|
80
|
+
|
52
81
|
def limit=(value)
|
53
82
|
@limit = value.blank? || value == 0 ? nil : value.to_i
|
54
83
|
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
module Searchgasm
|
2
2
|
module Search
|
3
|
+
# = Searchgasm Conditions
|
4
|
+
#
|
5
|
+
# Implements all of the conditions functionality into a searchgasm search. All of this functonality is extracted out into its own class Searchgasm::Conditions::Base. This is a separate module to help keep the code
|
6
|
+
# clean and organized.
|
3
7
|
module Conditions
|
4
8
|
def self.included(klass)
|
5
9
|
klass.class_eval do
|
@@ -10,12 +14,24 @@ module Searchgasm
|
|
10
14
|
end
|
11
15
|
end
|
12
16
|
|
13
|
-
def initialize_with_conditions(
|
14
|
-
self.conditions = Searchgasm::Conditions::Base.
|
15
|
-
initialize_without_conditions(
|
17
|
+
def initialize_with_conditions(init_options = {})
|
18
|
+
self.conditions = Searchgasm::Conditions::Base.create_virtual_class(klass).new
|
19
|
+
initialize_without_conditions(init_options)
|
16
20
|
end
|
17
21
|
|
18
22
|
# Sets conditions on the search. Accepts a hash or a Searchgasm::Conditions::Base object.
|
23
|
+
#
|
24
|
+
# === Examples
|
25
|
+
#
|
26
|
+
# search.conditions = {:first_name_like => "Ben"}
|
27
|
+
# search.conditions = User.new_conditions
|
28
|
+
#
|
29
|
+
# or to set a scope
|
30
|
+
#
|
31
|
+
# search.conditions = "user_group_id = 6"
|
32
|
+
#
|
33
|
+
# now you can create the rest of your search and your "scope" will get merged into your final SQL.
|
34
|
+
# What this does is determine if the value a hash or a conditions object, if not it sets it up as a scope.
|
19
35
|
def conditions_with_conditions=(values)
|
20
36
|
case values
|
21
37
|
when Searchgasm::Conditions::Base
|
@@ -25,22 +41,26 @@ module Searchgasm
|
|
25
41
|
end
|
26
42
|
end
|
27
43
|
|
44
|
+
# Tells searchgasm was relationships to include during the search. This is based on what conditions you set.
|
45
|
+
#
|
46
|
+
# <b>Be careful!</b>
|
47
|
+
# ActiveRecord associations can be an SQL train wreck. Make sure you think about what you are searching and that you aren't joining a table with a million records.
|
28
48
|
def include_with_conditions
|
29
49
|
includes = [include_without_conditions, conditions.includes].flatten.compact.uniq
|
30
50
|
includes.blank? ? nil : (includes.size == 1 ? includes.first : includes)
|
31
51
|
end
|
32
52
|
|
33
|
-
def sanitize_with_conditions(for_method = nil)
|
53
|
+
def sanitize_with_conditions(for_method = nil) # :nodoc:
|
34
54
|
find_options = sanitize_without_conditions(for_method)
|
35
55
|
find_options[:conditions] = find_options[:conditions].sanitize if find_options[:conditions]
|
36
56
|
find_options
|
37
57
|
end
|
38
58
|
|
39
|
-
def scope
|
59
|
+
def scope # :nodoc:
|
40
60
|
conditions.scope
|
41
61
|
end
|
42
62
|
|
43
|
-
def scope=(value)
|
63
|
+
def scope=(value) # :nodoc:
|
44
64
|
conditions.scope = value
|
45
65
|
end
|
46
66
|
end
|
@@ -3,7 +3,18 @@ module Searchgasm
|
|
3
3
|
# = Search Ordering
|
4
4
|
#
|
5
5
|
# The purpose of this module is to provide easy ordering for your searches. All that these options do is
|
6
|
-
# build :order for you. This plays a huge part in ordering your data on the interface. See
|
6
|
+
# build :order for you. This plays a huge part in ordering your data on the interface. See the options and examples below. The readme also touches on ordering. It's pretty simple thought:
|
7
|
+
#
|
8
|
+
# === Examples
|
9
|
+
#
|
10
|
+
# search.order_by = :id
|
11
|
+
# search.order_by = [:id, :first_name]
|
12
|
+
# search.order_by = {:user_group => :name}
|
13
|
+
# search.order_by = [:id, {:user_group => :name}]
|
14
|
+
# search.order_by = {:user_group => {:account => :name}} # you can traverse through all of your relationships
|
15
|
+
#
|
16
|
+
# search.order_as = "DESC"
|
17
|
+
# search.order_as = "ASC"
|
7
18
|
module Ordering
|
8
19
|
def self.included(klass)
|
9
20
|
klass.class_eval do
|
@@ -12,12 +23,12 @@ module Searchgasm
|
|
12
23
|
end
|
13
24
|
end
|
14
25
|
|
15
|
-
def include_with_ordering
|
26
|
+
def include_with_ordering # :nodoc:
|
16
27
|
includes = [include_without_ordering, order_by_includes].flatten.compact.uniq
|
17
28
|
includes.blank? ? nil : (includes.size == 1 ? includes.first : includes)
|
18
29
|
end
|
19
30
|
|
20
|
-
def order_with_ordering=(value)
|
31
|
+
def order_with_ordering=(value) # :nodoc
|
21
32
|
@order_by = nil
|
22
33
|
self.order_without_ordering = value
|
23
34
|
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module Searchgasm
|
2
2
|
module Search
|
3
|
+
# = Searchgasm Pagination
|
4
|
+
#
|
5
|
+
# Adds in pagination functionality to searchgasm
|
3
6
|
module Pagination
|
4
7
|
def self.included(klass)
|
5
8
|
klass.class_eval do
|
@@ -10,23 +13,26 @@ module Searchgasm
|
|
10
13
|
end
|
11
14
|
end
|
12
15
|
|
13
|
-
def limit_with_pagination=(value)
|
16
|
+
def limit_with_pagination=(value) # :nodoc:
|
14
17
|
r_value = self.limit_without_pagination = value
|
15
18
|
self.page = @page unless @page.nil? # retry page now that the limit has changed
|
16
19
|
r_value
|
17
20
|
end
|
18
21
|
|
19
|
-
def offset_with_pagination=(value)
|
22
|
+
def offset_with_pagination=(value) #:nodoc
|
20
23
|
r_value = self.offset_without_pagination = value
|
21
24
|
@page = nil
|
22
25
|
r_value
|
23
26
|
end
|
24
27
|
|
28
|
+
# The current page that the search is on
|
25
29
|
def page
|
26
30
|
return 1 if offset.blank? || limit.blank?
|
27
31
|
(offset.to_f / limit).floor + 1
|
28
32
|
end
|
33
|
+
alias_method :current_page, :page
|
29
34
|
|
35
|
+
# Lets you change the page for the next search
|
30
36
|
def page=(value)
|
31
37
|
# Have to use @offset, since self.offset= resets @page
|
32
38
|
if value.nil?
|
@@ -46,6 +52,7 @@ module Searchgasm
|
|
46
52
|
value
|
47
53
|
end
|
48
54
|
|
55
|
+
# The total number of pages in your next search
|
49
56
|
def page_count
|
50
57
|
return 1 if per_page.blank? || per_page <= 0
|
51
58
|
# Letting AR caching kick in with the count query
|
@@ -53,15 +60,53 @@ module Searchgasm
|
|
53
60
|
end
|
54
61
|
alias_method :page_total, :page_count
|
55
62
|
|
56
|
-
|
57
|
-
|
58
|
-
|
63
|
+
# Always returns 1, this is a convenience method
|
64
|
+
def first_page
|
65
|
+
1
|
66
|
+
end
|
67
|
+
|
68
|
+
# Changes the page to 1 and then runs the "all" search. What's different about this method is that it does not raise an exception if you are on the first page. Unlike prev_page! and next_page!
|
69
|
+
# I don't think an exception raised is warranted, because you are expecting the same results each time it is ran.
|
70
|
+
def first_page!
|
71
|
+
self.page = first_page
|
59
72
|
all
|
60
73
|
end
|
61
74
|
|
75
|
+
# Changes the page to the page - 1
|
76
|
+
def prev_page
|
77
|
+
self.page - 1
|
78
|
+
end
|
79
|
+
|
80
|
+
# Changes the page to page - 1 and runs the "all" search. Be careful with this method because if you are on the first page an exception is raised telling you that you are on the first page.
|
81
|
+
# I thought about just running the first page search again, but that seems confusing and unexpected.
|
62
82
|
def prev_page!
|
63
|
-
raise("You are on the first page") if page ==
|
64
|
-
self.page
|
83
|
+
raise("You are on the first page") if page == first_page
|
84
|
+
self.page = prev_page
|
85
|
+
all
|
86
|
+
end
|
87
|
+
|
88
|
+
# Change the page to page + 1
|
89
|
+
def next_page
|
90
|
+
self.page + 1
|
91
|
+
end
|
92
|
+
|
93
|
+
# Changes the page to page + 1 and calls the "all" method. Be careful with this method because if you are on the last page an exception is raised telling you that you are on the last page.
|
94
|
+
# I thought about just running the lat page search again, but that seems confusing and unexpected.
|
95
|
+
def next_page!
|
96
|
+
raise("You are on the last page") if page == last_page
|
97
|
+
self.page = next_page
|
98
|
+
all
|
99
|
+
end
|
100
|
+
|
101
|
+
# Always returns the page_count, this is a convenience method
|
102
|
+
def last_page
|
103
|
+
page_count
|
104
|
+
end
|
105
|
+
|
106
|
+
# Changes the page to the last page and runs the "all" search. What's different about this method is that it does not raise an exception if you are on the last page. Unlike prev_page! and next_page!
|
107
|
+
# I don't think an exception raised is warranted, because you are expecting the same results each time it is ran.
|
108
|
+
def last_page!
|
109
|
+
self.page = last_page
|
65
110
|
all
|
66
111
|
end
|
67
112
|
end
|
@@ -1,5 +1,25 @@
|
|
1
1
|
module Searchgasm
|
2
2
|
module Search
|
3
|
+
# = Searchgasm Protection
|
4
|
+
#
|
5
|
+
# This adds protection during mass asignments *only*. This allows you to pass a params object when doing mass assignments and not have to worry about Billy 13 year old adding in SQL injections.
|
6
|
+
# There is a section in the readme that covers protection but to reiterate:
|
7
|
+
#
|
8
|
+
# === Protected
|
9
|
+
#
|
10
|
+
# User.new_search(params[:search])
|
11
|
+
# User.new_conditions(params[:search])
|
12
|
+
#
|
13
|
+
# search.options = params[:search]
|
14
|
+
# conditions.conditions = params[:conditions]
|
15
|
+
#
|
16
|
+
# === NOT Protected
|
17
|
+
#
|
18
|
+
# User.new_search!(params[:search])
|
19
|
+
# User.new_conditions!(params[:search])
|
20
|
+
# User.find(:all, params[:search])
|
21
|
+
# User.first(params[:search])
|
22
|
+
# User.all(params[:search])
|
3
23
|
module Protection
|
4
24
|
# Options that are allowed when protecting against SQL injections (still checked though)
|
5
25
|
SAFE_OPTIONS = Base::SPECIAL_FIND_OPTIONS + [:conditions, :limit, :offset]
|
@@ -16,28 +36,30 @@ module Searchgasm
|
|
16
36
|
end
|
17
37
|
end
|
18
38
|
|
19
|
-
def limit_with_protection
|
39
|
+
def limit_with_protection # :nodoc:
|
20
40
|
return Config.per_page if protected? && !@set_limit
|
21
41
|
limit_without_protection
|
22
42
|
end
|
23
43
|
|
24
|
-
def limit_with_protection=(value)
|
44
|
+
def limit_with_protection=(value) # :nodoc:
|
25
45
|
@set_limit = true
|
26
46
|
self.limit_without_protection = value
|
27
47
|
end
|
28
48
|
|
29
|
-
def options_with_protection=(values)
|
49
|
+
def options_with_protection=(values) # :nodoc:
|
30
50
|
return unless values.is_a?(Hash)
|
31
51
|
self.protect = values.delete(:protect) if values.has_key?(:protect) # make sure we do this first
|
32
52
|
frisk!(values) if protect?
|
33
53
|
self.options_without_protection = values
|
34
54
|
end
|
35
55
|
|
56
|
+
# Accepts a boolean. Will protect mass assignemnts if set to true, and unprotect mass assignments if set to false
|
36
57
|
def protect=(value)
|
37
58
|
conditions.protect = value
|
38
59
|
@protect = value
|
39
60
|
end
|
40
61
|
|
62
|
+
# Convenience methof for determing if the search is protected or not.
|
41
63
|
def protect?
|
42
64
|
protect == true
|
43
65
|
end
|