objectreload-pagination 0.1.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.
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