objectreload-pagination 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ == 0.1.0, released 2009-08-08
2
+
3
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2009 Mateusz Drożdżyński
2
+ Based partially on will_paginate (c) 2007 PJ Hyett and Mislav Marohnić
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,35 @@
1
+ Display options:
2
+ :previous_label -- default: "« Previous" (this parameter is called :prev_label in versions 2.3.2 and older!)
3
+ :next_label -- default: "Next »"
4
+ :page_links -- when false, only previous/next links are rendered (default: true)
5
+ :inner_window -- how many links are shown around the current page (default: 4)
6
+ :outer_window -- how many links are around the first and the last page (default: 1)
7
+ :separator -- string separator for page HTML elements (default: single space)
8
+ :controls -- display controls only at the :top or :bottom of the pagination block (default: :both)
9
+ :per_page -- number of items displayed per page (default: 10)
10
+
11
+ HTML options:
12
+ :class -- CSS class name for the generated DIV (default: "pagination")
13
+ :container -- toggles rendering of the DIV container for pagination links, set to false only when you are rendering your own pagination markup (default: true)
14
+ :id -- HTML ID for the container (default: nil). Pass +true+ to have the ID automatically generated from the class name of objects in collection: for example, paginating
15
+ ArticleComment models would yield an ID of "article_comments_pagination".
16
+
17
+ Advanced options:
18
+ :param_name -- parameter name for page number in URLs (default: :page)
19
+ :params -- additional parameters when generating pagination links (eg. :controller => "foo", :action => nil)
20
+ :renderer -- class name, class or instance of a link renderer (default: Pagination::LinkRenderer)
21
+
22
+
23
+ All options not recognized by paginate will become HTML attributes on the container
24
+ element for pagination links (the DIV).
25
+ For example:
26
+
27
+ <% paginate @posts, :style => 'font-size: small' do |posts| %>
28
+ ...
29
+ <% end %>
30
+
31
+ ... will result in:
32
+
33
+ <div class="pagination" style="font-size: small"> ... </div>
34
+
35
+ No changes in controllers.
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "objectreload-pagination"
8
+ gem.summary = "Simple pagination - without changes in controllers"
9
+ gem.email = "gems@objectreload.com"
10
+ gem.homepage = "http://github.com/objectreload/pagination"
11
+ gem.authors = ["Mateusz Drozdzynski"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+ task :test => :check_dependencies
40
+
41
+ task :default => :test
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ if File.exist?('VERSION')
46
+ version = File.read('VERSION')
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "pagination_gem #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'pagination'
@@ -0,0 +1,19 @@
1
+ ActiveRecord::Base.class_eval do
2
+ class << self
3
+ VALID_FIND_OPTIONS = VALID_FIND_OPTIONS + [:eager]
4
+
5
+ def find_every_with_scope(options)
6
+ if options.delete(:eager)
7
+ find_every_without_scope(options)
8
+ else
9
+ scoped(options)
10
+ end
11
+ end
12
+ alias_method_chain :find_every, :scope
13
+
14
+ def find_initial_with_eager_loading(options)
15
+ find_initial_without_eager_loading(options.merge(:eager => true))
16
+ end
17
+ alias_method_chain :find_initial, :eager_loading
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ ActiveRecord::NamedScope::Scope.class_eval do
2
+ private
3
+ def load_found(*args)
4
+ @found = find(:all, :eager => true)
5
+ end
6
+ end
data/lib/pagination.rb ADDED
@@ -0,0 +1,12 @@
1
+ # Loads extensions to Ruby on Rails libraries needed to support pagination and lazy loading
2
+ require 'active_record/base_extensions'
3
+ require 'active_record/scope_extensions'
4
+
5
+ # Loads internal pagination classes
6
+ require 'pagination/collection'
7
+ require 'pagination/named_scope'
8
+ require 'pagination/enumerable'
9
+ require 'pagination/view_helpers'
10
+
11
+ # Loads the :paginate view helper
12
+ ActionView::Base.send :include, Pagination::ViewHelpers
@@ -0,0 +1,62 @@
1
+ module Pagination
2
+ class InvalidPage < ArgumentError
3
+ def initialize(page, max = 1)
4
+ super "#{page} must be greater than 0" if page < 1
5
+ super "#{page} must be less than #{max}" if page >= 1
6
+ end
7
+ end
8
+
9
+ class Collection < Array
10
+ attr_reader :current_page, :per_page, :total_entries, :total_pages
11
+
12
+ def initialize(options = {})
13
+ @collection = options[:collection]
14
+
15
+ @current_page = options[:current_page].to_i
16
+ raise InvalidPage.new(@current_page) if @current_page < 1
17
+
18
+ @per_page = options[:per_page].to_i
19
+ raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_page} given)" if @per_page < 1
20
+
21
+ self.replace(@collection.paginate(:limit => limit, :offset => offset))
22
+
23
+ self.total_entries ||= @collection.count
24
+ raise InvalidPage.new(current_page, total_pages) if current_page > total_pages
25
+ end
26
+
27
+ def previous_page
28
+ current_page - 1 if current_page > 1
29
+ end
30
+
31
+ def next_page
32
+ current_page + 1 if current_page < total_pages
33
+ end
34
+
35
+ protected
36
+
37
+ def limit
38
+ per_page
39
+ end
40
+
41
+ def offset
42
+ offset = (current_page-1)*limit
43
+ end
44
+
45
+ def total_entries=(number)
46
+ @total_entries = number.to_i
47
+ @total_pages = @total_entries > 0 ? (@total_entries / per_page.to_f).ceil : 1
48
+ end
49
+
50
+ def replace(array)
51
+ result = super
52
+
53
+ # The collection is shorter then page limit? Rejoice, because
54
+ # then we know that we are on the last page!
55
+ if total_entries.nil? and length < per_page and (current_page == 1 or length > 0)
56
+ self.total_entries = offset + length
57
+ end
58
+
59
+ result
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,5 @@
1
+ Enumerable.module_eval do
2
+ def paginate(options = {})
3
+ self[options[:offset]..(options[:offset] + options[:limit] - 1)] || []
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ ActiveRecord::NamedScope::Scope.class_eval do
2
+ def paginate(options = {})
3
+ scoped(:limit => options[:limit], :offset => options[:offset])
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module Pagination
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,322 @@
1
+ module Pagination
2
+ # = Pagination view helpers
3
+ #
4
+ # The main view helper, #paginate, renders
5
+ # pagination links around (top and bottom) given collection. The helper itself is lightweight
6
+ # and serves only as a wrapper around LinkRenderer instantiation; the
7
+ # renderer then does all the hard work of generating the HTML.
8
+ #
9
+ # == Global options for helpers
10
+ #
11
+ # Options for pagination helpers are optional and get their default values from the
12
+ # <tt>Pagination::ViewHelpers.pagination_options</tt> hash. You can write to this hash to
13
+ # override default options on the global level:
14
+ #
15
+ # Pagination::ViewHelpers.pagination_options[:previous_label] = 'Previous page'
16
+ #
17
+ # By putting this into "config/initializers/pagination.rb" (or simply environment.rb in
18
+ # older versions of Rails) you can easily translate link texts to previous
19
+ # and next pages, as well as override some other defaults to your liking.
20
+ module ViewHelpers
21
+ # default options that can be overridden on the global level
22
+ @@pagination_options = {
23
+ :class => 'pagination',
24
+ :previous_label => '&laquo; Previous',
25
+ :next_label => 'Next &raquo;',
26
+ :inner_window => 4, # links around the current page
27
+ :outer_window => 1, # links around beginning and end
28
+ :separator => ' ', # single space is friendly to spiders and non-graphic browsers
29
+ :controls => :both,
30
+ :per_page => 10,
31
+ :param_name => :page,
32
+ :params => nil,
33
+ :renderer => 'Pagination::LinkRenderer',
34
+ :page_links => true,
35
+ :container => true
36
+ }
37
+ mattr_reader :pagination_options
38
+
39
+ # Renders Digg/Flickr-style pagination for a Pagination::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
+ # * <tt>:controls</tt> -- display controls only at the <tt>:top</tt> or <tt>:bottom</tt> of the pagination block (default: <tt>:both</tt>)
52
+ # * <tt>:per_page</tt> -- number of items displayed per page (default: 10)
53
+ #
54
+ # HTML options:
55
+ # * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pagination")
56
+ # * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to
57
+ # false only when you are rendering your own pagination markup (default: true)
58
+ # * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID
59
+ # automatically generated from the class name of objects in collection: for example, paginating
60
+ # ArticleComment models would yield an ID of "article_comments_pagination".
61
+ #
62
+ # Advanced options:
63
+ # * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>)
64
+ # * <tt>:params</tt> -- additional parameters when generating pagination links
65
+ # (eg. <tt>:controller => "foo", :action => nil</tt>)
66
+ # * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default:
67
+ # <tt>Pagination::LinkRenderer</tt>)
68
+ #
69
+ # All options not recognized by paginate will become HTML attributes on the container
70
+ # element for pagination links (the DIV). For example:
71
+ #
72
+ # <% paginate @posts, :style => 'font-size: small' do |posts| %>
73
+ # ...
74
+ # <% end %>
75
+ #
76
+ # ... will result in:
77
+ #
78
+ # <div class="pagination" style="font-size: small"> ... </div>
79
+ #
80
+ def paginate(collection = [], options = {}, &block)
81
+ options = options.symbolize_keys.reverse_merge Pagination::ViewHelpers.pagination_options
82
+
83
+ collection = Pagination::Collection.new(options.merge(:collection => collection, :current_page => params[options[:param_name]]|| 1))
84
+ yield collection and return nil unless collection.total_pages > 1
85
+
86
+ # get the renderer instance
87
+ renderer = case options[:renderer]
88
+ when String
89
+ options[:renderer].to_s.constantize.new
90
+ when Class
91
+ options[:renderer].new
92
+ else
93
+ options[:renderer]
94
+ end
95
+
96
+ # render HTML for pagination
97
+ renderer.prepare collection, options, self
98
+ pagination = renderer.to_html.to_s
99
+
100
+ if block_given?
101
+ top = [:top, :both].include?(options[:controls]) ? pagination : ""
102
+ bottom = [:bottom, :both].include?(options[:controls]) ? pagination : ""
103
+ unless ActionView::Base.respond_to? :erb_variable
104
+ concat top
105
+ yield collection
106
+ unless bottom.empty?
107
+ concat bottom
108
+ end
109
+ else
110
+ content = top + capture(&block) + bottom
111
+ concat(content, block.binding)
112
+ end
113
+ else
114
+ collection
115
+ end
116
+ end
117
+ end
118
+
119
+ # This class does the heavy lifting of actually building the pagination
120
+ # links. It is used by the <tt>paginate</tt> helper internally.
121
+ class LinkRenderer
122
+
123
+ # The gap in page links is represented by:
124
+ #
125
+ # <span class="gap">&hellip;</span>
126
+ attr_accessor :gap_marker
127
+
128
+ def initialize
129
+ @gap_marker = '<span class="gap">&hellip;</span>'
130
+ end
131
+
132
+ # * +collection+ is a Pagination::Collection instance or any other object
133
+ # that conforms to that API
134
+ # * +options+ are forwarded from +paginate+ view helper
135
+ # * +template+ is the reference to the template being rendered
136
+ def prepare(collection, options, template)
137
+ @collection = collection
138
+ @options = options
139
+ @template = template
140
+
141
+ # reset values in case we're re-using this instance
142
+ @total_pages = @param_name = @url_string = nil
143
+ end
144
+
145
+ # Process it! This method returns the complete HTML string which contains
146
+ # pagination links. Feel free to subclass LinkRenderer and change this
147
+ # method as you see fit.
148
+ def to_html
149
+ links = @options[:page_links] ? windowed_links : []
150
+ # previous/next buttons
151
+ links.unshift page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:previous_label])
152
+ links.push page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label])
153
+
154
+ html = links.join(@options[:separator])
155
+ @options[:container] ? @template.content_tag(:div, html, html_attributes) : html
156
+ end
157
+
158
+ # Returns the subset of +options+ this instance was initialized with that
159
+ # represent HTML attributes for the container element of pagination links.
160
+ def html_attributes
161
+ return @html_attributes if @html_attributes
162
+ @html_attributes = @options.except *(Pagination::ViewHelpers.pagination_options.keys - [:class])
163
+ # pagination of Post models will have the ID of "posts_pagination"
164
+ if @options[:container] and @options[:id] === true
165
+ @html_attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination'
166
+ end
167
+ @html_attributes
168
+ end
169
+
170
+ protected
171
+
172
+ # Collects link items for visible page numbers.
173
+ def windowed_links
174
+ prev = nil
175
+
176
+ visible_page_numbers.inject [] do |links, n|
177
+ # detect gaps:
178
+ links << gap_marker if prev and n > prev + 1
179
+ links << page_link_or_span(n, 'current')
180
+ prev = n
181
+ links
182
+ end
183
+ end
184
+
185
+ # Calculates visible page numbers using the <tt>:inner_window</tt> and
186
+ # <tt>:outer_window</tt> options.
187
+ def visible_page_numbers
188
+ inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
189
+ window_from = current_page - inner_window
190
+ window_to = current_page + inner_window
191
+
192
+ # adjust lower or upper limit if other is out of bounds
193
+ if window_to > total_pages
194
+ window_from -= window_to - total_pages
195
+ window_to = total_pages
196
+ end
197
+ if window_from < 1
198
+ window_to += 1 - window_from
199
+ window_from = 1
200
+ window_to = total_pages if window_to > total_pages
201
+ end
202
+
203
+ visible = (1..total_pages).to_a
204
+ left_gap = (2 + outer_window)...window_from
205
+ right_gap = (window_to + 1)...(total_pages - outer_window)
206
+ visible -= left_gap.to_a if left_gap.last - left_gap.first > 1
207
+ visible -= right_gap.to_a if right_gap.last - right_gap.first > 1
208
+
209
+ visible
210
+ end
211
+
212
+ def page_link_or_span(page, span_class, text = nil)
213
+ text ||= page.to_s
214
+
215
+ if page and page != current_page
216
+ classnames = span_class && span_class.index(' ') && span_class.split(' ', 2).last
217
+ page_link page, text, :rel => rel_value(page), :class => classnames
218
+ else
219
+ page_span page, text, :class => span_class
220
+ end
221
+ end
222
+
223
+ def page_link(page, text, attributes = {})
224
+ @template.link_to text, url_for(page), attributes
225
+ end
226
+
227
+ def page_span(page, text, attributes = {})
228
+ @template.content_tag :span, text, attributes
229
+ end
230
+
231
+ # Returns URL params for +page_link_or_span+, taking the current GET params
232
+ # and <tt>:params</tt> option into account.
233
+ def url_for(page)
234
+ page_one = page == 1
235
+ unless @url_string and !page_one
236
+ @url_params = {}
237
+ # page links should preserve GET parameters
238
+ stringified_merge @url_params, @template.params if @template.request.get?
239
+ stringified_merge @url_params, @options[:params] if @options[:params]
240
+
241
+ if complex = param_name.index(/[^\w-]/)
242
+ page_param = parse_query_parameters("#{param_name}=#{page}")
243
+
244
+ stringified_merge @url_params, page_param
245
+ else
246
+ @url_params[param_name] = page_one ? 1 : 2
247
+ end
248
+
249
+ url = @template.url_for(@url_params)
250
+ return url if page_one
251
+
252
+ if complex
253
+ @url_string = url.sub(%r!((?:\?|&amp;)#{CGI.escape param_name}=)#{page}!, "\\1\0")
254
+ return url
255
+ else
256
+ @url_string = url
257
+ @url_params[param_name] = 3
258
+ @template.url_for(@url_params).split(//).each_with_index do |char, i|
259
+ if char == '3' and url[i, 1] == '2'
260
+ @url_string[i] = "\0"
261
+ break
262
+ end
263
+ end
264
+ end
265
+ end
266
+ # finally!
267
+ @url_string.sub "\0", page.to_s
268
+ end
269
+
270
+ private
271
+
272
+ def rel_value(page)
273
+ case page
274
+ when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '')
275
+ when @collection.next_page; 'next'
276
+ when 1; 'start'
277
+ end
278
+ end
279
+
280
+ def current_page
281
+ @current_page ||= @collection.current_page
282
+ end
283
+
284
+ def total_pages
285
+ @total_pages ||= @collection.total_pages
286
+ end
287
+
288
+ def param_name
289
+ @param_name ||= @options[:param_name].to_s
290
+ end
291
+
292
+ # Recursively merge into target hash by using stringified keys from the other one
293
+ def stringified_merge(target, other)
294
+ other.each do |key, value|
295
+ key = key.to_s # this line is what it's all about!
296
+ existing = target[key]
297
+
298
+ if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
299
+ stringified_merge(existing || (target[key] = {}), value)
300
+ else
301
+ target[key] = value
302
+ end
303
+ end
304
+ end
305
+
306
+ def parse_query_parameters(params)
307
+ if defined? Rack::Utils
308
+ # For Rails > 2.3
309
+ Rack::Utils.parse_nested_query(params)
310
+ elsif defined?(ActionController::AbstractRequest)
311
+ ActionController::AbstractRequest.parse_query_parameters(params)
312
+ elsif defined?(ActionController::UrlEncodedPairParser)
313
+ # For Rails > 2.2
314
+ ActionController::UrlEncodedPairParser.parse_query_parameters(params)
315
+ elsif defined?(CGIMethods)
316
+ CGIMethods.parse_query_parameters(params)
317
+ else
318
+ raise "unsupported ActionPack version"
319
+ end
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'active_record/base_extensions'
3
+ require 'active_record/scope_extensions'
4
+ require 'mocha'
5
+
6
+
7
+ class ActiveRecordExtentionsTest < Test::Unit::TestCase
8
+ context "active record extentions" do
9
+ setup do
10
+ User.delete_all
11
+ Article.delete_all
12
+
13
+ @user1 = User.create :first_name => 'Bob', :last_name => 'Builder'
14
+ @user2 = User.create :first_name => 'Bob', :last_name => 'Builder2'
15
+ @user3 = User.create :first_name => 'Tom', :last_name => 'Builder'
16
+
17
+ 5.times do |i|
18
+ Article.create(:title => "title_#{i}", :user => @user1)
19
+ end
20
+
21
+ 3.times do |i|
22
+ Article.create!(:title => "2title_#{i}", :user => @user2)
23
+ end
24
+ end
25
+
26
+ should "difference between Article.find_every_with_scope and Article.find_every_without_scope" do
27
+ users1 = User.all
28
+ users2 = User.all(:eager => true)
29
+ assert_equal users1, users2
30
+ assert_not_equal users1.class, users2.class
31
+
32
+ users1 = User.all(:conditions => {:first_name => 'Bob'})
33
+ users2 = User.all(:eager => true, :conditions => {:first_name => 'Bob'})
34
+ assert_equal users1, users2
35
+ assert_not_equal users1.class, users2.class
36
+ end
37
+
38
+ should "return ActiveRecord::NamedScope::Scope" do
39
+ assert_equal ActiveRecord::NamedScope::Scope, User.all.class
40
+ assert_equal ActiveRecord::NamedScope::Scope, User.all(:conditions => {:first_name => 'Bob'}).class
41
+ end
42
+
43
+ should "return an Array when called with :eager => true" do
44
+ assert_equal Array, Article.all(:eager => true).class
45
+ assert_equal Array, Article.all(:eager => true, :conditions => {:title => "title_1"}).class
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,71 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require File.dirname(__FILE__) + '/../lib/pagination/collection'
3
+ require File.dirname(__FILE__) + '/../lib/pagination/enumerable'
4
+
5
+ class CollectionTest < Test::Unit::TestCase
6
+ context "collection test" do
7
+ setup do
8
+ @collection = ('a'..'e').to_a
9
+ end
10
+
11
+ should "return elements from specific range" do
12
+ # see: enumerable.rb
13
+ # limit = per_page
14
+ # offset = (current_page - 1) * limit
15
+ [
16
+ { :offset => 0, :limit => 3, :expected => %w( a b c ) },
17
+ { :offset => 3, :limit => 3, :expected => %w( d e ) },
18
+ { :offset => 0, :limit => 5, :expected => %w( a b c d e ) },
19
+ { :offset => 6, :limit => 3, :expected => [] },
20
+ ].
21
+ each do |conditions|
22
+ expected = conditions.delete :expected
23
+ assert_equal expected, @collection.paginate(conditions)
24
+ end
25
+ end
26
+
27
+ context "init pagination collection" do
28
+ setup do
29
+ @collection = [1,2,3,4,5]
30
+ @paginated_collection = paginate_collection(@collection)
31
+ end
32
+
33
+ should "return total pages" do
34
+ assert_equal 3, @paginated_collection.total_pages
35
+ end
36
+
37
+ should "return previous page" do
38
+ assert_equal 1, @paginated_collection.previous_page
39
+ end
40
+
41
+ should "return next page" do
42
+ assert_equal 3, @paginated_collection.next_page
43
+ end
44
+
45
+ should "array be shorter after paginate" do
46
+ pag_array = paginate_collection(@collection, 1, 4)
47
+ assert_not_equal @collection, pag_array.count
48
+ assert_equal 4, pag_array.count
49
+ end
50
+
51
+ should "rise an error InvalidPage" do
52
+ assert_raise(Pagination::InvalidPage) { paginate_collection(@collection, -2, 2) }
53
+ assert_raise(Pagination::InvalidPage) { paginate_collection(@collection, 1000, 2) }
54
+ end
55
+
56
+ should "rise an error ArgumentError" do
57
+ assert_raise(ArgumentError) { paginate_collection(@collection, 2, -2) }
58
+ end
59
+ end
60
+ end
61
+
62
+ private
63
+ def paginate_collection(collection, current_page=2, per_page=2, total_entries=nil)
64
+ Pagination::Collection.new(
65
+ :collection => collection,
66
+ :current_page => current_page,
67
+ :per_page => per_page,
68
+ :total_entries => total_entries
69
+ )
70
+ end
71
+ end
@@ -0,0 +1,180 @@
1
+ require 'action_controller'
2
+ require 'action_controller/test_process'
3
+
4
+ require 'pagination'
5
+
6
+ ActionController::Routing::Routes.draw do |map|
7
+ map.connect 'dummy/page/:page', :controller => 'dummy'
8
+ map.connect 'dummy/dots/page.:page', :controller => 'dummy', :action => 'dots'
9
+ map.connect 'ibocorp/:page', :controller => 'ibocorp',
10
+ :requirements => { :page => /\d+/ },
11
+ :defaults => { :page => 1 }
12
+
13
+ map.connect ':controller/:action/:id'
14
+ end
15
+
16
+ ActionController::Base.perform_caching = false
17
+
18
+ class Pagination::ViewTestCase < Test::Unit::TestCase
19
+ if defined?(ActionController::TestCase::Assertions)
20
+ include ActionController::TestCase::Assertions
21
+ end
22
+ if defined?(ActiveSupport::Testing::Deprecation)
23
+ include ActiveSupport::Testing::Deprecation
24
+ end
25
+
26
+ def setup
27
+ super
28
+ @controller = DummyController.new
29
+ @request = @controller.request
30
+ @html_result = nil
31
+ @template = '<%= paginate collection, options do |tmp|; tmp.each do |t|; end; end %>'
32
+
33
+ @view = ActionView::Base.new
34
+ @view.assigns['controller'] = @controller
35
+ @view.assigns['_request'] = @request
36
+ @view.assigns['_params'] = @request.params
37
+ end
38
+
39
+ def default_test; end
40
+
41
+ protected
42
+
43
+ def paginate(collection = {}, options = {}, &block)
44
+
45
+ if collection.instance_of? Hash
46
+ @request.params :page => collection[:page] || 1
47
+ collection = [1,2,3,4,5,6,7,8,9,10,11]
48
+ end
49
+ options = {:per_page => 4, :controls => :top}.merge(options)
50
+
51
+ locals = { :collection => collection, :options => options }
52
+
53
+ unless @view.respond_to? :render_template
54
+ # Rails 2.2
55
+ @html_result = ActionView::InlineTemplate.new(@template).render(@view, locals)
56
+ else
57
+ if defined? ActionView::InlineTemplate
58
+ # Rails 2.1
59
+ args = [ ActionView::InlineTemplate.new(@view, @template, locals) ]
60
+ else
61
+ # older Rails versions
62
+ args = [nil, @template, nil, locals]
63
+ end
64
+
65
+ @html_result = @view.render_template(*args)
66
+ end
67
+
68
+ @html_document = HTML::Document.new(@html_result, true, false)
69
+
70
+ if block_given?
71
+ classname = options[:class] || Pagination::ViewHelpers.pagination_options[:class]
72
+ assert_select("div.#{classname}",1 , 'no main DIV', &block)
73
+ end
74
+
75
+ end
76
+
77
+ def response_from_page_or_rjs
78
+ @html_document.root
79
+ end
80
+
81
+ def validate_page_numbers expected, links, param_name = :page
82
+ param_pattern = /\W#{CGI.escape(param_name.to_s)}=([^&]*)/
83
+
84
+ assert_equal(expected, links.map { |e|
85
+ e['href'] =~ param_pattern
86
+ $1 ? $1.to_i : $1
87
+ })
88
+ end
89
+
90
+ def assert_links_match pattern, links = nil, numbers = nil
91
+ links ||= assert_select 'div.pagination a[href]' do |elements|
92
+ elements
93
+ end
94
+
95
+ pages = [] if numbers
96
+
97
+ links.each do |el|
98
+ assert_match pattern, el['href']
99
+ if numbers
100
+ el['href'] =~ pattern
101
+ pages << ($1.nil?? nil : $1.to_i)
102
+ end
103
+ end
104
+
105
+ assert_equal numbers, pages, "page numbers don't match" if numbers
106
+ end
107
+
108
+ def assert_no_links_match pattern
109
+ assert_select 'div.pagination a[href]' do |elements|
110
+ elements.each do |el|
111
+ assert_no_match pattern, el['href']
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ class DummyRequest
118
+ attr_accessor :symbolized_path_parameters
119
+
120
+ def initialize
121
+ @get = true
122
+ @params = {}
123
+ @symbolized_path_parameters = { :controller => 'foo', :action => 'bar'}
124
+ end
125
+
126
+ def get?
127
+ @get
128
+ end
129
+
130
+ def post
131
+ @get = false
132
+ end
133
+
134
+ def relative_url_root
135
+ ''
136
+ end
137
+
138
+ def params(more = nil)
139
+ @params.update(more) if more
140
+ @params
141
+ end
142
+ end
143
+
144
+ class DummyController
145
+ attr_reader :request
146
+ attr_accessor :controller_name
147
+
148
+ def initialize
149
+ @request = DummyRequest.new
150
+ @url = ActionController::UrlRewriter.new(@request, @request.params)
151
+ end
152
+
153
+ def params
154
+ @request.params
155
+ end
156
+
157
+ def url_for(params)
158
+ @url.rewrite(params)
159
+ end
160
+ end
161
+
162
+ module HTML
163
+ Node.class_eval do
164
+ def inner_text
165
+ children.map(&:inner_text).join('')
166
+ end
167
+ end
168
+
169
+ Text.class_eval do
170
+ def inner_text
171
+ self.to_s
172
+ end
173
+ end
174
+
175
+ Tag.class_eval do
176
+ def inner_text
177
+ childless?? '' : super
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'activerecord'
3
+ require 'shoulda'
4
+ require 'logger'
5
+
6
+
7
+ ActiveRecord::Base.configurations = {'sqlite3' => {:adapter => 'sqlite3', :database => ':memory:'}}
8
+ ActiveRecord::Base.establish_connection('sqlite3')
9
+
10
+ ActiveRecord::Base.logger = Logger.new(STDERR)
11
+ ActiveRecord::Base.logger.level = Logger::WARN
12
+
13
+ ActiveRecord::Schema.define(:version => 0) do
14
+ create_table :users do |t|
15
+ t.string :first_name, :default => ''
16
+ t.string :last_name, :default => ''
17
+ end
18
+
19
+ create_table :articles do |t|
20
+ t.string :title, :default => ''
21
+ t.integer :user_id
22
+ end
23
+ end
24
+
25
+ class User < ActiveRecord::Base
26
+ has_many :articles
27
+ named_scope :bob, :conditions => {:first_name => 'Bob'}
28
+
29
+ def find_all
30
+ User.find_all
31
+ end
32
+ end
33
+
34
+ class Article < ActiveRecord::Base
35
+ belongs_to :user
36
+ end
@@ -0,0 +1,224 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require File.dirname(__FILE__) + '/lib/view_test_process'
3
+
4
+ class ViewHelpersTest < Pagination::ViewTestCase
5
+ context "paginate method" do
6
+
7
+ should "return full output" do
8
+ paginate({:page => 1})
9
+ expected = <<-HTML
10
+ <div class="pagination"><span class="disabled prev_page">&laquo; Previous</span>
11
+ <span class="current">1</span>
12
+ <a href="/foo/bar?page=2" rel="next">2</a>
13
+ <a href="/foo/bar?page=3">3</a>
14
+ <a href="/foo/bar?page=2" class="next_page" rel="next">Next &raquo;</a></div>
15
+ HTML
16
+ expected.strip!.gsub!(/\s{2,}/, ' ')
17
+
18
+ assert_dom_equal expected, @html_result
19
+ end
20
+
21
+ should "show links to pages" do
22
+ paginate do |pagination|
23
+ assert_select 'a[href]', 3 do |elements|
24
+ validate_page_numbers [2,3,2], elements
25
+ assert_select elements.last, ':last-child', "Next &raquo;"
26
+ end
27
+ assert_select 'span', 2
28
+ assert_select 'span.disabled:first-child', '&laquo; Previous'
29
+ assert_select 'span.current', '1'
30
+ assert_equal '&laquo; Previous 1 2 3 Next &raquo;', pagination.first.inner_text
31
+ end
32
+ end
33
+
34
+ should "paginate with options" do
35
+ paginate({:page => 2},
36
+ :class => 'paginate', :previous_label => 'Prev', :next_label => 'Next') do
37
+ assert_select 'a[href]', 4 do |elements|
38
+ validate_page_numbers [1,1,3,3], elements
39
+ # test rel attribute values:
40
+ assert_select elements[1], 'a', '1' do |link|
41
+ assert_equal 'prev start', link.first['rel']
42
+ end
43
+ assert_select elements.first, 'a', "Prev" do |link|
44
+ assert_equal 'prev start', link.first['rel']
45
+ end
46
+ assert_select elements.last, 'a', "Next" do |link|
47
+ assert_equal 'next', link.first['rel']
48
+ end
49
+ end
50
+ assert_select 'span.current', '2'
51
+ end
52
+ end
53
+
54
+ should "paginate using renderer class" do
55
+ paginate( {}, :per_page => 5, :renderer => AdditionalLinkAttributesRenderer) do
56
+ assert_select 'a[default=true]', 3
57
+ end
58
+ end
59
+
60
+ should "no pagination when page count is one" do
61
+ paginate({:page => 1}, {:per_page => 30})
62
+ assert_equal '', @html_result
63
+ end
64
+
65
+ should "paginate using renderer instance" do
66
+ renderer = Pagination::LinkRenderer.new
67
+ renderer.gap_marker = '<span class="my-gap">~~</span>'
68
+
69
+ paginate({}, {:per_page => 2, :inner_window => 0, :outer_window => 0, :renderer => renderer}) do
70
+ assert_select 'span.my-gap', '~~'
71
+ end
72
+
73
+ renderer = AdditionalLinkAttributesRenderer.new(:title => 'rendered')
74
+ paginate({}, :per_page => 5, :renderer => renderer) do
75
+ assert_select 'a[title=rendered]', 3
76
+ end
77
+ end
78
+
79
+ should "previous next links have classnames" do
80
+ paginate do |pagination|
81
+ assert_select 'span.disabled.prev_page:first-child'
82
+ assert_select 'a.next_page[href]:last-child'
83
+ end
84
+ end
85
+
86
+ should "paginate without container" do
87
+ paginate({}, :container => false)
88
+ assert_select 'div.pagination', 0, 'main DIV present when it shouldn\'t'
89
+ end
90
+
91
+ should "paginate without page links" do
92
+ paginate({:page => 2}, :page_links => false) do
93
+ assert_select 'a[href]', 2 do |elements|
94
+ validate_page_numbers [1,3], elements
95
+ end
96
+ end
97
+ end
98
+
99
+ should "paginate windows" do
100
+ paginate({:page => 6}, {:per_page => 1, :inner_window => 1}) do |pagination|
101
+ assert_select 'a[href]', 8 do |elements|
102
+ validate_page_numbers [5,1,2,5,7,10,11,7], elements
103
+ assert_select elements.first, 'a', '&laquo; Previous'
104
+ assert_select elements.last, 'a', 'Next &raquo;'
105
+ end
106
+ assert_select 'span.current', '6'
107
+ assert_equal '&laquo; Previous 1 2 &hellip; 5 6 7 &hellip; 10 11 Next &raquo;', pagination.first.inner_text
108
+ end
109
+ end
110
+
111
+ should "paginate eliminates small gaps" do
112
+ paginate({:page => 6}, {:per_page => 1, :inner_window => 2}) do
113
+ assert_select 'a[href]', 12 do |elements|
114
+ validate_page_numbers [5,1,2,3,4,5,7,8,9,10,11,7], elements
115
+ end
116
+ end
117
+ end
118
+
119
+ should "return container id" do
120
+ paginate do |div|
121
+ assert_nil div.first['id']
122
+ end
123
+
124
+ # magic ID
125
+ paginate({}, :id => true) do |div|
126
+ assert_equal 'fixnums_pagination', div.first['id']
127
+ end
128
+
129
+ # explicit ID
130
+ paginate({}, :id => 'custom_id') do |div|
131
+ assert_equal 'custom_id', div.first['id']
132
+ end
133
+ end
134
+
135
+ should "paginate with custom page param" do
136
+ paginate({}, {:param_name => :developers_page}) do
137
+ assert_select 'a[href]', 3 do |elements|
138
+ validate_page_numbers [2,3,2], elements, :developers_page
139
+ end
140
+ end
141
+ end
142
+
143
+ should "paginate preserves parameters on get" do
144
+ @request.params :foo => { :bar => 'baz' }
145
+ paginate
146
+ assert_links_match /foo%5Bbar%5D=baz/
147
+ end
148
+
149
+ should "paginate doesnt preserve parameters on post" do
150
+ @request.post
151
+ @request.params :foo => 'bar'
152
+ paginate
153
+ assert_no_links_match /foo=bar/
154
+ end
155
+
156
+ should "adding additional parameters" do
157
+ paginate({}, :params => { :foo => 'bar' })
158
+ assert_links_match /foo=bar/
159
+ end
160
+
161
+ should "adding anchor parameter" do
162
+ paginate({}, :params => { :anchor => 'anchor' })
163
+ assert_links_match /#anchor$/
164
+ end
165
+
166
+ should "removing arbitrary parameters" do
167
+ @request.params :foo => 'bar'
168
+ paginate({}, :params => { :foo => nil })
169
+ assert_no_links_match /foo=bar/
170
+ end
171
+
172
+ should "adding additional route parameters" do
173
+ paginate({}, :params => { :controller => 'baz', :action => 'list' })
174
+ assert_links_match %r{\Wbaz/list\W}
175
+ end
176
+
177
+ should "will paginate with atmark url" do
178
+ @request.symbolized_path_parameters[:action] = "@tag"
179
+ renderer = Pagination::LinkRenderer.new
180
+
181
+ paginate({ :page => 1 }, :renderer=>renderer)
182
+ assert_links_match %r[/foo/@tag\?page=\d]
183
+ end
184
+
185
+ should "custom routing page param" do
186
+ @request.symbolized_path_parameters.update :controller => 'dummy', :action => nil
187
+ paginate({}, :per_page => 2) do
188
+ assert_select 'a[href]', 6 do |links|
189
+ assert_links_match %r{/page/(\d+)$}, links, [2, 3, 4, 5, 6, 2]
190
+ end
191
+ end
192
+ end
193
+
194
+ should "custom routing page param with dot separator" do
195
+ @request.symbolized_path_parameters.update :controller => 'dummy', :action => 'dots'
196
+ paginate({}, :per_page => 2) do
197
+ assert_select 'a[href]', 6 do |links|
198
+ assert_links_match %r{/page\.(\d+)$}, links, [2, 3, 4, 5, 6, 2]
199
+ end
200
+ end
201
+ end
202
+
203
+ should "custom routing with first page hidden" do
204
+ @request.symbolized_path_parameters.update :controller => 'ibocorp', :action => nil
205
+ paginate({:page => 2}, :per_page => 2) do
206
+ assert_select 'a[href]', 7 do |links|
207
+ assert_links_match %r{/ibocorp(?:/(\d+))?$}, links, [nil, nil, 3, 4, 5, 6, 3]
208
+ end
209
+ end
210
+ end
211
+
212
+ end
213
+ end
214
+
215
+ class AdditionalLinkAttributesRenderer < Pagination::LinkRenderer
216
+ def initialize(link_attributes = nil)
217
+ super()
218
+ @additional_link_attributes = link_attributes || { :default => 'true' }
219
+ end
220
+
221
+ def page_link(page, text, attributes = {})
222
+ @template.link_to text, url_for(page), attributes.merge(@additional_link_attributes)
223
+ end
224
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: objectreload-pagination
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mateusz Drozdzynski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-13 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: gems@objectreload.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - CHANGELOG.rdoc
27
+ - LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - init.rb
31
+ - lib/active_record/base_extensions.rb
32
+ - lib/active_record/scope_extensions.rb
33
+ - lib/pagination.rb
34
+ - lib/pagination/collection.rb
35
+ - lib/pagination/enumerable.rb
36
+ - lib/pagination/named_scope.rb
37
+ - lib/pagination/version.rb
38
+ - lib/pagination/view_helpers.rb
39
+ - test/active_record_extentions_test.rb
40
+ - test/collection_test.rb
41
+ - test/lib/view_test_process.rb
42
+ - test/test_helper.rb
43
+ - test/view_helpers_test.rb
44
+ has_rdoc: true
45
+ homepage: http://github.com/objectreload/pagination
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --charset=UTF-8
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ requirements: []
66
+
67
+ rubyforge_project:
68
+ rubygems_version: 1.3.5
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Simple pagination - without changes in controllers
72
+ test_files:
73
+ - test/test_helper.rb
74
+ - test/lib/view_test_process.rb
75
+ - test/active_record_extentions_test.rb
76
+ - test/collection_test.rb
77
+ - test/view_helpers_test.rb