-
Notifications
You must be signed in to change notification settings - Fork 868
/
active_record.rb
247 lines (218 loc) · 7.51 KB
/
active_record.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
require 'will_paginate/per_page'
require 'will_paginate/page_number'
require 'will_paginate/collection'
require 'active_record'
module WillPaginate
# = Paginating finders for ActiveRecord models
#
# WillPaginate adds +paginate+, +per_page+ and other methods to
# ActiveRecord::Base class methods and associations.
#
# In short, paginating finders are equivalent to ActiveRecord finders; the
# only difference is that we start with "paginate" instead of "find" and
# that <tt>:page</tt> is required parameter:
#
# @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC'
#
module ActiveRecord
# makes a Relation look like WillPaginate::Collection
module RelationMethods
include WillPaginate::CollectionMethods
attr_accessor :current_page
attr_writer :total_entries, :wp_count_options
def per_page(value = nil)
if value.nil? then limit_value
else limit(value)
end
end
# TODO: solve with less relation clones and code dups
def limit(num)
rel = super
if rel.current_page
rel.offset rel.current_page.to_offset(rel.limit_value).to_i
else
rel
end
end
# dirty hack to enable `first` after `limit` behavior above
def first(*args)
if current_page
rel = clone
rel.current_page = nil
rel.first(*args)
else
super
end
end
# fix for Rails 3.0
def find_last
if !loaded? and offset_value || limit_value
@last ||= to_a.last
else
super
end
end
def offset(value = nil)
if value.nil? then offset_value
else super(value)
end
end
def total_entries
@total_entries ||= begin
if loaded? and size < limit_value and (current_page == 1 or size > 0)
offset_value + size
else
@total_entries_queried = true
result = count
result = result.size if result.respond_to?(:size) and !result.is_a?(Integer)
result
end
end
end
def count
if limit_value
excluded = [:order, :limit, :offset, :reorder]
excluded << :includes unless eager_loading?
rel = self.except(*excluded)
# TODO: hack. decide whether to keep
rel = rel.apply_finder_options(@wp_count_options) if defined? @wp_count_options
rel.count
else
super
end
end
# workaround for Active Record 3.0
def size
if !loaded? and limit_value and group_values.empty?
[super, limit_value].min
else
super
end
end
# overloaded to be pagination-aware
def empty?
if !loaded? and offset_value
result = count
result = result.size if result.respond_to?(:size) and !result.is_a?(Integer)
result <= offset_value
else
super
end
end
def clone
copy_will_paginate_data super
end
# workaround for Active Record 3.0
def scoped(options = nil)
copy_will_paginate_data super
end
def to_a
if current_page.nil? then super # workaround for Active Record 3.0
else
::WillPaginate::Collection.create(current_page, limit_value) do |col|
col.replace super
col.total_entries ||= total_entries
end
end
end
private
def copy_will_paginate_data(other)
other.current_page = current_page unless other.current_page
other.total_entries = nil if defined? @total_entries_queried
other.wp_count_options = @wp_count_options if defined? @wp_count_options
other
end
end
module Pagination
def paginate(options)
options = options.dup
pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" }
per_page = options.delete(:per_page) || self.per_page
total = options.delete(:total_entries)
count_options = options.delete(:count)
options.delete(:page)
rel = limit(per_page.to_i).page(pagenum)
rel = rel.apply_finder_options(options) if options.any?
rel.wp_count_options = count_options if count_options
rel.total_entries = total.to_i unless total.blank?
rel
end
def page(num)
rel = if ::ActiveRecord::Relation === self
self
elsif !defined?(::ActiveRecord::Scoping) or ::ActiveRecord::Scoping::ClassMethods.method_defined? :with_scope
# Active Record 3
scoped
else
# Active Record 4
all
end
rel = rel.extending(RelationMethods)
pagenum = ::WillPaginate::PageNumber(num.nil? ? 1 : num)
per_page = rel.limit_value || self.per_page
rel = rel.offset(pagenum.to_offset(per_page).to_i)
rel = rel.limit(per_page) unless rel.limit_value
rel.current_page = pagenum
rel
end
end
module BaseMethods
# Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
# based on the params otherwise used by paginating finds: +page+ and
# +per_page+.
#
# Example:
#
# @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000],
# :page => params[:page], :per_page => 3
#
# A query for counting rows will automatically be generated if you don't
# supply <tt>:total_entries</tt>. If you experience problems with this
# generated SQL, you might want to perform the count manually in your
# application.
#
def paginate_by_sql(sql, options)
pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" } || 1
per_page = options[:per_page] || self.per_page
total = options[:total_entries]
WillPaginate::Collection.create(pagenum, per_page, total) do |pager|
query = sanitize_sql(sql.dup)
original_query = query.dup
oracle = self.connection.adapter_name =~ /^(oracle|oci$)/i
# add limit, offset
if oracle
query = <<-SQL
SELECT * FROM (
SELECT rownum rnum, a.* FROM (#{query}) a
WHERE rownum <= #{pager.offset + pager.per_page}
) WHERE rnum >= #{pager.offset}
SQL
else
query << " LIMIT #{pager.per_page} OFFSET #{pager.offset}"
end
# perfom the find
pager.replace find_by_sql(query)
unless pager.total_entries
count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s.]+$/mi, ''
count_query = "SELECT COUNT(*) FROM (#{count_query})"
count_query << ' AS count_table' unless oracle
# perform the count query
pager.total_entries = count_by_sql(count_query)
end
end
end
end
# mix everything into Active Record
::ActiveRecord::Base.extend PerPage
::ActiveRecord::Base.extend Pagination
::ActiveRecord::Base.extend BaseMethods
klasses = [::ActiveRecord::Relation]
if defined? ::ActiveRecord::Associations::CollectionProxy
klasses << ::ActiveRecord::Associations::CollectionProxy
else
klasses << ::ActiveRecord::Associations::AssociationCollection
end
# support pagination on associations and scopes
klasses.each { |klass| klass.send(:include, Pagination) }
end
end