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.

@@ -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
@@ -1,7 +1,7 @@
1
1
  module WillPaginate #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 2
4
- MINOR = 1
4
+ MINOR = 2
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -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 specify the collection object for will_paginate?" unless collection
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.page_count > 1
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, 'disabled', @options[:prev_label])
128
- links.push page_link_or_span(@collection.next_page, 'disabled', @options[:next_label])
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
- def gap_marker; '...'; end
198
+ # The gap in page links is represented by:
199
+ #
200
+ # <span class="gap">&hellip;</span>
201
+ def gap_marker
202
+ '<span class="gap">&hellip;</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
- elsif window_from < 1
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 = 'current', text = nil)
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, url_options(page)
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 => span_class
252
+ @template.content_tag :span, text, :class => classnames.join(' ')
189
253
  end
190
254
  end
191
255
 
192
- def url_options(page)
193
- options = { param_name => page }
194
- # page links should preserve GET parameters
195
- options = params.merge(options) if @template.request.get?
196
- options.rec_merge!(@options[:params]) if @options[:params]
197
- return options
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.page_count
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].to_sym
300
+ @param_name ||= @options[:param_name].to_s
212
301
  end
213
302
 
214
- def params
215
- @params ||= @template.params.to_hash.symbolize_keys
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
@@ -19,5 +19,3 @@ else
19
19
  gem 'activerecord'
20
20
  end
21
21
  end
22
-
23
- $:.unshift "#{plugin_root}/lib"
@@ -1,5 +1,5 @@
1
- require File.dirname(__FILE__) + '/helper'
2
- require 'will_paginate/core_ext'
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
- assert_equal conditions[:expected], collection.paginate(conditions.slice(:page, :per_page))
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
- assert_deprecated 'paginate API' do
26
- result = (1..50).to_a.paginate(2, 10)
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(page_count each offset size current_page per_page total_entries)
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.page_count
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
- bad_input = [0, -1, nil, '', 'Schnitzel']
112
+ bad_inputs = [0, -1, nil, '', 'Schnitzel']
97
113
 
98
- bad_input.each do |bad|
99
- assert_raise(WillPaginate::InvalidPage) { create(bad) }
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(dirname, 'lib', 'load_fixtures')
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