stilkov-will_paginate 2.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CHANGELOG.rdoc +110 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +107 -0
  4. data/Rakefile +62 -0
  5. data/examples/apple-circle.gif +0 -0
  6. data/examples/index.haml +69 -0
  7. data/examples/index.html +92 -0
  8. data/examples/pagination.css +90 -0
  9. data/examples/pagination.sass +91 -0
  10. data/init.rb +1 -0
  11. data/lib/will_paginate.rb +82 -0
  12. data/lib/will_paginate/array.rb +16 -0
  13. data/lib/will_paginate/collection.rb +146 -0
  14. data/lib/will_paginate/core_ext.rb +32 -0
  15. data/lib/will_paginate/finder.rb +260 -0
  16. data/lib/will_paginate/named_scope.rb +170 -0
  17. data/lib/will_paginate/named_scope_patch.rb +37 -0
  18. data/lib/will_paginate/version.rb +9 -0
  19. data/lib/will_paginate/view_helpers.rb +390 -0
  20. data/test/boot.rb +21 -0
  21. data/test/collection_test.rb +143 -0
  22. data/test/console +8 -0
  23. data/test/database.yml +22 -0
  24. data/test/finder_test.rb +476 -0
  25. data/test/fixtures/admin.rb +3 -0
  26. data/test/fixtures/developer.rb +14 -0
  27. data/test/fixtures/developers_projects.yml +13 -0
  28. data/test/fixtures/project.rb +15 -0
  29. data/test/fixtures/projects.yml +6 -0
  30. data/test/fixtures/replies.yml +29 -0
  31. data/test/fixtures/reply.rb +7 -0
  32. data/test/fixtures/schema.rb +38 -0
  33. data/test/fixtures/topic.rb +10 -0
  34. data/test/fixtures/topics.yml +30 -0
  35. data/test/fixtures/user.rb +2 -0
  36. data/test/fixtures/users.yml +35 -0
  37. data/test/helper.rb +37 -0
  38. data/test/lib/activerecord_test_case.rb +36 -0
  39. data/test/lib/activerecord_test_connector.rb +75 -0
  40. data/test/lib/load_fixtures.rb +11 -0
  41. data/test/lib/view_test_process.rb +171 -0
  42. data/test/tasks.rake +59 -0
  43. data/test/view_test.rb +363 -0
  44. metadata +132 -0
