will_paginate 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

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