searchgasm 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +6 -0
- data/README.rdoc +10 -0
- data/lib/searchgasm/conditions/base.rb +28 -7
- data/lib/searchgasm/config.rb +72 -0
- data/lib/searchgasm/helpers/control_types/links.rb +57 -26
- data/lib/searchgasm/shared/searching.rb +2 -0
- data/lib/searchgasm/version.rb +2 -2
- data/searchgasm.gemspec +2 -2
- data/test/test_active_record_base.rb +6 -0
- data/test/test_conditions_base.rb +12 -0
- data/test/test_search_pagination.rb +11 -0
- metadata +1 -1
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
== 1.1.0 released 2008-09-18
|
2
|
+
|
3
|
+
* Added the options :inner_spread and :outer_spread to the page_links helper. Also added various config options for setting page_links defaults.
|
4
|
+
* Updated calculation methods to ignore :limit and :offset. AR returns 0 or nil on calculations that provide an offset.
|
5
|
+
* Added support to allow for "any" of the conditions, instead of all of them. Joins conditions with "or" instead of "and". See Searchgasm::Conditions::Base or the readme
|
6
|
+
|
1
7
|
== 1.0.4 released 2008-09-18
|
2
8
|
|
3
9
|
* Fixed bugs when performing calculations and searches on has_many through relationships.
|
data/README.rdoc
CHANGED
@@ -189,6 +189,16 @@ Don't need pagination, ordering, or any of the other options? Search with condit
|
|
189
189
|
Pass a conditions object right into ActiveRecord:
|
190
190
|
|
191
191
|
User.all(:conditions => conditions)
|
192
|
+
|
193
|
+
== Match ANY or ALL of the conditions
|
194
|
+
|
195
|
+
As you saw above, the nice thing about Searchgasm is it's integration with forms. I designed the "any" option so that forms can set this as well, just like a condition.
|
196
|
+
|
197
|
+
search = User.new_search(:conditions => {:age_gt => 18})
|
198
|
+
search.conditions.first_name_contains = "Ben"
|
199
|
+
search.conditions.any = true # can set this to "true" or "1" or "yes"
|
200
|
+
search.all # will join all conditions with "or" instead of "and"
|
201
|
+
# ... all operations above are available
|
192
202
|
|
193
203
|
== Scoped searching
|
194
204
|
|
@@ -9,7 +9,7 @@ module Searchgasm
|
|
9
9
|
include Searchgasm::Shared::Searching
|
10
10
|
include Searchgasm::Shared::VirtualClasses
|
11
11
|
|
12
|
-
attr_accessor :relationship_name, :sql
|
12
|
+
attr_accessor :any, :relationship_name, :sql
|
13
13
|
|
14
14
|
class << self
|
15
15
|
attr_accessor :added_klass_conditions, :added_column_conditions, :added_associations
|
@@ -66,6 +66,7 @@ module Searchgasm
|
|
66
66
|
|
67
67
|
def needed?(model_class, conditions) # :nodoc:
|
68
68
|
if conditions.is_a?(Hash)
|
69
|
+
return true if conditions[:any]
|
69
70
|
column_names = model_class.column_names
|
70
71
|
conditions.stringify_keys.keys.each do |condition|
|
71
72
|
return true unless column_names.include?(condition)
|
@@ -83,10 +84,31 @@ module Searchgasm
|
|
83
84
|
self.conditions = init_conditions
|
84
85
|
end
|
85
86
|
|
87
|
+
# Determines if we should join the conditions with "AND" or "OR".
|
88
|
+
#
|
89
|
+
# === Examples
|
90
|
+
#
|
91
|
+
# search.conditions.any = true # will join all conditions with "or", you can also set this to "true", "1", or "yes"
|
92
|
+
# search.conditions.any = false # will join all conditions with "and"
|
93
|
+
def any=(value)
|
94
|
+
associations.each { |association| association.any = value }
|
95
|
+
@any = value
|
96
|
+
end
|
97
|
+
|
98
|
+
def any # :nodoc:
|
99
|
+
any?
|
100
|
+
end
|
101
|
+
|
102
|
+
# Convenience method for determining if we should join the conditions with "AND" or "OR".
|
103
|
+
def any?
|
104
|
+
@any == true || @any == "true" || @any == "1" || @any == "yes"
|
105
|
+
end
|
106
|
+
|
86
107
|
# A list of includes to use when searching, includes relationships
|
87
108
|
def includes
|
88
109
|
i = []
|
89
110
|
associations.each do |association|
|
111
|
+
next if association.conditions.blank?
|
90
112
|
association_includes = association.includes
|
91
113
|
i << (association_includes.blank? ? association.relationship_name.to_sym : {association.relationship_name.to_sym => association_includes})
|
92
114
|
end
|
@@ -101,10 +123,10 @@ module Searchgasm
|
|
101
123
|
end
|
102
124
|
|
103
125
|
# Sanitizes the conditions down into conditions that ActiveRecord::Base.find can understand.
|
104
|
-
def sanitize
|
105
|
-
conditions = merge_conditions(*objects.collect { |object| object.sanitize })
|
126
|
+
def sanitize
|
127
|
+
conditions = merge_conditions(*(objects.collect { |object| object.sanitize } << {:any => any}))
|
106
128
|
return sql if conditions.blank?
|
107
|
-
merged_conditions = merge_conditions(conditions, sql)
|
129
|
+
merged_conditions = merge_conditions(conditions, sql, :any => any)
|
108
130
|
merged_conditions
|
109
131
|
end
|
110
132
|
|
@@ -123,8 +145,7 @@ module Searchgasm
|
|
123
145
|
def conditions
|
124
146
|
conditions_hash = {}
|
125
147
|
objects.each do |object|
|
126
|
-
|
127
|
-
when self.class
|
148
|
+
if object.class < Searchgasm::Conditions::Base
|
128
149
|
relationship_conditions = object.conditions
|
129
150
|
next if relationship_conditions.blank?
|
130
151
|
conditions_hash[object.relationship_name.to_sym] = relationship_conditions
|
@@ -218,7 +239,7 @@ module Searchgasm
|
|
218
239
|
end
|
219
240
|
|
220
241
|
def assert_valid_conditions(conditions)
|
221
|
-
conditions.stringify_keys.fast_assert_valid_keys(self.class.condition_names + self.class.association_names)
|
242
|
+
conditions.stringify_keys.fast_assert_valid_keys(self.class.condition_names + self.class.association_names + ["any"])
|
222
243
|
end
|
223
244
|
|
224
245
|
def associations
|
data/lib/searchgasm/config.rb
CHANGED
@@ -43,6 +43,78 @@ module Searchgasm
|
|
43
43
|
@desc_indicator = value
|
44
44
|
end
|
45
45
|
|
46
|
+
def page_links_first # :nodoc:
|
47
|
+
@page_links_first
|
48
|
+
end
|
49
|
+
|
50
|
+
# The default for the :first option for the page_links helper.
|
51
|
+
#
|
52
|
+
# * <tt>Default:</tt> nil
|
53
|
+
# * <tt>Accepts:</tt> Anything you want, text, html, etc. nil to disable
|
54
|
+
def page_links_first=(value)
|
55
|
+
@page_links_first = value
|
56
|
+
end
|
57
|
+
|
58
|
+
def page_links_last # :nodoc:
|
59
|
+
@page_links_last
|
60
|
+
end
|
61
|
+
|
62
|
+
# The default for the :last option for the page_links helper.
|
63
|
+
#
|
64
|
+
# * <tt>Default:</tt> nil
|
65
|
+
# * <tt>Accepts:</tt> Anything you want, text, html, etc. nil to disable
|
66
|
+
def page_links_last=(value)
|
67
|
+
@page_links_last = value
|
68
|
+
end
|
69
|
+
|
70
|
+
def page_links_inner_spread # :nodoc:
|
71
|
+
@page_links_inner_spread ||= 3
|
72
|
+
end
|
73
|
+
|
74
|
+
# The default for the :inner_spread option for the page_links helper.
|
75
|
+
#
|
76
|
+
# * <tt>Default:</tt> 3
|
77
|
+
# * <tt>Accepts:</tt> Any integer >= 1, set to nil to show all pages
|
78
|
+
def page_links_inner_spread=(value)
|
79
|
+
@page_links_inner_spread = value
|
80
|
+
end
|
81
|
+
|
82
|
+
def page_links_outer_spread # :nodoc:
|
83
|
+
@page_links_outer_spread ||= 2
|
84
|
+
end
|
85
|
+
|
86
|
+
# The default for the :outer_spread option for the page_links helper.
|
87
|
+
#
|
88
|
+
# * <tt>Default:</tt> 2
|
89
|
+
# * <tt>Accepts:</tt> Any integer >= 1, set to nil to display, 0 to only show the "..." separator
|
90
|
+
def page_links_outer_spread=(value)
|
91
|
+
@page_links_outer_spread = value
|
92
|
+
end
|
93
|
+
|
94
|
+
def page_links_next # :nodoc:
|
95
|
+
@page_links_next ||= "< Next"
|
96
|
+
end
|
97
|
+
|
98
|
+
# The default for the :next option for the page_links helper.
|
99
|
+
#
|
100
|
+
# * <tt>Default:</tt> "< Next"
|
101
|
+
# * <tt>Accepts:</tt> Anything you want, text, html, etc. nil to disable
|
102
|
+
def page_links_next=(value)
|
103
|
+
@page_links_next = value
|
104
|
+
end
|
105
|
+
|
106
|
+
def page_links_prev # :nodoc:
|
107
|
+
@page_links_prev ||= "< Prev"
|
108
|
+
end
|
109
|
+
|
110
|
+
# The default for the :prev option for the page_links helper.
|
111
|
+
#
|
112
|
+
# * <tt>Default:</tt> "< Prev"
|
113
|
+
# * <tt>Accepts:</tt> Anything you want, text, html, etc. nil to disable
|
114
|
+
def page_links_prev=(value)
|
115
|
+
@page_links_prev = value
|
116
|
+
end
|
117
|
+
|
46
118
|
def per_page # :nodoc:
|
47
119
|
@per_page
|
48
120
|
end
|
@@ -1,9 +1,6 @@
|
|
1
1
|
module Searchgasm
|
2
2
|
module Helpers
|
3
3
|
module ControlTypes
|
4
|
-
# = Links Control Types
|
5
|
-
#
|
6
|
-
# These helpers create a group of links to help navigate through search data.
|
7
4
|
module Links
|
8
5
|
# Creates a group of links that order the data by a column or columns. All that this does is loop through the :choices option and call order_by_link and then glue it all together.
|
9
6
|
#
|
@@ -14,7 +11,7 @@ module Searchgasm
|
|
14
11
|
#
|
15
12
|
# === Options
|
16
13
|
#
|
17
|
-
# Please look at order_by_link. All options there are applicable here and are passed onto each option.
|
14
|
+
# Please look at order_by_link. All options there are applicable here and are passed onto each option.
|
18
15
|
#
|
19
16
|
# * <tt>:choices</tt> -- default: the models column names, the choices to loop through when calling order_by_link
|
20
17
|
def order_by_links(options = {})
|
@@ -35,7 +32,7 @@ module Searchgasm
|
|
35
32
|
#
|
36
33
|
# === Options
|
37
34
|
#
|
38
|
-
# Please look at order_as_link. All options there are applicable here and are passed onto each option.
|
35
|
+
# Please look at order_as_link. All options there are applicable here and are passed onto each option.
|
39
36
|
#
|
40
37
|
# * <tt>:choices</tt> -- default: ["asc", "desc"], the choices to loop through when calling order_as_link
|
41
38
|
def order_as_links(options = {})
|
@@ -56,7 +53,7 @@ module Searchgasm
|
|
56
53
|
#
|
57
54
|
# === Options
|
58
55
|
#
|
59
|
-
# Please look at per_page_link. All options there are applicable here and are passed onto each option.
|
56
|
+
# Please look at per_page_link. All options there are applicable here and are passed onto each option.
|
60
57
|
#
|
61
58
|
# * <tt>:choices</tt> -- default: [10, 25, 50, 100, 150, 200, nil], the choices to loop through when calling per_page_link.
|
62
59
|
def per_page_links(options = {})
|
@@ -75,7 +72,7 @@ module Searchgasm
|
|
75
72
|
# page_links
|
76
73
|
# page_links(:first => "<< First", :last => "Last >>")
|
77
74
|
#
|
78
|
-
# === Classes and
|
75
|
+
# === Classes and tags
|
79
76
|
#
|
80
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
|
81
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
|
@@ -83,7 +80,7 @@ module Searchgasm
|
|
83
80
|
#
|
84
81
|
# * <tt>page</tt> - This is in *every* element, span or a.
|
85
82
|
# * <tt>first_page</tt> - This is for the "first page" element only.
|
86
|
-
# * <tt>
|
83
|
+
# * <tt>prev_page</tt> - This is for the "prev page" element only.
|
87
84
|
# * <tt>current_page</tt> - This is for the current page element
|
88
85
|
# * <tt>next_page</tt> - This is for the "next page" element only.
|
89
86
|
# * <tt>last_page</tt> - This is for the "last page" element only.
|
@@ -91,33 +88,64 @@ module Searchgasm
|
|
91
88
|
#
|
92
89
|
# === Options
|
93
90
|
#
|
94
|
-
# Please look at per_page_link. All options there are applicable here and are passed onto each option.
|
95
|
-
#
|
96
|
-
# * <tt>:
|
97
|
-
# * <tt>:
|
98
|
-
# *
|
91
|
+
# Please look at per_page_link. All options there are applicable here and are passed onto each option.
|
92
|
+
#
|
93
|
+
# * <tt>:inner_spread</tt> -- default: 3, set to nil to show all pages, this represents how many choices available on each side of the current page
|
94
|
+
# * <tt>:outer_spread</tt> -- default: 1, set to nil to disable, this represents how many choices are in the "outer" spread. If set to 0, the separator will be present with no page links. This option changes the links from
|
95
|
+
# * "< Prev 2 3 4 [5] 6 7 8 Next >" with 10 total pages to:
|
96
|
+
# * "< Prev ... 3 4 [5] 6 7 ... Next >" for :outer_spread = 0 and :inner_spread = 3
|
97
|
+
# * "< Prev 1 ... 3 4 [5] 6 7 ... 10 Next >" for :outer_spread = 1 and :inner_spread = 3
|
98
|
+
# * "< Prev 1 2 ... 3 4 [5] 6 7 ... 9 10 Next >" for :outer_spread = 2 and :inner_spread = 3 (outer_spread = number of absolute pages on each side)
|
99
|
+
# * Outer spread pages will not be visible unless the current_page is more than :inner_spread away from the first or last page.
|
100
|
+
# * <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
|
101
|
+
# * <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
|
99
102
|
# * <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
|
100
103
|
# * <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
|
101
104
|
def page_links(options = {})
|
102
105
|
add_page_links_defaults!(options)
|
103
106
|
return if options[:last_page] <= 1
|
104
107
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
108
|
+
inner_spread_start = inner_spread_end = lower_gap = lower_outer_spread_start = lower_outer_spread_end = upper_gap = upper_outer_spread_start = upper_outer_spread_end = 0
|
109
|
+
if !options[:inner_spread].blank?
|
110
|
+
inner_spread_start = options[:current_page] - options[:inner_spread]
|
111
|
+
inner_spread_start = options[:first_page] if inner_spread_start < options[:first_page]
|
112
|
+
inner_spread_end = options[:current_page] + options[:inner_spread]
|
113
|
+
inner_spread_end = options[:last_page] if inner_spread_end > options[:last_page]
|
114
|
+
|
115
|
+
if !options[:outer_spread].blank?
|
116
|
+
lower_gap = inner_spread_start - options[:first_page]
|
117
|
+
if lower_gap > 0
|
118
|
+
lower_outer_spread_start = options[:first_page]
|
119
|
+
lower_outer_spread_end = options[:outer_spread] > lower_gap ? lower_gap : options[:outer_spread]
|
120
|
+
end
|
121
|
+
|
122
|
+
upper_gap = options[:last_page] - inner_spread_end
|
123
|
+
if upper_gap > 0
|
124
|
+
upper_outer_spread_start = options[:last_page] - (options[:outer_spread] > upper_gap ? upper_gap : options[:outer_spread]) + 1
|
125
|
+
upper_outer_spread_end = options[:last_page]
|
126
|
+
end
|
127
|
+
end
|
112
128
|
else
|
113
|
-
|
114
|
-
|
129
|
+
inner_spread_start = options[:first_page]
|
130
|
+
inner_spread_end = options[:last_page]
|
115
131
|
end
|
116
132
|
|
117
133
|
html = ""
|
118
134
|
html += span_or_page_link(:first, options.deep_dup, options[:current_page] == options[:first_page]) if options[:first]
|
119
135
|
html += span_or_page_link(:prev, options.deep_dup, options[:current_page] == options[:first_page]) if options[:prev]
|
120
|
-
|
136
|
+
|
137
|
+
if lower_gap > 0
|
138
|
+
(lower_outer_spread_start..lower_outer_spread_end).each { |page| html += span_or_page_link(page, options.deep_dup, false) }
|
139
|
+
html += content_tag(:span, "…", options[:html]) if (inner_spread_start - lower_outer_spread_end) > 1
|
140
|
+
end
|
141
|
+
|
142
|
+
(inner_spread_start..inner_spread_end).each { |page| html += span_or_page_link(page, options.deep_dup, page == options[:current_page]) }
|
143
|
+
|
144
|
+
if upper_gap > 0
|
145
|
+
html += content_tag(:span, "…", options[:html]) if (upper_outer_spread_start - inner_spread_end) > 1
|
146
|
+
(upper_outer_spread_start..upper_outer_spread_end).each { |page| html += span_or_page_link(page, options.deep_dup, false) }
|
147
|
+
end
|
148
|
+
|
121
149
|
html += span_or_page_link(:next, options.deep_dup, options[:current_page] == options[:last_page]) if options[:next]
|
122
150
|
html += span_or_page_link(:last, options.deep_dup, options[:current_page] == options[:last_page]) if options[:last]
|
123
151
|
html
|
@@ -154,9 +182,12 @@ module Searchgasm
|
|
154
182
|
options[:first_page] ||= 1
|
155
183
|
options[:last_page] ||= options[:search_obj].page_count
|
156
184
|
options[:current_page] ||= options[:search_obj].page
|
157
|
-
options[:
|
158
|
-
options[:
|
159
|
-
options[:
|
185
|
+
options[:inner_spread] = Config.page_links_inner_spread unless options.has_key?(:inner_spread)
|
186
|
+
options[:outer_spread] = Config.page_links_outer_spread unless options.has_key?(:outer_spread)
|
187
|
+
options[:prev] = Config.page_links_prev unless options.has_key?(:prev)
|
188
|
+
options[:next] = Config.page_links_next unless options.has_key?(:next)
|
189
|
+
options[:first] = Config.page_links_first unless options.has_key?(:first)
|
190
|
+
options[:last] = Config.page_links_last unless options.has_key?(:last)
|
160
191
|
options
|
161
192
|
end
|
162
193
|
|
@@ -30,6 +30,8 @@ module Searchgasm
|
|
30
30
|
klass.send(:with_scope, :find => options) do
|
31
31
|
find_options = (self.class < Searchgasm::Conditions::Base ? {:conditions => sanitize} : sanitize)
|
32
32
|
find_options.delete(:select)
|
33
|
+
find_options.delete(:limit)
|
34
|
+
find_options.delete(:offset)
|
33
35
|
args << find_options
|
34
36
|
klass.#{method}(*args)
|
35
37
|
end
|
data/lib/searchgasm/version.rb
CHANGED
data/searchgasm.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
|
2
|
-
# Gem::Specification for Searchgasm-1.0
|
2
|
+
# Gem::Specification for Searchgasm-1.1.0
|
3
3
|
# Originally generated by Echoe
|
4
4
|
|
5
5
|
--- !ruby/object:Gem::Specification
|
6
6
|
name: searchgasm
|
7
7
|
version: !ruby/object:Gem::Version
|
8
|
-
version: 1.0
|
8
|
+
version: 1.1.0
|
9
9
|
platform: ruby
|
10
10
|
authors:
|
11
11
|
- Ben Johnson of Binary Logic
|
@@ -84,5 +84,11 @@ class TestActiveRecordBase < Test::Unit::TestCase
|
|
84
84
|
def test_count
|
85
85
|
assert_equal 3, Account.count
|
86
86
|
assert_equal 3, Account.count(:limit => 1)
|
87
|
+
assert_equal 0, Account.count(:limit => 1, :offset => 1) # not sure why AR doesn't ignore offset like it does for limit
|
88
|
+
search = Account.new_search
|
89
|
+
assert_equal 3, search.count
|
90
|
+
search.per_page = 10
|
91
|
+
search.page = 10
|
92
|
+
assert_equal 3, search.count
|
87
93
|
end
|
88
94
|
end
|
@@ -173,4 +173,16 @@ class TestConditionsBase < Test::Unit::TestCase
|
|
173
173
|
assert_equal 1, conditions.minimum('id')
|
174
174
|
assert_equal 4, conditions.sum('id')
|
175
175
|
end
|
176
|
+
|
177
|
+
def test_any
|
178
|
+
conditions = Account.new_conditions
|
179
|
+
conditions.name_contains = "Binary"
|
180
|
+
assert_equal Account.find(1, 3), conditions.all
|
181
|
+
conditions.id = 1
|
182
|
+
assert_equal [Account.find(1)], conditions.all
|
183
|
+
conditions.any = true
|
184
|
+
assert_equal Account.find(1, 3), conditions.all
|
185
|
+
conditions.any = false
|
186
|
+
assert_equal [Account.find(1)], conditions.all
|
187
|
+
end
|
176
188
|
end
|
@@ -67,6 +67,17 @@ class TestSearchPagination < Test::Unit::TestCase
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def test_page_count
|
70
|
+
search = Account.new_search
|
71
|
+
assert_equal 1, search.page_count
|
72
|
+
search.per_page = 1
|
73
|
+
assert_equal 3, search.page_count
|
74
|
+
search.per_page = 100
|
75
|
+
assert_equal 1, search.page_count
|
70
76
|
|
77
|
+
Searchgasm::Config.per_page = 1
|
78
|
+
search = Account.new_search
|
79
|
+
assert_equal 3, search.page_count
|
80
|
+
search.conditions.users.first_name_contains
|
81
|
+
assert_equal 3, search.page_count
|
71
82
|
end
|
72
83
|
end
|