@@ -0,0 +1,9 @@
1
+ module WillPaginate
2
+ module VERSION
3
+ MAJOR = 2
4
+ MINOR = 3
5
+ TINY = 5
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,390 @@
1
+ require 'will_paginate/core_ext'
2
+
3
+ module WillPaginate
4
+ # = Will Paginate view helpers
5
+ #
6
+ # The main view helper, #will_paginate, renders
7
+ # pagination links for the given collection. The helper itself is lightweight
8
+ # and serves only as a wrapper around LinkRenderer instantiation; the
9
+ # renderer then does all the hard work of generating the HTML.
10
+ #
11
+ # == Global options for helpers
12
+ #
13
+ # Options for pagination helpers are optional and get their default values from the
14
+ # <tt>WillPaginate::ViewHelpers.pagination_options</tt> hash. You can write to this hash to
15
+ # override default options on the global level:
16
+ #
17
+ # WillPaginate::ViewHelpers.pagination_options[:previous_label] = 'Previous page'
18
+ #
19
+ # By putting this into "config/initializers/will_paginate.rb" (or simply environment.rb in
20
+ # older versions of Rails) you can easily translate link texts to previous
21
+ # and next pages, as well as override some other defaults to your liking.
22
+ module ViewHelpers
23
+ # default options that can be overridden on the global level
24
+ @@pagination_options = {
25
+ :class => 'pagination',
26
+ :previous_label => '&laquo; Previous',
27
+ :next_label => 'Next &raquo;',
28
+ :inner_window => 4, # links around the current page
29
+ :outer_window => 1, # links around beginning and end
30
+ :separator => ' ', # single space is friendly to spiders and non-graphic browsers
31
+ :param_name => :page,
32
+ :params => nil,
33
+ :renderer => 'WillPaginate::LinkRenderer',
34
+ :page_links => true,
35
+ :container => true
36
+ }
37
+ mattr_reader :pagination_options
38
+
39
+ # Renders Digg/Flickr-style pagination for a WillPaginate::Collection
40
+ # object. Nil is returned if there is only one page in total; no point in
41
+ # rendering the pagination in that case...
42
+ #
43
+ # ==== Options
44
+ # Display options:
45
+ # * <tt>:previous_label</tt> -- default: "« Previous" (this parameter is called <tt>:prev_label</tt> in versions <b>2.3.2</b> and older!)
46
+ # * <tt>:next_label</tt> -- default: "Next »"
47
+ # * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true)
48
+ # * <tt>:inner_window</tt> -- how many links are shown around the current page (default: 4)
49
+ # * <tt>:outer_window</tt> -- how many links are around the first and the last page (default: 1)
50
+ # * <tt>:separator</tt> -- string separator for page HTML elements (default: single space)
51
+ #
52
+ # HTML options:
53
+ # * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pagination")
54
+ # * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to
55
+ # false only when you are rendering your own pagination markup (default: true)
56
+ # * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID
57
+ # automatically generated from the class name of objects in collection: for example, paginating
58
+ # ArticleComment models would yield an ID of "article_comments_pagination".
59
+ #
60
+ # Advanced options:
61
+ # * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>)
62
+ # * <tt>:params</tt> -- additional parameters when generating pagination links
63
+ # (eg. <tt>:controller => "foo", :action => nil</tt>)
64
+ # * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default:
65
+ # <tt>WillPaginate::LinkRenderer</tt>)
66
+ #
67
+ # All options not recognized by will_paginate will become HTML attributes on the container
68
+ # element for pagination links (the DIV). For example:
69
+ #
70
+ # <%= will_paginate @posts, :style => 'font-size: small' %>
71
+ #
72
+ # ... will result in:
73
+ #
74
+ # <div class="pagination" style="font-size: small"> ... </div>
75
+ #
76
+ # ==== Using the helper without arguments
77
+ # If the helper is called without passing in the collection object, it will
78
+ # try to read from the instance variable inferred by the controller name.
79
+ # For example, calling +will_paginate+ while the current controller is
80
+ # PostsController will result in trying to read from the <tt>@posts</tt>
81
+ # variable. Example:
82
+ #
83
+ # <%= will_paginate :id => true %>
84
+ #
85
+ # ... will result in <tt>@post</tt> collection getting paginated:
86
+ #
87
+ # <div class="pagination" id="posts_pagination"> ... </div>
88
+ #
89
+ def will_paginate(collection = nil, options = {})
90
+ options, collection = collection, nil if collection.is_a? Hash
91
+ unless collection or !controller
92
+ collection_name = "@#{controller.controller_name}"
93
+ collection = instance_variable_get(collection_name)
94
+ raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " +
95
+ "forget to pass the collection object for will_paginate?" unless collection
96
+ end
97
+ # early exit if there is nothing to render
98
+ return nil unless WillPaginate::ViewHelpers.total_pages_for_collection(collection) > 1
99
+
100
+ options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers.pagination_options
101
+ if options[:prev_label]
102
+ WillPaginate::Deprecation::warn(":prev_label view parameter is now :previous_label; the old name has been deprecated.")
103
+ options[:previous_label] = options.delete(:prev_label)
104
+ end
105
+
106
+ # get the renderer instance
107
+ renderer = case options[:renderer]
108
+ when String
109
+ options[:renderer].to_s.constantize.new
110
+ when Class
111
+ options[:renderer].new
112
+ else
113
+ options[:renderer]
114
+ end
115
+ # render HTML for pagination
116
+ renderer.prepare collection, options, self
117
+ renderer.to_html
118
+ end
119
+
120
+ # Wrapper for rendering pagination links at both top and bottom of a block
121
+ # of content.
122
+ #
123
+ # <% paginated_section @posts do %>
124
+ # <ol id="posts">
125
+ # <% for post in @posts %>
126
+ # <li> ... </li>
127
+ # <% end %>
128
+ # </ol>
129
+ # <% end %>
130
+ #
131
+ # will result in:
132
+ #
133
+ # <div class="pagination"> ... </div>
134
+ # <ol id="posts">
135
+ # ...
136
+ # </ol>
137
+ # <div class="pagination"> ... </div>
138
+ #
139
+ # Arguments are passed to a <tt>will_paginate</tt> call, so the same options
140
+ # apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with two
141
+ # blocks of pagination links sharing the same ID (which is invalid HTML).
142
+ def paginated_section(*args, &block)
143
+ pagination = will_paginate(*args).to_s
144
+
145
+ unless ActionView::Base.respond_to? :erb_variable
146
+ concat pagination
147
+ yield
148
+ concat pagination
149
+ else
150
+ content = pagination + capture(&block) + pagination
151
+ concat(content, block.binding)
152
+ end
153
+ end
154
+
155
+ # Renders a helpful message with numbers of displayed vs. total entries.
156
+ # You can use this as a blueprint for your own, similar helpers.
157
+ #
158
+ # <%= page_entries_info @posts %>
159
+ # #-> Displaying posts 6 - 10 of 26 in total
160
+ #
161
+ # By default, the message will use the humanized class name of objects
162
+ # in collection: for instance, "project types" for ProjectType models.
163
+ # Override this with the <tt>:entry_name</tt> parameter:
164
+ #
165
+ # <%= page_entries_info @posts, :entry_name => 'item' %>
166
+ # #-> Displaying items 6 - 10 of 26 in total
167
+ def page_entries_info(collection, options = {})
168
+ entry_name = options[:entry_name] ||
169
+ (collection.empty?? 'entry' : collection.first.class.name.underscore.sub('_', ' '))
170
+
171
+ if collection.total_pages < 2
172
+ case collection.size
173
+ when 0; "No #{entry_name.pluralize} found"
174
+ when 1; "Displaying <b>1</b> #{entry_name}"
175
+ else; "Displaying <b>all #{collection.size}</b> #{entry_name.pluralize}"
176
+ end
177
+ else
178
+ %{Displaying #{entry_name.pluralize} <b>%d&nbsp;-&nbsp;%d</b> of <b>%d</b> in total} % [
179
+ collection.offset + 1,
180
+ collection.offset + collection.length,
181
+ collection.total_entries
182
+ ]
183
+ end
184
+ end
185
+
186
+ def self.total_pages_for_collection(collection) #:nodoc:
187
+ if collection.respond_to?('page_count') and !collection.respond_to?('total_pages')
188
+ WillPaginate::Deprecation.warn <<-MSG
189
+ You are using a paginated collection of class #{collection.class.name}
190
+ which conforms to the old API of WillPaginate::Collection by using
191
+ `page_count`, while the current method name is `total_pages`. Please
192
+ upgrade yours or 3rd-party code that provides the paginated collection.
193
+ MSG
194
+ class << collection
195
+ def total_pages; page_count; end
196
+ end
197
+ end
198
+ collection.total_pages
199
+ end
200
+ end
201
+
202
+ # This class does the heavy lifting of actually building the pagination
203
+ # links. It is used by the <tt>will_paginate</tt> helper internally.
204
+ class LinkRenderer
205
+
206
+ # The gap in page links is represented by:
207
+ #
208
+ # <span class="gap">&hellip;</span>
209
+ attr_accessor :gap_marker
210
+
211
+ def initialize
212
+ @gap_marker = '<span class="gap">&hellip;</span>'
213
+ end
214
+
215
+ # * +collection+ is a WillPaginate::Collection instance or any other object
216
+ # that conforms to that API
217
+ # * +options+ are forwarded from +will_paginate+ view helper
218
+ # * +template+ is the reference to the template being rendered
219
+ def prepare(collection, options, template)
220
+ @collection = collection
221
+ @options = options
222
+ @template = template
223
+
224
+ # reset values in case we're re-using this instance
225
+ @total_pages = @param_name = @url_string = nil
226
+ end
227
+
228
+ # Process it! This method returns the complete HTML string which contains
229
+ # pagination links. Feel free to subclass LinkRenderer and change this
230
+ # method as you see fit.
231
+ def to_html
232
+ links = @options[:page_links] ? windowed_links : []
233
+ # previous/next buttons
234
+ links.unshift page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:previous_label])
235
+ links.push page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label])
236
+
237
+ html = links.join(@options[:separator])
238
+ @options[:container] ? @template.content_tag(:div, html, html_attributes) : html
239
+ end
240
+
241
+ # Returns the subset of +options+ this instance was initialized with that
242
+ # represent HTML attributes for the container element of pagination links.
243
+ def html_attributes
244
+ return @html_attributes if @html_attributes
245
+ @html_attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class])
246
+ # pagination of Post models will have the ID of "posts_pagination"
247
+ if @options[:container] and @options[:id] === true
248
+ @html_attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination'
249
+ end
250
+ @html_attributes
251
+ end
252
+
253
+ protected
254
+
255
+ # Collects link items for visible page numbers.
256
+ def windowed_links
257
+ prev = nil
258
+
259
+ visible_page_numbers.inject [] do |links, n|
260
+ # detect gaps:
261
+ links << gap_marker if prev and n > prev + 1
262
+ links << page_link_or_span(n, 'current')
263
+ prev = n
264
+ links
265
+ end
266
+ end
267
+
268
+ # Calculates visible page numbers using the <tt>:inner_window</tt> and
269
+ # <tt>:outer_window</tt> options.
270
+ def visible_page_numbers
271
+ inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
272
+ window_from = current_page - inner_window
273
+ window_to = current_page + inner_window
274
+
275
+ # adjust lower or upper limit if other is out of bounds
276
+ if window_to > total_pages
277
+ window_from -= window_to - total_pages
278
+ window_to = total_pages
279
+ end
280
+ if window_from < 1
281
+ window_to += 1 - window_from
282
+ window_from = 1
283
+ window_to = total_pages if window_to > total_pages
284
+ end
285
+
286
+ visible = (1..total_pages).to_a
287
+ left_gap = (2 + outer_window)...window_from
288
+ right_gap = (window_to + 1)...(total_pages - outer_window)
289
+ visible -= left_gap.to_a if left_gap.last - left_gap.first > 1
290
+ visible -= right_gap.to_a if right_gap.last - right_gap.first > 1
291
+
292
+ visible
293
+ end
294
+
295
+ def page_link_or_span(page, span_class, text = nil)
296
+ text ||= page.to_s
297
+
298
+ if page and page != current_page
299
+ classnames = span_class && span_class.index(' ') && span_class.split(' ', 2).last
300
+ page_link page, text, :rel => rel_value(page), :class => classnames
301
+ else
302
+ page_span page, text, :class => span_class
303
+ end
304
+ end
305
+
306
+ def page_link(page, text, attributes = {})
307
+ @template.link_to text, url_for(page), attributes
308
+ end
309
+
310
+ def page_span(page, text, attributes = {})
311
+ @template.content_tag :span, text, attributes
312
+ end
313
+
314
+ # Returns URL params for +page_link_or_span+, taking the current GET params
315
+ # and <tt>:params</tt> option into account.
316
+ def url_for(page)
317
+ page_one = page == 1
318
+ unless @url_string and !page_one
319
+ @url_params = {}
320
+ # page links should preserve GET parameters
321
+ stringified_merge @url_params, @template.params if @template.request.get?
322
+ stringified_merge @url_params, @options[:params] if @options[:params]
323
+
324
+ if complex = param_name.index(/[^\w-]/)
325
+ page_param = (defined?(CGIMethods) ? CGIMethods : ActionController::AbstractRequest).
326
+ parse_query_parameters("#{param_name}=#{page}")
327
+
328
+ stringified_merge @url_params, page_param
329
+ else
330
+ @url_params[param_name] = page_one ? 1 : 2
331
+ end
332
+
333
+ url = @template.url_for(@url_params)
334
+ return url if page_one
335
+
336
+ if complex
337
+ @url_string = url.sub(%r!((?:\?|&amp;)#{CGI.escape param_name}=)#{page}!, '\1@')
338
+ return url
339
+ else
340
+ @url_string = url
341
+ @url_params[param_name] = 3
342
+ @template.url_for(@url_params).split(//).each_with_index do |char, i|
343
+ if char == '3' and url[i, 1] == '2'
344
+ @url_string[i] = '@'
345
+ break
346
+ end
347
+ end
348
+ end
349
+ end
350
+ # finally!
351
+ @url_string.sub '@', page.to_s
352
+ end
353
+
354
+ private
355
+
356
+ def rel_value(page)
357
+ case page
358
+ when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '')
359
+ when @collection.next_page; 'next'
360
+ when 1; 'start'
361
+ end
362
+ end
363
+
364
+ def current_page
365
+ @collection.current_page
366
+ end
367
+
368
+ def total_pages
369
+ @total_pages ||= WillPaginate::ViewHelpers.total_pages_for_collection(@collection)
370
+ end
371
+
372
+ def param_name
373
+ @param_name ||= @options[:param_name].to_s
374
+ end
375
+
376
+ # Recursively merge into target hash by using stringified keys from the other one
377
+ def stringified_merge(target, other)
378
+ other.each do |key, value|
379
+ key = key.to_s # this line is what it's all about!
380
+ existing = target[key]
381
+
382
+ if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
383
+ stringified_merge(existing || (target[key] = {}), value)
384
+ else
385
+ target[key] = value
386
+ end
387
+ end
388
+ end
389
+ end
390
+ end
data/test/boot.rb ADDED
@@ -0,0 +1,21 @@
1
+ plugin_root = File.join(File.dirname(__FILE__), '..')
2
+ version = ENV['RAILS_VERSION']
3
+ version = nil if version and version == ""
4
+
5
+ # first look for a symlink to a copy of the framework
6
+ if !version and framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p }
7
+ puts "found framework root: #{framework_root}"
8
+ # this allows for a plugin to be tested outside of an app and without Rails gems
9
+ $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib"
10
+ else
11
+ # simply use installed gems if available
12
+ puts "using Rails#{version ? ' ' + version : nil} gems"
13
+ require 'rubygems'
14
+
15
+ if version
16
+ gem 'rails', version
17
+ else
18
+ gem 'actionpack'
19
+ gem 'activerecord'
20
+ end
21
+ end
@@ -0,0 +1,143 @@
1
+ require 'helper'
2
+ require 'will_paginate/array'
3
+
4
+ class ArrayPaginationTest < Test::Unit::TestCase
5
+
6
+ def setup ; end
7
+
8
+ def test_simple
9
+ collection = ('a'..'e').to_a
10
+
11
+ [{ :page => 1, :per_page => 3, :expected => %w( a b c ) },
12
+ { :page => 2, :per_page => 3, :expected => %w( d e ) },
13
+ { :page => 1, :per_page => 5, :expected => %w( a b c d e ) },
14
+ { :page => 3, :per_page => 5, :expected => [] },
15
+ ].
16
+ each do |conditions|
17
+ expected = conditions.delete :expected
18
+ assert_equal expected, collection.paginate(conditions)
19
+ end
20
+ end
21
+
22
+ def test_defaults
23
+ result = (1..50).to_a.paginate
24
+ assert_equal 1, result.current_page
25
+ assert_equal 30, result.size
26
+ end
27
+
28
+ def test_deprecated_api
29
+ assert_raise(ArgumentError) { [].paginate(2) }
30
+ assert_raise(ArgumentError) { [].paginate(2, 10) }
31
+ end
32
+
33
+ def test_total_entries_has_precedence
34
+ result = %w(a b c).paginate :total_entries => 5
35
+ assert_equal 5, result.total_entries
36
+ end
37
+
38
+ def test_argument_error_with_params_and_another_argument
39
+ assert_raise ArgumentError do
40
+ [].paginate({}, 5)
41
+ end
42
+ end
43
+
44
+ def test_paginated_collection
45
+ entries = %w(a b c)
46
+ collection = create(2, 3, 10) do |pager|
47
+ assert_equal entries, pager.replace(entries)
48
+ end
49
+
50
+ assert_equal entries, collection
51
+ assert_respond_to_all collection, %w(total_pages each offset size current_page per_page total_entries)
52
+ assert_kind_of Array, collection
53
+ assert_instance_of Array, collection.entries
54
+ assert_equal 3, collection.offset
55
+ assert_equal 4, collection.total_pages
56
+ assert !collection.out_of_bounds?
57
+ end
58
+
59
+ def test_previous_next_pages
60
+ collection = create(1, 1, 3)
61
+ assert_nil collection.previous_page
62
+ assert_equal 2, collection.next_page
63
+
64
+ collection = create(2, 1, 3)
65
+ assert_equal 1, collection.previous_page
66
+ assert_equal 3, collection.next_page
67
+
68
+ collection = create(3, 1, 3)
69
+ assert_equal 2, collection.previous_page
70
+ assert_nil collection.next_page
71
+ end
72
+
73
+ def test_out_of_bounds
74
+ entries = create(2, 3, 2){}
75
+ assert entries.out_of_bounds?
76
+
77
+ entries = create(1, 3, 2){}
78
+ assert !entries.out_of_bounds?
79
+ end
80
+
81
+ def test_guessing_total_count
82
+ entries = create do |pager|
83
+ # collection is shorter than limit
84
+ pager.replace array
85
+ end
86
+ assert_equal 8, entries.total_entries
87
+
88
+ entries = create(2, 5, 10) do |pager|
89
+ # collection is shorter than limit, but we have an explicit count
90
+ pager.replace array
91
+ end
92
+ assert_equal 10, entries.total_entries
93
+
94
+ entries = create do |pager|
95
+ # collection is the same as limit; we can't guess
96
+ pager.replace array(5)
97
+ end
98
+ assert_equal nil, entries.total_entries
99
+
100
+ entries = create do |pager|
101
+ # collection is empty; we can't guess
102
+ pager.replace array(0)
103
+ end
104
+ assert_equal nil, entries.total_entries
105
+
106
+ entries = create(1) do |pager|
107
+ # collection is empty and we're on page 1,
108
+ # so the whole thing must be empty, too
109
+ pager.replace array(0)
110
+ end
111
+ assert_equal 0, entries.total_entries
112
+ end
113
+
114
+ def test_invalid_page
115
+ bad_inputs = [0, -1, nil, '', 'Schnitzel']
116
+
117
+ bad_inputs.each do |bad|
118
+ assert_raise(WillPaginate::InvalidPage) { create bad }
119
+ end
120
+ end
121
+
122
+ def test_invalid_per_page_setting
123
+ assert_raise(ArgumentError) { create(1, -1) }
124
+ end
125
+
126
+ def test_page_count_was_removed
127
+ assert_raise(NoMethodError) { create.page_count }
128
+ # It's `total_pages` now.
129
+ end
130
+
131
+ private
132
+ def create(page = 2, limit = 5, total = nil, &block)
133
+ if block_given?
134
+ WillPaginate::Collection.create(page, limit, total, &block)
135
+ else
136
+ WillPaginate::Collection.new(page, limit, total)
137
+ end
138
+ end
139
+
140
+ def array(size = 3)
141
+ Array.new(size)
142
+ end
143
+ end