will_paginate 2.1.0 → 2.2.0
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.
Potentially problematic release.
This version of will_paginate might be problematic. Click here for more details.
- data/CHANGELOG +32 -2
- data/LICENSE +1 -1
- data/{README → README.rdoc} +42 -59
- data/Rakefile +41 -4
- data/examples/apple-circle.gif +0 -0
- data/examples/index.haml +69 -0
- data/examples/index.html +92 -0
- data/examples/pagination.css +90 -0
- data/examples/pagination.sass +91 -0
- data/init.rb +1 -0
- data/lib/will_paginate.rb +34 -11
- data/lib/will_paginate/array.rb +16 -0
- data/lib/will_paginate/collection.rb +37 -24
- data/lib/will_paginate/core_ext.rb +1 -49
- data/lib/will_paginate/finder.rb +29 -4
- data/lib/will_paginate/named_scope.rb +132 -0
- data/lib/will_paginate/named_scope_patch.rb +50 -0
- data/lib/will_paginate/version.rb +1 -1
- data/lib/will_paginate/view_helpers.rb +123 -21
- data/test/boot.rb +0 -2
- data/test/{array_pagination_test.rb → collection_test.rb} +37 -28
- data/test/console +2 -3
- data/test/database.yml +22 -0
- data/test/finder_test.rb +150 -56
- data/test/fixtures/developer.rb +2 -0
- data/test/fixtures/projects.yml +3 -4
- data/test/fixtures/reply.rb +2 -0
- data/test/fixtures/topic.rb +2 -0
- data/test/helper.rb +13 -1
- data/test/lib/activerecord_test_case.rb +14 -1
- data/test/lib/activerecord_test_connector.rb +19 -10
- data/test/lib/load_fixtures.rb +3 -5
- data/test/lib/view_test_process.rb +73 -0
- data/test/view_test.rb +314 -0
- metadata +32 -31
- data/Manifest.txt +0 -34
- data/config/release.rb +0 -82
- data/setup.rb +0 -1585
- data/test/pagination_test.rb +0 -257
@@ -0,0 +1,50 @@
|
|
1
|
+
## based on http://dev.rubyonrails.org/changeset/9084
|
2
|
+
|
3
|
+
ActiveRecord::Associations::AssociationProxy.class_eval do
|
4
|
+
protected
|
5
|
+
def with_scope(*args, &block)
|
6
|
+
@reflection.klass.send :with_scope, *args, &block
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
[ ActiveRecord::Associations::AssociationCollection,
|
11
|
+
ActiveRecord::Associations::HasManyThroughAssociation ].each do |klass|
|
12
|
+
klass.class_eval do
|
13
|
+
protected
|
14
|
+
def method_missing_without_paginate(method, *args)
|
15
|
+
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
16
|
+
if block_given?
|
17
|
+
super { |*block_args| yield(*block_args) }
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
elsif @reflection.klass.scopes.include?(method)
|
22
|
+
@reflection.klass.scopes[method].call(self, *args)
|
23
|
+
else
|
24
|
+
with_scope construct_scope do
|
25
|
+
if block_given?
|
26
|
+
@reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
|
27
|
+
else
|
28
|
+
@reflection.klass.send(method, *args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Rails 1.2.6
|
37
|
+
ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
|
38
|
+
protected
|
39
|
+
def method_missing(method, *args, &block)
|
40
|
+
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
41
|
+
super
|
42
|
+
elsif @reflection.klass.scopes.include?(method)
|
43
|
+
@reflection.klass.scopes[method].call(self, *args)
|
44
|
+
else
|
45
|
+
@reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
|
46
|
+
@reflection.klass.send(method, *args, &block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end if ActiveRecord::VERSION::MAJOR < 2
|
@@ -85,10 +85,11 @@ module WillPaginate
|
|
85
85
|
collection_name = "@#{controller.controller_name}"
|
86
86
|
collection = instance_variable_get(collection_name)
|
87
87
|
raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " +
|
88
|
-
"forget to
|
88
|
+
"forget to pass the collection object for will_paginate?" unless collection
|
89
89
|
end
|
90
90
|
# early exit if there is nothing to render
|
91
|
-
return nil unless collection
|
91
|
+
return nil unless WillPaginate::ViewHelpers.total_pages_for_collection(collection) > 1
|
92
|
+
|
92
93
|
options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers.pagination_options
|
93
94
|
# create the renderer instance
|
94
95
|
renderer_class = options[:renderer].to_s.constantize
|
@@ -96,6 +97,34 @@ module WillPaginate
|
|
96
97
|
# render HTML for pagination
|
97
98
|
renderer.to_html
|
98
99
|
end
|
100
|
+
|
101
|
+
# Wrapper for rendering pagination links at both top and bottom of a block
|
102
|
+
# of content.
|
103
|
+
#
|
104
|
+
# <% paginated_section @posts do %>
|
105
|
+
# <ol id="posts">
|
106
|
+
# <% for post in @posts %>
|
107
|
+
# <li> ... </li>
|
108
|
+
# <% end %>
|
109
|
+
# </ol>
|
110
|
+
# <% end %>
|
111
|
+
#
|
112
|
+
# will result in:
|
113
|
+
#
|
114
|
+
# <div class="pagination"> ... </div>
|
115
|
+
# <ol id="posts">
|
116
|
+
# ...
|
117
|
+
# </ol>
|
118
|
+
# <div class="pagination"> ... </div>
|
119
|
+
#
|
120
|
+
# Arguments are passed to a <tt>will_paginate</tt> call, so the same options
|
121
|
+
# apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with two
|
122
|
+
# blocks of pagination links sharing the same ID (which is invalid HTML).
|
123
|
+
def paginated_section(*args, &block)
|
124
|
+
pagination = will_paginate(*args).to_s
|
125
|
+
content = pagination + capture(&block) + pagination
|
126
|
+
concat content, block.binding
|
127
|
+
end
|
99
128
|
|
100
129
|
# Renders a helpful message with numbers of displayed vs. total entries.
|
101
130
|
# You can use this as a blueprint for your own, similar helpers.
|
@@ -109,28 +138,51 @@ module WillPaginate
|
|
109
138
|
collection.total_entries
|
110
139
|
]
|
111
140
|
end
|
141
|
+
|
142
|
+
def self.total_pages_for_collection(collection) #:nodoc:
|
143
|
+
if collection.respond_to? :page_count and !collection.respond_to? :total_pages
|
144
|
+
WillPaginate::Deprecation.warn <<-MSG
|
145
|
+
You are using a paginated collection of class #{collection.class.name}
|
146
|
+
which conforms to the old API of WillPaginate::Collection by using
|
147
|
+
`page_count`, while the current method name is `total_pages`. Please
|
148
|
+
upgrade yours or 3rd-party code that provides the paginated collection.
|
149
|
+
MSG
|
150
|
+
class << collection
|
151
|
+
def total_pages; page_count; end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
collection.total_pages
|
155
|
+
end
|
112
156
|
end
|
113
157
|
|
114
158
|
# This class does the heavy lifting of actually building the pagination
|
115
159
|
# links. It is used by +will_paginate+ helper internally.
|
116
160
|
class LinkRenderer
|
117
|
-
|
161
|
+
# * +collection+ is a WillPaginate::Collection instance or any other object
|
162
|
+
# that conforms to that API
|
163
|
+
# * +options+ are forwarded from +will_paginate+ view helper
|
164
|
+
# * +template+ is the reference to the template being rendered
|
118
165
|
def initialize(collection, options, template)
|
119
166
|
@collection = collection
|
120
167
|
@options = options
|
121
168
|
@template = template
|
122
169
|
end
|
123
170
|
|
171
|
+
# Process it! This method returns the complete HTML string which contains
|
172
|
+
# pagination links. Feel free to subclass LinkRenderer and change this
|
173
|
+
# method as you see fit.
|
124
174
|
def to_html
|
125
175
|
links = @options[:page_links] ? windowed_links : []
|
126
176
|
# previous/next buttons
|
127
|
-
links.unshift page_link_or_span(@collection.previous_page,
|
128
|
-
links.push page_link_or_span(@collection.next_page,
|
177
|
+
links.unshift page_link_or_span(@collection.previous_page, %w(disabled prev_page), @options[:prev_label])
|
178
|
+
links.push page_link_or_span(@collection.next_page, %w(disabled next_page), @options[:next_label])
|
129
179
|
|
130
180
|
html = links.join(@options[:separator])
|
131
181
|
@options[:container] ? @template.content_tag(:div, html, html_attributes) : html
|
132
182
|
end
|
133
183
|
|
184
|
+
# Returns the subset of +options+ this instance was initialized with that
|
185
|
+
# represent HTML attributes for the container element of pagination links.
|
134
186
|
def html_attributes
|
135
187
|
return @html_attributes if @html_attributes
|
136
188
|
@html_attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class])
|
@@ -143,20 +195,28 @@ module WillPaginate
|
|
143
195
|
|
144
196
|
protected
|
145
197
|
|
146
|
-
|
198
|
+
# The gap in page links is represented by:
|
199
|
+
#
|
200
|
+
# <span class="gap">…</span>
|
201
|
+
def gap_marker
|
202
|
+
'<span class="gap">…</span>'
|
203
|
+
end
|
147
204
|
|
205
|
+
# Collects link items for visible page numbers.
|
148
206
|
def windowed_links
|
149
207
|
prev = nil
|
150
208
|
|
151
209
|
visible_page_numbers.inject [] do |links, n|
|
152
210
|
# detect gaps:
|
153
211
|
links << gap_marker if prev and n > prev + 1
|
154
|
-
links << page_link_or_span(n)
|
212
|
+
links << page_link_or_span(n, 'current')
|
155
213
|
prev = n
|
156
214
|
links
|
157
215
|
end
|
158
216
|
end
|
159
217
|
|
218
|
+
# Calculates visible page numbers using the <tt>:inner_window</tt> and
|
219
|
+
# <tt>:outer_window</tt> options.
|
160
220
|
def visible_page_numbers
|
161
221
|
inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
|
162
222
|
window_from = current_page - inner_window
|
@@ -166,9 +226,11 @@ module WillPaginate
|
|
166
226
|
if window_to > total_pages
|
167
227
|
window_from -= window_to - total_pages
|
168
228
|
window_to = total_pages
|
169
|
-
|
229
|
+
end
|
230
|
+
if window_from < 1
|
170
231
|
window_to += 1 - window_from
|
171
232
|
window_from = 1
|
233
|
+
window_to = total_pages if window_to > total_pages
|
172
234
|
end
|
173
235
|
|
174
236
|
visible = (1..total_pages).to_a
|
@@ -180,39 +242,79 @@ module WillPaginate
|
|
180
242
|
visible
|
181
243
|
end
|
182
244
|
|
183
|
-
def page_link_or_span(page, span_class
|
245
|
+
def page_link_or_span(page, span_class, text = nil)
|
184
246
|
text ||= page.to_s
|
247
|
+
classnames = Array[*span_class]
|
248
|
+
|
185
249
|
if page and page != current_page
|
186
|
-
@template.link_to text,
|
250
|
+
@template.link_to text, url_for(page), :rel => rel_value(page), :class => classnames[1]
|
187
251
|
else
|
188
|
-
@template.content_tag :span, text, :class =>
|
252
|
+
@template.content_tag :span, text, :class => classnames.join(' ')
|
189
253
|
end
|
190
254
|
end
|
191
255
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
256
|
+
# Returns URL params for +page_link_or_span+, taking the current GET params
|
257
|
+
# and <tt>:params</tt> option into account.
|
258
|
+
def url_for(page)
|
259
|
+
unless @url_string
|
260
|
+
@url_params = { :escape => false }
|
261
|
+
# page links should preserve GET parameters
|
262
|
+
stringified_merge @url_params, @template.params if @template.request.get?
|
263
|
+
stringified_merge @url_params, @options[:params] if @options[:params]
|
264
|
+
|
265
|
+
if param_name.index(/[^\w-]/)
|
266
|
+
page_param = (defined?(CGIMethods) ? CGIMethods : ActionController::AbstractRequest).
|
267
|
+
parse_query_parameters("#{param_name}=#{page}")
|
268
|
+
|
269
|
+
stringified_merge @url_params, page_param
|
270
|
+
else
|
271
|
+
@url_params[param_name] = page
|
272
|
+
end
|
273
|
+
|
274
|
+
url = @template.url_for(@url_params)
|
275
|
+
@url_string = url.sub(/([?&]#{CGI.escape param_name}=)#{page}/, '\1@')
|
276
|
+
return url
|
277
|
+
end
|
278
|
+
@url_string.sub '@', page.to_s
|
198
279
|
end
|
199
280
|
|
200
281
|
private
|
201
282
|
|
283
|
+
def rel_value(page)
|
284
|
+
case page
|
285
|
+
when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '')
|
286
|
+
when @collection.next_page; 'next'
|
287
|
+
when 1; 'start'
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
202
291
|
def current_page
|
203
292
|
@collection.current_page
|
204
293
|
end
|
205
294
|
|
206
295
|
def total_pages
|
207
|
-
@collection
|
296
|
+
@total_pages ||= WillPaginate::ViewHelpers.total_pages_for_collection(@collection)
|
208
297
|
end
|
209
298
|
|
210
299
|
def param_name
|
211
|
-
@param_name ||= @options[:param_name].
|
300
|
+
@param_name ||= @options[:param_name].to_s
|
212
301
|
end
|
213
302
|
|
214
|
-
def
|
215
|
-
|
303
|
+
def stringified_merge(target, other)
|
304
|
+
other.each do |key, value|
|
305
|
+
key = key.to_s
|
306
|
+
existing = target[key]
|
307
|
+
|
308
|
+
if value.is_a?(Hash)
|
309
|
+
target[key] = existing = {} if existing.nil?
|
310
|
+
if existing.is_a?(Hash)
|
311
|
+
stringified_merge(existing, value)
|
312
|
+
return
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
target[key] = value
|
317
|
+
end
|
216
318
|
end
|
217
319
|
end
|
218
320
|
end
|
data/test/boot.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require 'will_paginate/
|
1
|
+
require 'helper'
|
2
|
+
require 'will_paginate/array'
|
3
3
|
|
4
4
|
class ArrayPaginationTest < Test::Unit::TestCase
|
5
5
|
def test_simple
|
@@ -11,7 +11,8 @@ class ArrayPaginationTest < Test::Unit::TestCase
|
|
11
11
|
{ :page => 3, :per_page => 5, :expected => [] },
|
12
12
|
].
|
13
13
|
each do |conditions|
|
14
|
-
|
14
|
+
expected = conditions.delete :expected
|
15
|
+
assert_equal expected, collection.paginate(conditions)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
@@ -22,14 +23,8 @@ class ArrayPaginationTest < Test::Unit::TestCase
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def test_deprecated_api
|
25
|
-
|
26
|
-
|
27
|
-
assert_equal 2, result.current_page
|
28
|
-
assert_equal (11..20).to_a, result
|
29
|
-
assert_equal 50, result.total_entries
|
30
|
-
end
|
31
|
-
|
32
|
-
assert_deprecated { [].paginate nil }
|
26
|
+
assert_raise(ArgumentError) { [].paginate(2) }
|
27
|
+
assert_raise(ArgumentError) { [].paginate(2, 10) }
|
33
28
|
end
|
34
29
|
|
35
30
|
def test_total_entries_has_precedence
|
@@ -50,14 +45,28 @@ class ArrayPaginationTest < Test::Unit::TestCase
|
|
50
45
|
end
|
51
46
|
|
52
47
|
assert_equal entries, collection
|
53
|
-
assert_respond_to_all collection, %w(
|
48
|
+
assert_respond_to_all collection, %w(total_pages each offset size current_page per_page total_entries)
|
54
49
|
assert_kind_of Array, collection
|
55
50
|
assert_instance_of Array, collection.entries
|
56
51
|
assert_equal 3, collection.offset
|
57
|
-
assert_equal 4, collection.
|
52
|
+
assert_equal 4, collection.total_pages
|
58
53
|
assert !collection.out_of_bounds?
|
59
54
|
end
|
60
55
|
|
56
|
+
def test_previous_next_pages
|
57
|
+
collection = create(1, 1, 3)
|
58
|
+
assert_nil collection.previous_page
|
59
|
+
assert_equal 2, collection.next_page
|
60
|
+
|
61
|
+
collection = create(2, 1, 3)
|
62
|
+
assert_equal 1, collection.previous_page
|
63
|
+
assert_equal 3, collection.next_page
|
64
|
+
|
65
|
+
collection = create(3, 1, 3)
|
66
|
+
assert_equal 2, collection.previous_page
|
67
|
+
assert_nil collection.next_page
|
68
|
+
end
|
69
|
+
|
61
70
|
def test_out_of_bounds
|
62
71
|
entries = create(2, 3, 2){}
|
63
72
|
assert entries.out_of_bounds?
|
@@ -90,13 +99,20 @@ class ArrayPaginationTest < Test::Unit::TestCase
|
|
90
99
|
pager.replace array(0)
|
91
100
|
end
|
92
101
|
assert_equal nil, entries.total_entries
|
102
|
+
|
103
|
+
entries = create(1) do |pager|
|
104
|
+
# collection is empty and we're on page 1,
|
105
|
+
# so the whole thing must be empty, too
|
106
|
+
pager.replace array(0)
|
107
|
+
end
|
108
|
+
assert_equal 0, entries.total_entries
|
93
109
|
end
|
94
110
|
|
95
111
|
def test_invalid_page
|
96
|
-
|
112
|
+
bad_inputs = [0, -1, nil, '', 'Schnitzel']
|
97
113
|
|
98
|
-
|
99
|
-
assert_raise(WillPaginate::InvalidPage) { create
|
114
|
+
bad_inputs.each do |bad|
|
115
|
+
assert_raise(WillPaginate::InvalidPage) { create bad }
|
100
116
|
end
|
101
117
|
end
|
102
118
|
|
@@ -104,6 +120,11 @@ class ArrayPaginationTest < Test::Unit::TestCase
|
|
104
120
|
assert_raise(ArgumentError) { create(1, -1) }
|
105
121
|
end
|
106
122
|
|
123
|
+
def test_page_count_was_removed
|
124
|
+
assert_raise(NoMethodError) { create.page_count }
|
125
|
+
# It's `total_pages` now.
|
126
|
+
end
|
127
|
+
|
107
128
|
private
|
108
129
|
def create(page = 2, limit = 5, total = nil, &block)
|
109
130
|
if block_given?
|
@@ -116,16 +137,4 @@ class ArrayPaginationTest < Test::Unit::TestCase
|
|
116
137
|
def array(size = 3)
|
117
138
|
Array.new(size)
|
118
139
|
end
|
119
|
-
|
120
|
-
def collect_deprecations
|
121
|
-
old_behavior = WillPaginate::Deprecation.behavior
|
122
|
-
deprecations = []
|
123
|
-
WillPaginate::Deprecation.behavior = Proc.new do |message, callstack|
|
124
|
-
deprecations << message
|
125
|
-
end
|
126
|
-
result = yield
|
127
|
-
[result, deprecations]
|
128
|
-
ensure
|
129
|
-
WillPaginate::Deprecation.behavior = old_behavior
|
130
|
-
end
|
131
140
|
end
|
data/test/console
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
3
3
|
libs = []
|
4
|
-
dirname = File.dirname(__FILE__)
|
5
4
|
|
6
5
|
libs << 'irb/completion'
|
7
|
-
libs << File.join(
|
6
|
+
libs << File.join('lib', 'load_fixtures')
|
8
7
|
|
9
|
-
exec "#{irb}#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt"
|
8
|
+
exec "#{irb} -Ilib:test#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt"
|
data/test/database.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
sqlite3:
|
2
|
+
database: ":memory:"
|
3
|
+
adapter: sqlite3
|
4
|
+
timeout: 500
|
5
|
+
|
6
|
+
sqlite2:
|
7
|
+
database: ":memory:"
|
8
|
+
adapter: sqlite2
|
9
|
+
|
10
|
+
mysql:
|
11
|
+
adapter: mysql
|
12
|
+
username: rails
|
13
|
+
password: mislav
|
14
|
+
encoding: utf8
|
15
|
+
database: will_paginate_unittest
|
16
|
+
|
17
|
+
postgres:
|
18
|
+
adapter: postgresql
|
19
|
+
username: mislav
|
20
|
+
password: mislav
|
21
|
+
database: will_paginate_unittest
|
22
|
+
min_messages: warning
|