forked from mislav/will_paginate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
collection.rb
152 lines (137 loc) · 5.63 KB
/
collection.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
require 'will_paginate/per_page'
require 'will_paginate/page_number'
module WillPaginate
# Any will_paginate-compatible collection should have these methods:
#
# current_page, per_page, offset, total_entries, total_pages
#
# It can also define some of these optional methods:
#
# out_of_bounds?, previous_page, next_page
#
# This module provides few of these methods.
module CollectionMethods
def total_pages
total_entries.zero? ? 1 : (total_entries / per_page.to_f).ceil
end
# current_page - 1 or nil if there is no previous page
def previous_page
current_page > 1 ? (current_page - 1) : nil
end
# current_page + 1 or nil if there is no next page
def next_page
current_page < total_pages ? (current_page + 1) : nil
end
# Helper method that is true when someone tries to fetch a page with a
# larger number than the last page. Can be used in combination with flashes
# and redirecting.
def out_of_bounds?
current_page > total_pages
end
end
# = The key to pagination
# Arrays returned from paginating finds are, in fact, instances of this little
# class. You may think of WillPaginate::Collection as an ordinary array with
# some extra properties. Those properties are used by view helpers to generate
# correct page links.
#
# WillPaginate::Collection also assists in rolling out your own pagination
# solutions: see +create+.
#
# If you are writing a library that provides a collection which you would like
# to conform to this API, you don't have to copy these methods over; simply
# make your plugin/gem dependant on this library and do:
#
# require 'will_paginate/collection'
# # WillPaginate::Collection is now available for use
class Collection < Array
include CollectionMethods
attr_reader :current_page, :per_page, :total_entries
# Arguments to the constructor are the current page number, per-page limit
# and the total number of entries. The last argument is optional because it
# is best to do lazy counting; in other words, count *conditionally* after
# populating the collection using the +replace+ method.
def initialize(page, per_page = WillPaginate.per_page, total = nil)
@current_page = WillPaginate::PageNumber(page)
@per_page = per_page.to_i
self.total_entries = total if total
end
# Just like +new+, but yields the object after instantiation and returns it
# afterwards. This is very useful for manual pagination:
#
# @entries = WillPaginate::Collection.create(1, 10) do |pager|
# result = Post.find(:all, :limit => pager.per_page, :offset => pager.offset)
# # inject the result array into the paginated collection:
# pager.replace(result)
#
# unless pager.total_entries
# # the pager didn't manage to guess the total count, do it manually
# pager.total_entries = Post.count
# end
# end
#
# The possibilities with this are endless. For another example, here is how
# WillPaginate used to define pagination for Array instances:
#
# Array.class_eval do
# def paginate(page = 1, per_page = 15)
# WillPaginate::Collection.create(page, per_page, size) do |pager|
# pager.replace self[pager.offset, pager.per_page].to_a
# end
# end
# end
#
# The Array#paginate API has since then changed, but this still serves as a
# fine example of WillPaginate::Collection usage.
def self.create(page, per_page, total = nil)
pager = new(page, per_page, total)
yield pager
pager
end
# Current offset of the paginated collection. If we're on the first page,
# it is always 0. If we're on the 2nd page and there are 30 entries per page,
# the offset is 30. This property is useful if you want to render ordinals
# side by side with records in the view: simply start with offset + 1.
def offset
current_page.to_offset(per_page).to_i
end
def total_entries=(number)
@total_entries = number.to_i
end
# This is a magic wrapper for the original Array#replace method. It serves
# for populating the paginated collection after initialization.
#
# Why magic? Because it tries to guess the total number of entries judging
# by the size of given array. If it is shorter than +per_page+ limit, then we
# know we're on the last page. This trick is very useful for avoiding
# unnecessary hits to the database to do the counting after we fetched the
# data for the current page.
#
# However, after using +replace+ you should always test the value of
# +total_entries+ and set it to a proper value if it's +nil+. See the example
# in +create+.
def replace(array)
result = super
# The collection is shorter then page limit? Rejoice, because
# then we know that we are on the last page!
if total_entries.nil? and length < per_page and (current_page == 1 or length > 0)
self.total_entries = offset + length
end
result
end
# We want to return paginated collections when mapped. Unfortunately
# when arrays are copied in C code for slices or other operations,
# they are initialized using the array initializer, not ours. This
# means that current page is lost and creating a new array is impossible.
# we treat copied arrays as non-paginated collections and just delegate to super.
def map(&block)
if current_page.nil?
super(&block)
else
self.class.create(current_page, per_page, total_entries) do |pager|
pager.replace(super(&block))
end
end
end
end
end