hobo_will_paginate 2.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.
- checksums.yaml +15 -0
- data/LICENSE +18 -0
- data/README.md +61 -0
- data/Rakefile +25 -0
- data/lib/will_paginate.rb +25 -0
- data/lib/will_paginate/active_record.rb +216 -0
- data/lib/will_paginate/array.rb +57 -0
- data/lib/will_paginate/collection.rb +149 -0
- data/lib/will_paginate/core_ext.rb +30 -0
- data/lib/will_paginate/data_mapper.rb +95 -0
- data/lib/will_paginate/deprecation.rb +55 -0
- data/lib/will_paginate/i18n.rb +22 -0
- data/lib/will_paginate/locale/en.yml +33 -0
- data/lib/will_paginate/page_number.rb +57 -0
- data/lib/will_paginate/per_page.rb +27 -0
- data/lib/will_paginate/railtie.rb +68 -0
- data/lib/will_paginate/sequel.rb +39 -0
- data/lib/will_paginate/version.rb +9 -0
- data/lib/will_paginate/view_helpers.rb +161 -0
- data/lib/will_paginate/view_helpers/action_view.rb +148 -0
- data/lib/will_paginate/view_helpers/link_renderer.rb +132 -0
- data/lib/will_paginate/view_helpers/link_renderer_base.rb +77 -0
- data/lib/will_paginate/view_helpers/merb.rb +26 -0
- data/lib/will_paginate/view_helpers/sinatra.rb +41 -0
- data/spec/ci.rb +29 -0
- data/spec/collection_spec.rb +139 -0
- data/spec/console +12 -0
- data/spec/console_fixtures.rb +28 -0
- data/spec/database.yml +22 -0
- data/spec/finders/active_record_spec.rb +543 -0
- data/spec/finders/activerecord_test_connector.rb +113 -0
- data/spec/finders/data_mapper_spec.rb +103 -0
- data/spec/finders/data_mapper_test_connector.rb +54 -0
- data/spec/finders/sequel_spec.rb +67 -0
- data/spec/finders/sequel_test_connector.rb +9 -0
- data/spec/fixtures/admin.rb +3 -0
- data/spec/fixtures/developer.rb +13 -0
- data/spec/fixtures/developers_projects.yml +13 -0
- data/spec/fixtures/project.rb +15 -0
- data/spec/fixtures/projects.yml +6 -0
- data/spec/fixtures/replies.yml +29 -0
- data/spec/fixtures/reply.rb +9 -0
- data/spec/fixtures/schema.rb +38 -0
- data/spec/fixtures/topic.rb +7 -0
- data/spec/fixtures/topics.yml +30 -0
- data/spec/fixtures/user.rb +2 -0
- data/spec/fixtures/users.yml +35 -0
- data/spec/page_number_spec.rb +65 -0
- data/spec/per_page_spec.rb +41 -0
- data/spec/spec_helper.rb +71 -0
- data/spec/view_helpers/action_view_spec.rb +423 -0
- data/spec/view_helpers/base_spec.rb +130 -0
- data/spec/view_helpers/link_renderer_base_spec.rb +87 -0
- data/spec/view_helpers/view_example_group.rb +114 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZjdmYzE1NDE1YzU4NzFkOTM2MzcyZjJkNzM5ODIyYzNiYmNmZTcxYQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
Mzc3MTg1ZDhkNTE1YmM4ZmQ3YzIwMGMzYTM2MTRmNTBkMmQxZmY2Zg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YmJmMzQ1ZWZkMGZkY2Y5ZjMwODg3YjA3ODY1ZDE1YTQzMmVhMzFiZDljMGJi
|
10
|
+
ODA5YTBhNzdjZWEyYWZiYWU1MzcwZTBiNWIxM2E3Y2QwZWM2NTUxNTRlOWZl
|
11
|
+
ODBhMmIwOWViMjcyY2NmNTNjZDUwNTBmZDljYTk1NzQ2NWM5MDU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
N2ZmNWM4MDBiM2IxOWRlNGMxOTU5MTQxMjIyMThlOTdiYjhiNTExZTNhZWRl
|
14
|
+
NDExY2Y5YWMxOGQ5OWEwZjQyYmVmNGIwZWE5MjhhMmRiOTJlMTVlN2QyOWJl
|
15
|
+
NDA2ZDMxODhlOGYzYzkwYzg0YzBlZmJkMTU0YzQ0YzMyMzFlNTI=
|
data/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2009 Mislav Marohnić
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# will_paginate
|
2
|
+
|
3
|
+
will_paginate is a pagination library that integrates with Ruby on Rails, Sinatra, Merb, DataMapper and Sequel.
|
4
|
+
|
5
|
+
Installation:
|
6
|
+
|
7
|
+
``` ruby
|
8
|
+
## Gemfile for Rails 3, Sinatra, and Merb
|
9
|
+
gem 'will_paginate', '~> 3.0'
|
10
|
+
```
|
11
|
+
|
12
|
+
See [installation instructions][install] on the wiki for more info.
|
13
|
+
|
14
|
+
|
15
|
+
## Basic will_paginate use
|
16
|
+
|
17
|
+
``` ruby
|
18
|
+
## perform a paginated query:
|
19
|
+
@posts = Post.paginate(:page => params[:page])
|
20
|
+
|
21
|
+
# or, use an explicit "per page" limit:
|
22
|
+
Post.paginate(:page => params[:page], :per_page => 30)
|
23
|
+
|
24
|
+
## render page links in the view:
|
25
|
+
<%= will_paginate @posts %>
|
26
|
+
```
|
27
|
+
|
28
|
+
And that's it! You're done. You just need to add some CSS styles to [make those pagination links prettier][css].
|
29
|
+
|
30
|
+
You can customize the default "per_page" value:
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
# for the Post model
|
34
|
+
class Post
|
35
|
+
self.per_page = 10
|
36
|
+
end
|
37
|
+
|
38
|
+
# set per_page globally
|
39
|
+
WillPaginate.per_page = 10
|
40
|
+
```
|
41
|
+
|
42
|
+
New in Active Record 3:
|
43
|
+
|
44
|
+
``` ruby
|
45
|
+
# paginate in Active Record now returns a Relation
|
46
|
+
Post.where(:published => true).paginate(:page => params[:page]).order('id DESC')
|
47
|
+
|
48
|
+
# the new, shorter page() method
|
49
|
+
Post.page(params[:page]).order('created_at DESC')
|
50
|
+
```
|
51
|
+
|
52
|
+
See [the wiki][wiki] for more documentation. [Ask on the group][group] if you have usage questions. [Report bugs][issues] on GitHub.
|
53
|
+
|
54
|
+
Happy paginating.
|
55
|
+
|
56
|
+
|
57
|
+
[wiki]: https://github.com/mislav/will_paginate/wiki
|
58
|
+
[install]: https://github.com/mislav/will_paginate/wiki/Installation "will_paginate installation"
|
59
|
+
[group]: http://groups.google.com/group/will_paginate "will_paginate discussion and support group"
|
60
|
+
[issues]: https://github.com/mislav/will_paginate/issues
|
61
|
+
[css]: http://mislav.uniqpath.com/will_paginate/
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
begin
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
rescue LoadError
|
4
|
+
# no spec tasks
|
5
|
+
else
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc 'Run ALL OF the specs'
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
# t.ruby_opts = '-w'
|
11
|
+
t.pattern = 'spec/finders/active_record_spec.rb' if ENV['DB'] and ENV['DB'] != 'sqlite3'
|
12
|
+
end
|
13
|
+
|
14
|
+
namespace :spec do
|
15
|
+
desc "Run Rails specs"
|
16
|
+
RSpec::Core::RakeTask.new(:rails) do |t|
|
17
|
+
t.pattern = %w'spec/finders/active_record_spec.rb spec/view_helpers/action_view_spec.rb'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Run specs against both Rails 3.1 and Rails 3.0'
|
23
|
+
task :rails3 do |variable|
|
24
|
+
system 'bundle exec rake spec && BUNDLE_GEMFILE=Gemfile.rails3.0 bundle exec rake spec:rails'
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# You will paginate!
|
2
|
+
module WillPaginate
|
3
|
+
end
|
4
|
+
|
5
|
+
if defined?(Rails::Railtie)
|
6
|
+
require 'will_paginate/railtie'
|
7
|
+
elsif defined?(Rails::Initializer)
|
8
|
+
raise "will_paginate 3.0 is not compatible with Rails 2.3 or older"
|
9
|
+
end
|
10
|
+
|
11
|
+
if defined?(Merb::AbstractController)
|
12
|
+
require 'will_paginate/view_helpers/merb'
|
13
|
+
|
14
|
+
Merb::BootLoader.before_app_loads do
|
15
|
+
adapters = { :datamapper => 'data_mapper', :activerecord => 'active_record', :sequel => 'sequel' }
|
16
|
+
# auto-load the right ORM adapter
|
17
|
+
if adapter = adapters[Merb.orm]
|
18
|
+
require "will_paginate/#{adapter}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if defined?(Sinatra) and Sinatra.respond_to? :register
|
24
|
+
require 'will_paginate/view_helpers/sinatra'
|
25
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'will_paginate/per_page'
|
2
|
+
require 'will_paginate/page_number'
|
3
|
+
require 'will_paginate/collection'
|
4
|
+
require 'active_record'
|
5
|
+
|
6
|
+
module WillPaginate
|
7
|
+
# = Paginating finders for ActiveRecord models
|
8
|
+
#
|
9
|
+
# WillPaginate adds +paginate+, +per_page+ and other methods to
|
10
|
+
# ActiveRecord::Base class methods and associations.
|
11
|
+
#
|
12
|
+
# In short, paginating finders are equivalent to ActiveRecord finders; the
|
13
|
+
# only difference is that we start with "paginate" instead of "find" and
|
14
|
+
# that <tt>:page</tt> is required parameter:
|
15
|
+
#
|
16
|
+
# @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC'
|
17
|
+
#
|
18
|
+
module ActiveRecord
|
19
|
+
# makes a Relation look like WillPaginate::Collection
|
20
|
+
module RelationMethods
|
21
|
+
include WillPaginate::CollectionMethods
|
22
|
+
|
23
|
+
attr_accessor :current_page
|
24
|
+
attr_writer :total_entries, :wp_count_options
|
25
|
+
|
26
|
+
def per_page(value = nil)
|
27
|
+
if value.nil? then limit_value
|
28
|
+
else limit(value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# TODO: solve with less relation clones and code dups
|
33
|
+
def limit(num)
|
34
|
+
rel = super
|
35
|
+
if rel.current_page
|
36
|
+
rel.offset rel.current_page.to_offset(rel.limit_value).to_i
|
37
|
+
else
|
38
|
+
rel
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def offset(value = nil)
|
43
|
+
if value.nil? then offset_value
|
44
|
+
else super(value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def total_entries
|
49
|
+
@total_entries ||= begin
|
50
|
+
if loaded? and size < limit_value and (current_page == 1 or size > 0)
|
51
|
+
offset_value + size
|
52
|
+
else
|
53
|
+
@total_entries_queried = true
|
54
|
+
result = count
|
55
|
+
result = result.size if result.respond_to?(:size) and !result.is_a?(Integer)
|
56
|
+
result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def count
|
62
|
+
if limit_value
|
63
|
+
excluded = [:order, :limit, :offset]
|
64
|
+
excluded << :includes unless eager_loading?
|
65
|
+
rel = self.except(*excluded)
|
66
|
+
# TODO: hack. decide whether to keep
|
67
|
+
rel = rel.apply_finder_options(@wp_count_options) if defined? @wp_count_options
|
68
|
+
rel.count
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# workaround for Active Record 3.0
|
75
|
+
def size
|
76
|
+
if !loaded? and limit_value and group_values.empty?
|
77
|
+
[super, limit_value].min
|
78
|
+
else
|
79
|
+
super
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# overloaded to be pagination-aware
|
84
|
+
def empty?
|
85
|
+
if !loaded? and offset_value
|
86
|
+
result = count
|
87
|
+
result = result.size if result.respond_to?(:size) and !result.is_a?(Integer)
|
88
|
+
result <= offset_value
|
89
|
+
else
|
90
|
+
super
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def clone
|
95
|
+
copy_will_paginate_data super
|
96
|
+
end
|
97
|
+
|
98
|
+
# workaround for Active Record 3.0
|
99
|
+
def scoped(options = nil)
|
100
|
+
copy_will_paginate_data super
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_a
|
104
|
+
if current_page.nil? then super # workaround for Active Record 3.0
|
105
|
+
else
|
106
|
+
::WillPaginate::Collection.create(current_page, limit_value) do |col|
|
107
|
+
col.replace super
|
108
|
+
col.total_entries ||= total_entries
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def copy_will_paginate_data(other)
|
116
|
+
other.current_page = current_page unless other.current_page
|
117
|
+
other.total_entries = nil if defined? @total_entries_queried
|
118
|
+
other.wp_count_options = @wp_count_options if defined? @wp_count_options
|
119
|
+
other
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
module Pagination
|
124
|
+
def paginate(options)
|
125
|
+
options = options.dup
|
126
|
+
pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" }
|
127
|
+
per_page = options.delete(:per_page) || self.per_page
|
128
|
+
total = options.delete(:total_entries)
|
129
|
+
|
130
|
+
count_options = options.delete(:count)
|
131
|
+
options.delete(:page)
|
132
|
+
|
133
|
+
rel = limit(per_page.to_i).page(pagenum)
|
134
|
+
rel.wp_count_options = count_options if count_options
|
135
|
+
rel.total_entries = total.to_i unless total.blank?
|
136
|
+
rel
|
137
|
+
end
|
138
|
+
|
139
|
+
def page(num)
|
140
|
+
rel = self.extending(RelationMethods)
|
141
|
+
pagenum = ::WillPaginate::PageNumber(num.nil? ? 1 : num)
|
142
|
+
per_page = rel.limit_value || self.per_page
|
143
|
+
rel = rel.offset(pagenum.to_offset(per_page).to_i)
|
144
|
+
rel = rel.limit(per_page) unless rel.limit_value
|
145
|
+
rel.current_page = pagenum
|
146
|
+
rel
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
module BaseMethods
|
151
|
+
# Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
|
152
|
+
# based on the params otherwise used by paginating finds: +page+ and
|
153
|
+
# +per_page+.
|
154
|
+
#
|
155
|
+
# Example:
|
156
|
+
#
|
157
|
+
# @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000],
|
158
|
+
# :page => params[:page], :per_page => 3
|
159
|
+
#
|
160
|
+
# A query for counting rows will automatically be generated if you don't
|
161
|
+
# supply <tt>:total_entries</tt>. If you experience problems with this
|
162
|
+
# generated SQL, you might want to perform the count manually in your
|
163
|
+
# application.
|
164
|
+
#
|
165
|
+
def paginate_by_sql(sql, options)
|
166
|
+
pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" } || 1
|
167
|
+
per_page = options[:per_page] || self.per_page
|
168
|
+
total = options[:total_entries]
|
169
|
+
|
170
|
+
WillPaginate::Collection.create(pagenum, per_page, total) do |pager|
|
171
|
+
query = sanitize_sql(sql.dup)
|
172
|
+
original_query = query.dup
|
173
|
+
oracle = self.connection.adapter_name =~ /^(oracle|oci$)/i
|
174
|
+
|
175
|
+
# add limit, offset
|
176
|
+
if oracle
|
177
|
+
query = <<-SQL
|
178
|
+
SELECT * FROM (
|
179
|
+
SELECT rownum rnum, a.* FROM (#{query}) a
|
180
|
+
WHERE rownum <= #{pager.offset + pager.per_page}
|
181
|
+
) WHERE rnum >= #{pager.offset}
|
182
|
+
SQL
|
183
|
+
else
|
184
|
+
query << " LIMIT #{pager.per_page} OFFSET #{pager.offset}"
|
185
|
+
end
|
186
|
+
|
187
|
+
# perfom the find
|
188
|
+
pager.replace find_by_sql(query)
|
189
|
+
|
190
|
+
unless pager.total_entries
|
191
|
+
count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s.]+$/mi, ''
|
192
|
+
count_query = "SELECT COUNT(*) FROM (#{count_query})"
|
193
|
+
count_query << ' AS count_table' unless oracle
|
194
|
+
# perform the count query
|
195
|
+
pager.total_entries = count_by_sql(count_query)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# mix everything into Active Record
|
202
|
+
::ActiveRecord::Base.extend PerPage
|
203
|
+
::ActiveRecord::Base.extend Pagination
|
204
|
+
::ActiveRecord::Base.extend BaseMethods
|
205
|
+
|
206
|
+
klasses = [::ActiveRecord::Relation]
|
207
|
+
if defined? ::ActiveRecord::Associations::CollectionProxy
|
208
|
+
klasses << ::ActiveRecord::Associations::CollectionProxy
|
209
|
+
else
|
210
|
+
klasses << ::ActiveRecord::Associations::AssociationCollection
|
211
|
+
end
|
212
|
+
|
213
|
+
# support pagination on associations and scopes
|
214
|
+
klasses.each { |klass| klass.send(:include, Pagination) }
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'will_paginate/collection'
|
2
|
+
|
3
|
+
class Array
|
4
|
+
# Paginates a static array (extracting a subset of it). The result is a
|
5
|
+
# WillPaginate::Collection instance, which is an array with a few more
|
6
|
+
# properties about its paginated state.
|
7
|
+
#
|
8
|
+
# Parameters:
|
9
|
+
# * <tt>:page</tt> - current page, defaults to 1
|
10
|
+
# * <tt>:per_page</tt> - limit of items per page, defaults to 30
|
11
|
+
# * <tt>:total_entries</tt> - total number of items in the array, defaults to
|
12
|
+
# <tt>array.length</tt> (obviously)
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
# arr = ['a', 'b', 'c', 'd', 'e']
|
16
|
+
# paged = arr.paginate(:per_page => 2) #-> ['a', 'b']
|
17
|
+
# paged.total_entries #-> 5
|
18
|
+
# arr.paginate(:page => 2, :per_page => 2) #-> ['c', 'd']
|
19
|
+
# arr.paginate(:page => 3, :per_page => 2) #-> ['e']
|
20
|
+
#
|
21
|
+
# This method was originally {suggested by Desi
|
22
|
+
# McAdam}[http://www.desimcadam.com/archives/8] and later proved to be the
|
23
|
+
# most useful method of will_paginate library.
|
24
|
+
def paginate(options = {})
|
25
|
+
page = options[:page] || 1
|
26
|
+
per_page = options[:per_page] || WillPaginate.per_page
|
27
|
+
total = options[:total_entries] || self.length
|
28
|
+
|
29
|
+
WillPaginate::Collection.create(page, per_page, total) do |pager|
|
30
|
+
pager.replace self[pager.offset, pager.per_page].to_a
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_accessor :member_class, :origin, :origin_attribute
|
35
|
+
|
36
|
+
# Hobo Extension
|
37
|
+
def to_url_path
|
38
|
+
base_path = origin.try.to_url_path
|
39
|
+
"#{base_path}/#{origin_attribute}" unless base_path.blank?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Hobo Extension
|
43
|
+
def typed_id
|
44
|
+
origin and origin_id = origin.try.typed_id and "#{origin_id}:#{origin_attribute}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Hobo Extension
|
48
|
+
def paginate_with_hobo_metadata(*args, &block)
|
49
|
+
collection = paginate_without_hobo_metadata(*args, &block)
|
50
|
+
collection.member_class = member_class
|
51
|
+
collection.origin = try.proxy_owner
|
52
|
+
collection.origin_attribute = try.proxy_association._?.reflection._?.name
|
53
|
+
collection
|
54
|
+
end
|
55
|
+
alias_method_chain :paginate, :hobo_metadata
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'will_paginate/per_page'
|
2
|
+
require 'will_paginate/page_number'
|
3
|
+
|
4
|
+
module WillPaginate
|
5
|
+
# Any will_paginate-compatible collection should have these methods:
|
6
|
+
#
|
7
|
+
# current_page, per_page, offset, total_entries, total_pages
|
8
|
+
#
|
9
|
+
# It can also define some of these optional methods:
|
10
|
+
#
|
11
|
+
# out_of_bounds?, previous_page, next_page
|
12
|
+
#
|
13
|
+
# This module provides few of these methods.
|
14
|
+
module CollectionMethods
|
15
|
+
def total_pages
|
16
|
+
total_entries.zero? ? 1 : (total_entries / per_page.to_f).ceil
|
17
|
+
end
|
18
|
+
|
19
|
+
# current_page - 1 or nil if there is no previous page
|
20
|
+
def previous_page
|
21
|
+
current_page > 1 ? (current_page - 1) : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
# current_page + 1 or nil if there is no next page
|
25
|
+
def next_page
|
26
|
+
current_page < total_pages ? (current_page + 1) : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
# Helper method that is true when someone tries to fetch a page with a
|
30
|
+
# larger number than the last page. Can be used in combination with flashes
|
31
|
+
# and redirecting.
|
32
|
+
def out_of_bounds?
|
33
|
+
current_page > total_pages
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# = The key to pagination
|
38
|
+
# Arrays returned from paginating finds are, in fact, instances of this little
|
39
|
+
# class. You may think of WillPaginate::Collection as an ordinary array with
|
40
|
+
# some extra properties. Those properties are used by view helpers to generate
|
41
|
+
# correct page links.
|
42
|
+
#
|
43
|
+
# WillPaginate::Collection also assists in rolling out your own pagination
|
44
|
+
# solutions: see +create+.
|
45
|
+
#
|
46
|
+
# If you are writing a library that provides a collection which you would like
|
47
|
+
# to conform to this API, you don't have to copy these methods over; simply
|
48
|
+
# make your plugin/gem dependant on this library and do:
|
49
|
+
#
|
50
|
+
# require 'will_paginate/collection'
|
51
|
+
# # WillPaginate::Collection is now available for use
|
52
|
+
class Collection < Array
|
53
|
+
include CollectionMethods
|
54
|
+
|
55
|
+
attr_reader :current_page, :per_page, :total_entries
|
56
|
+
|
57
|
+
# Arguments to the constructor are the current page number, per-page limit
|
58
|
+
# and the total number of entries. The last argument is optional because it
|
59
|
+
# is best to do lazy counting; in other words, count *conditionally* after
|
60
|
+
# populating the collection using the +replace+ method.
|
61
|
+
def initialize(page, per_page = WillPaginate.per_page, total = nil)
|
62
|
+
@current_page = WillPaginate::PageNumber(page)
|
63
|
+
@per_page = per_page.to_i
|
64
|
+
self.total_entries = total if total
|
65
|
+
end
|
66
|
+
|
67
|
+
# Just like +new+, but yields the object after instantiation and returns it
|
68
|
+
# afterwards. This is very useful for manual pagination:
|
69
|
+
#
|
70
|
+
# @entries = WillPaginate::Collection.create(1, 10) do |pager|
|
71
|
+
# result = Post.find(:all, :limit => pager.per_page, :offset => pager.offset)
|
72
|
+
# # inject the result array into the paginated collection:
|
73
|
+
# pager.replace(result)
|
74
|
+
#
|
75
|
+
# unless pager.total_entries
|
76
|
+
# # the pager didn't manage to guess the total count, do it manually
|
77
|
+
# pager.total_entries = Post.count
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# The possibilities with this are endless. For another example, here is how
|
82
|
+
# WillPaginate used to define pagination for Array instances:
|
83
|
+
#
|
84
|
+
# Array.class_eval do
|
85
|
+
# def paginate(page = 1, per_page = 15)
|
86
|
+
# WillPaginate::Collection.create(page, per_page, size) do |pager|
|
87
|
+
# pager.replace self[pager.offset, pager.per_page].to_a
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# The Array#paginate API has since then changed, but this still serves as a
|
93
|
+
# fine example of WillPaginate::Collection usage.
|
94
|
+
def self.create(page, per_page, total = nil)
|
95
|
+
pager = new(page, per_page, total)
|
96
|
+
yield pager
|
97
|
+
pager
|
98
|
+
end
|
99
|
+
|
100
|
+
# Current offset of the paginated collection. If we're on the first page,
|
101
|
+
# it is always 0. If we're on the 2nd page and there are 30 entries per page,
|
102
|
+
# the offset is 30. This property is useful if you want to render ordinals
|
103
|
+
# side by side with records in the view: simply start with offset + 1.
|
104
|
+
def offset
|
105
|
+
current_page.to_offset(per_page).to_i
|
106
|
+
end
|
107
|
+
|
108
|
+
def total_entries=(number)
|
109
|
+
@total_entries = number.to_i
|
110
|
+
end
|
111
|
+
|
112
|
+
# This is a magic wrapper for the original Array#replace method. It serves
|
113
|
+
# for populating the paginated collection after initialization.
|
114
|
+
#
|
115
|
+
# Why magic? Because it tries to guess the total number of entries judging
|
116
|
+
# by the size of given array. If it is shorter than +per_page+ limit, then we
|
117
|
+
# know we're on the last page. This trick is very useful for avoiding
|
118
|
+
# unnecessary hits to the database to do the counting after we fetched the
|
119
|
+
# data for the current page.
|
120
|
+
#
|
121
|
+
# However, after using +replace+ you should always test the value of
|
122
|
+
# +total_entries+ and set it to a proper value if it's +nil+. See the example
|
123
|
+
# in +create+.
|
124
|
+
def replace(array)
|
125
|
+
result = super
|
126
|
+
|
127
|
+
# The collection is shorter then page limit? Rejoice, because
|
128
|
+
# then we know that we are on the last page!
|
129
|
+
if total_entries.nil? and length < per_page and (current_page == 1 or length > 0)
|
130
|
+
self.total_entries = offset + length
|
131
|
+
end
|
132
|
+
|
133
|
+
result
|
134
|
+
end
|
135
|
+
|
136
|
+
attr_accessor :member_class, :origin, :origin_attribute
|
137
|
+
|
138
|
+
# Hobo extension: make paginate_by_sql, etc. carry metadata
|
139
|
+
def replace_with_hobo_metadata(array)
|
140
|
+
result = replace_without_hobo_metadata(array)
|
141
|
+
self.member_class = array.try.member_class
|
142
|
+
self.origin = array.try.origin
|
143
|
+
self.origin_attribute = array.try.origin_attribute
|
144
|
+
result
|
145
|
+
end
|
146
|
+
alias_method_chain :replace, :hobo_metadata
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|