activerecord_cursor_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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +220 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/examples/jwt_cursor_serializer.rb +14 -0
- data/lib/activerecord_cursor_pagination/ascending_order.rb +21 -0
- data/lib/activerecord_cursor_pagination/class_formatter.rb +19 -0
- data/lib/activerecord_cursor_pagination/configuration.rb +45 -0
- data/lib/activerecord_cursor_pagination/cursor.rb +144 -0
- data/lib/activerecord_cursor_pagination/cursor_scope.rb +426 -0
- data/lib/activerecord_cursor_pagination/descending_order.rb +21 -0
- data/lib/activerecord_cursor_pagination/empty_cursor.rb +35 -0
- data/lib/activerecord_cursor_pagination/extension.rb +18 -0
- data/lib/activerecord_cursor_pagination/model_extension.rb +94 -0
- data/lib/activerecord_cursor_pagination/order_base.rb +275 -0
- data/lib/activerecord_cursor_pagination/secret_key_finder.rb +15 -0
- data/lib/activerecord_cursor_pagination/secure_cursor_serializer.rb +44 -0
- data/lib/activerecord_cursor_pagination/serializer.rb +39 -0
- data/lib/activerecord_cursor_pagination/sql_signer.rb +31 -0
- data/lib/activerecord_cursor_pagination/version.rb +3 -0
- data/lib/activerecord_cursor_pagination.rb +81 -0
- metadata +225 -0
@@ -0,0 +1,426 @@
|
|
1
|
+
module ActiverecordCursorPagination
|
2
|
+
class CursorScope
|
3
|
+
attr_reader :per_page
|
4
|
+
|
5
|
+
##
|
6
|
+
# @example Empty cursor
|
7
|
+
# Posts.where(published: true).order(created_at: :desc).cursor(nil, per: 100)
|
8
|
+
# Posts.where(published: true).order(created_at: :desc).cursor("", per: 100)
|
9
|
+
# Posts.where(published: true).order(created_at: :desc).cursor(EmptyCursor.new, per: 100)
|
10
|
+
#
|
11
|
+
# @example Serialized cursor
|
12
|
+
# Posts.where(published: true).order(created_at: :desc).cursor("SerializedCursorString", per: 100)
|
13
|
+
#
|
14
|
+
# @example Record cursor
|
15
|
+
# Posts.where(published: true).order(created_at: :desc).cursor(Post.find!(6), per: 100)
|
16
|
+
#
|
17
|
+
# @example Cursor
|
18
|
+
# cursor = ...deserialized cursor...
|
19
|
+
# Posts.where(published: true).order(created_at: :desc).cursor(cursor, per: 100)
|
20
|
+
#
|
21
|
+
# @param [Class] klass Model class
|
22
|
+
# @param [ActiveRecord::Relation] scope The database query.
|
23
|
+
# @param [String, Cursor, EmptyCursor, ActiveRecord::Base, nil] cursor The current page cursor.
|
24
|
+
# @option [Integer] per The number of records per page.
|
25
|
+
#
|
26
|
+
# @raise [InvalidCursorError] When the cursor is does not match the query or the cursor is not a valid type.
|
27
|
+
def initialize(klass, scope, cursor, per: 15)
|
28
|
+
@scope = scope.except :offset, :limit
|
29
|
+
@klass = klass
|
30
|
+
@per_page = per
|
31
|
+
@table = @scope.table_name
|
32
|
+
@id_column = "#{@table}.id"
|
33
|
+
|
34
|
+
initialize_order_columns
|
35
|
+
initialize_cursor cursor
|
36
|
+
initialize_order_column_values
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Get if the query is for single records
|
41
|
+
#
|
42
|
+
# @return [Boolean] True if only one record per page
|
43
|
+
def single_record?
|
44
|
+
@per_page === 1
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Get the total count of records from the query scope.
|
49
|
+
#
|
50
|
+
# @return [Integer] The number of total records.
|
51
|
+
def scope_size
|
52
|
+
@scope.except(:select).size
|
53
|
+
end
|
54
|
+
|
55
|
+
alias_method :scope_count, :scope_size
|
56
|
+
alias_method :total_count, :scope_size
|
57
|
+
alias_method :total, :scope_size
|
58
|
+
|
59
|
+
##
|
60
|
+
# Get if there are not records from the query scope.
|
61
|
+
#
|
62
|
+
# @return [Boolean] True if the query scope is empty.
|
63
|
+
def scope_empty?
|
64
|
+
total_count.zero?
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_method :scope_none?, :scope_empty?
|
68
|
+
|
69
|
+
##
|
70
|
+
# Get if there are records from the query scope.
|
71
|
+
#
|
72
|
+
# @return [Boolean] True if the query scope is not empty.
|
73
|
+
def scope_any?
|
74
|
+
!scope_empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Get if there is only one record from the query scope.
|
79
|
+
#
|
80
|
+
# @return [Boolean] True if there is only one record.
|
81
|
+
def scope_one?
|
82
|
+
total_count == 1
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Get if there are many records from the query scope.
|
87
|
+
#
|
88
|
+
# @return [Boolean] True if there is more than one record.
|
89
|
+
def scope_many?
|
90
|
+
total_count > 1
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Get the number of records in the current page
|
95
|
+
#
|
96
|
+
# @return [Integer] The number of records
|
97
|
+
def size
|
98
|
+
current_page_scope.except(:select).size
|
99
|
+
end
|
100
|
+
|
101
|
+
alias_method :count, :size
|
102
|
+
alias_method :length, :size
|
103
|
+
|
104
|
+
##
|
105
|
+
# Get if there no records in the current page.
|
106
|
+
#
|
107
|
+
# @return [Boolean] True if there are no records.
|
108
|
+
def empty?
|
109
|
+
size.zero?
|
110
|
+
end
|
111
|
+
|
112
|
+
alias_method :none?, :empty?
|
113
|
+
|
114
|
+
##
|
115
|
+
# Get if there are records in the current page.
|
116
|
+
#
|
117
|
+
# @return [Boolean] True if not empty.
|
118
|
+
def any?
|
119
|
+
!empty?
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Get if there are many records in the current page.
|
124
|
+
#
|
125
|
+
# @return [Boolean] True if there is more than one record.
|
126
|
+
def many?
|
127
|
+
size > 1
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Get if there is only one in the current page.
|
132
|
+
#
|
133
|
+
# @return [Boolean] True if there is only one record.
|
134
|
+
def one?
|
135
|
+
size == 1
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Get if there is a previous page from the cursor
|
140
|
+
#
|
141
|
+
# @return [Boolean] True if there is previous page
|
142
|
+
def previous_page?
|
143
|
+
return false if scope_empty?
|
144
|
+
previous_page_scope.any?
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Get if there is another page
|
149
|
+
#
|
150
|
+
# @return [Boolean] True if there is a next page
|
151
|
+
def next_page?
|
152
|
+
return false if scope_empty?
|
153
|
+
next_page_scope.any?
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Get if the cursor is the first page
|
158
|
+
#
|
159
|
+
# @return [Boolean] True if first page
|
160
|
+
def first_page?
|
161
|
+
scope_empty? || !previous_page?
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Get if the cursor is the last page
|
166
|
+
#
|
167
|
+
# @return [Boolean] True if last page
|
168
|
+
def last_page?
|
169
|
+
scope_empty? || !next_page?
|
170
|
+
end
|
171
|
+
|
172
|
+
##
|
173
|
+
# Get the current cursor
|
174
|
+
#
|
175
|
+
# @return [String] The current cursor
|
176
|
+
def current_cursor
|
177
|
+
@cursor.to_param
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Get the next page cursor
|
182
|
+
#
|
183
|
+
# @return [String]
|
184
|
+
def next_cursor
|
185
|
+
return EmptyCursor.to_param unless next_page?
|
186
|
+
ids = next_page_scope.pluck(:id)
|
187
|
+
Cursor.to_param @klass, @scope, @per_page, ids.first, ids.last
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Get the next record
|
192
|
+
#
|
193
|
+
# @raise [NotSingleRecordError] If the number of records per page is not one.
|
194
|
+
#
|
195
|
+
# @return [ActiveRecord::Base]
|
196
|
+
def next_cursor_record
|
197
|
+
raise NotSingleRecordError unless single_record?
|
198
|
+
next_page_scope.first if next_page?
|
199
|
+
end
|
200
|
+
|
201
|
+
##
|
202
|
+
# Get the previous page cursor.
|
203
|
+
#
|
204
|
+
# @return [String]
|
205
|
+
def previous_cursor
|
206
|
+
return EmptyCursor.to_param unless previous_page?
|
207
|
+
ids = previous_page_scope.pluck(:id)
|
208
|
+
Cursor.to_param @klass, @scope, @per_page, ids.last, ids.first
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Get the previous record.
|
213
|
+
#
|
214
|
+
# @raise [NotSingleRecordError] If the number of records per page is not one.
|
215
|
+
#
|
216
|
+
# @return [ActiveRecord::Base]
|
217
|
+
def previous_cursor_record
|
218
|
+
raise NotSingleRecordError unless single_record?
|
219
|
+
previous_page_scope.first if previous_page?
|
220
|
+
end
|
221
|
+
|
222
|
+
##
|
223
|
+
# Iterate each record in the current page.
|
224
|
+
#
|
225
|
+
# @yield [ActiveRecord::Base] Invokes the block with each active record.
|
226
|
+
def each(&block)
|
227
|
+
current_page_scope.each &block
|
228
|
+
end
|
229
|
+
|
230
|
+
##
|
231
|
+
# Iterate each record in the current page.
|
232
|
+
#
|
233
|
+
# @yield [ActiveRecord::Base, Integer] Invokes the block with each active record and page row index.
|
234
|
+
def each_with_index(&block)
|
235
|
+
i = 0
|
236
|
+
current_page_scope.each do |r|
|
237
|
+
block.call r, i
|
238
|
+
i += 1
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
##
|
243
|
+
# Map each record in the current page.
|
244
|
+
#
|
245
|
+
# @yield [ActiveRecord::Base] Invokes the block with each active record.
|
246
|
+
def map(&block)
|
247
|
+
current_page_scope.map &block
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# Map each record in the current page.
|
252
|
+
#
|
253
|
+
# @yield [ActiveRecord::Base, Integer] Invokes the block with each active record and page row index.
|
254
|
+
def map_with_index(&block)
|
255
|
+
current_page_scope.map.with_index do |r, i|
|
256
|
+
block.call r, i
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
def initialize_order_columns
|
263
|
+
# FIXME Limitation / Code Smell - Invoking Private Method
|
264
|
+
# As of right now, there is no public method to get the values from the .order() method.
|
265
|
+
# The private .order_values method is undocumented and future releases of ActiveRecord can
|
266
|
+
# remove/change this method.
|
267
|
+
# See .order! for reference to the method.
|
268
|
+
# https://apidock.com/rails/v5.1.7/ActiveRecord/QueryMethods/order%21
|
269
|
+
@order_columns = @scope.send(:order_values).map.with_index do |node, index|
|
270
|
+
order = OrderBase.parse node, index
|
271
|
+
order.base_id = order.table === @table && order.name == 'id'
|
272
|
+
order
|
273
|
+
end
|
274
|
+
|
275
|
+
unless @order_columns.any?(&:base_id?)
|
276
|
+
order_column = AscendingOrder.new @table, 'id', @order_columns.size
|
277
|
+
order_column.base_id = true
|
278
|
+
@order_columns << order_column
|
279
|
+
@scope = @scope.order order_column.order_sql
|
280
|
+
end
|
281
|
+
|
282
|
+
@id_column_index = @order_columns.find_index &:base_id?
|
283
|
+
end
|
284
|
+
|
285
|
+
def initialize_cursor(cursor)
|
286
|
+
@cursor = EmptyCursor.new
|
287
|
+
|
288
|
+
if cursor.nil? || cursor.try(:empty?)
|
289
|
+
ids = build_sql_order(@scope, false).limit(@per_page).pluck(:id)
|
290
|
+
|
291
|
+
@cursor = Cursor.new @klass, @scope, @per_page, ids.first, ids.last unless ids.empty?
|
292
|
+
elsif cursor.is_a? Cursor
|
293
|
+
@cursor = cursor
|
294
|
+
elsif cursor.is_a? String
|
295
|
+
@cursor = Cursor.parse cursor
|
296
|
+
elsif cursor.is_a? ActiveRecord::Base
|
297
|
+
if single_record?
|
298
|
+
@cursor = Cursor.new @klass, @scope, @per_page, cursor.id, cursor.id
|
299
|
+
else
|
300
|
+
calculate_cursor_from_record cursor
|
301
|
+
end
|
302
|
+
else
|
303
|
+
raise InvalidCursorError.new("Invalid cursor type #{cursor.class.name}", cursor)
|
304
|
+
end
|
305
|
+
|
306
|
+
@cursor.validate! @klass, @scope, @per_page unless @cursor.empty?
|
307
|
+
end
|
308
|
+
|
309
|
+
def initialize_order_column_values
|
310
|
+
if @cursor.empty?
|
311
|
+
@start_column_values = @end_column_values = []
|
312
|
+
elsif single_record?
|
313
|
+
@start_column_values = @end_column_values = ensure_array @scope.only(:from, :joins)
|
314
|
+
.where(id: @cursor.start_id).limit(1)
|
315
|
+
.pluck(*@order_columns.map(&:quote_full_name))
|
316
|
+
.first
|
317
|
+
else
|
318
|
+
query = @scope.only :from, :joins
|
319
|
+
|
320
|
+
@start_column_values = ensure_array query.where(id: @cursor.start_id)
|
321
|
+
.limit(1)
|
322
|
+
.pluck(*@order_columns.map(&:quote_full_name))
|
323
|
+
.first
|
324
|
+
|
325
|
+
@end_column_values = ensure_array query.where(id: @cursor.end_id)
|
326
|
+
.limit(1)
|
327
|
+
.pluck(*@order_columns.map(&:quote_full_name))
|
328
|
+
.first
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def build_sql_order(query, reverse)
|
333
|
+
order_array = @order_columns.map { |c| reverse ? c.reverse.order_sql : c.order_sql }
|
334
|
+
query.unscope(:order).order(*order_array)
|
335
|
+
end
|
336
|
+
|
337
|
+
def build_sql_from_columns(query, column_values, id_direction: :start)
|
338
|
+
conditions = []
|
339
|
+
|
340
|
+
values_hash = 0.upto(column_values.size - 1).inject({}) do |hash, i|
|
341
|
+
hash.merge @order_columns[i].statement_key => column_values[i]
|
342
|
+
end
|
343
|
+
|
344
|
+
@order_columns.each_with_index do |column, i|
|
345
|
+
sql = []
|
346
|
+
|
347
|
+
0.upto(i - 1) { |p| sql << @order_columns[p].equals_sql }
|
348
|
+
|
349
|
+
if column.base_id?
|
350
|
+
if id_direction == :previous
|
351
|
+
sql << column.reverse.than_sql
|
352
|
+
elsif id_direction == :next
|
353
|
+
sql << column.than_sql
|
354
|
+
elsif id_direction == :end
|
355
|
+
sql << column.reverse.than_or_equal_sql
|
356
|
+
else
|
357
|
+
sql << column.than_or_equal_sql
|
358
|
+
end
|
359
|
+
else
|
360
|
+
if id_direction == :previous || id_direction == :end
|
361
|
+
sql << column.reverse.than_sql
|
362
|
+
else
|
363
|
+
sql << column.than_sql
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
conditions << "(#{sql.join ' AND '})"
|
368
|
+
end
|
369
|
+
|
370
|
+
query.where conditions.join(' OR '), values_hash
|
371
|
+
end
|
372
|
+
|
373
|
+
def calculate_cursor_from_record(record)
|
374
|
+
column_values = @scope.only(:from, :joins)
|
375
|
+
.where(id: record.id)
|
376
|
+
.limit(1)
|
377
|
+
.pluck(*@order_columns.map(&:full_name))
|
378
|
+
.first
|
379
|
+
|
380
|
+
column_values = ensure_array column_values
|
381
|
+
query = build_sql_order @scope.only(:from, :joins, :where), false
|
382
|
+
count_query = build_sql_from_columns query, column_values, id_direction: :previous
|
383
|
+
count_query = build_sql_order count_query, true
|
384
|
+
page = (count_query.count / @per_page).floor
|
385
|
+
|
386
|
+
page_values = query.offset(page * @per_page)
|
387
|
+
.limit(@per_page)
|
388
|
+
.pluck(*@order_columns.map(&:full_name))
|
389
|
+
|
390
|
+
@start_column_values = ensure_array page_values.first
|
391
|
+
@end_column_values = ensure_array page_values.last
|
392
|
+
|
393
|
+
@cursor = Cursor.new @klass,
|
394
|
+
@scope,
|
395
|
+
@per_page,
|
396
|
+
@start_column_values[@id_column_index],
|
397
|
+
@end_column_values[@id_column_index]
|
398
|
+
end
|
399
|
+
|
400
|
+
def previous_page_scope
|
401
|
+
query = build_sql_from_columns @scope, @start_column_values, id_direction: :previous
|
402
|
+
query = build_sql_order query, true
|
403
|
+
query.limit @per_page
|
404
|
+
end
|
405
|
+
|
406
|
+
def current_page_scope
|
407
|
+
if @cursor.empty?
|
408
|
+
build_sql_order(@scope, false).limit(@per_page)
|
409
|
+
else
|
410
|
+
query = build_sql_from_columns @scope, @start_column_values, id_direction: :start
|
411
|
+
query = build_sql_from_columns query, @end_column_values, id_direction: :end
|
412
|
+
build_sql_order query, false
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
def next_page_scope
|
417
|
+
query = build_sql_from_columns @scope, @end_column_values, id_direction: :next
|
418
|
+
query = build_sql_order query, false
|
419
|
+
query.limit @per_page
|
420
|
+
end
|
421
|
+
|
422
|
+
def ensure_array(value)
|
423
|
+
value.is_a?(Array) ? value : [value]
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiverecordCursorPagination
|
2
|
+
class DescendingOrder < OrderBase
|
3
|
+
def direction
|
4
|
+
:desc
|
5
|
+
end
|
6
|
+
|
7
|
+
def reverse
|
8
|
+
order = AscendingOrder.new table, name, index
|
9
|
+
order.base_id = base_id
|
10
|
+
order
|
11
|
+
end
|
12
|
+
|
13
|
+
def than_op
|
14
|
+
'<'
|
15
|
+
end
|
16
|
+
|
17
|
+
def than_or_equal_op
|
18
|
+
'<='
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ActiverecordCursorPagination
|
2
|
+
class EmptyCursor
|
3
|
+
##
|
4
|
+
# Is the cursor not empty
|
5
|
+
#
|
6
|
+
# @return [Boolean]
|
7
|
+
def present?
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Is the cursor empty
|
13
|
+
#
|
14
|
+
# @return [Boolean]
|
15
|
+
def empty?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Get the string representation of the cursor
|
21
|
+
#
|
22
|
+
# @return [String] The serialized cursor
|
23
|
+
def to_s
|
24
|
+
''
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :to_param, :to_s
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def to_param
|
31
|
+
EmptyCursor.new.to_param
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiverecordCursorPagination
|
2
|
+
module Extension
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def inherited(kls)
|
7
|
+
super
|
8
|
+
kls.send :include, ModelExtension if kls.superclass == ActiveRecord::Base
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
included do
|
13
|
+
descendants.each do |kls|
|
14
|
+
kls.send :include, ModelExtension if kls.superclass == ActiveRecord::Base
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module ActiverecordCursorPagination
|
2
|
+
module ModelExtension
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
##
|
7
|
+
# Get the paginated cursor for the current query
|
8
|
+
#
|
9
|
+
# @param [String, Cursor, EmptyCursor, ActiveRecord::Base, nil] cursor
|
10
|
+
#
|
11
|
+
# The current page cursor.
|
12
|
+
#
|
13
|
+
# If an ActiveRecord::Base is passed, the current page will be calculated based on the record id.
|
14
|
+
#
|
15
|
+
# @option [Integer] per
|
16
|
+
#
|
17
|
+
# Limit the number of records per page.
|
18
|
+
#
|
19
|
+
# @return [CursorScope] The current page scope
|
20
|
+
def cursor(cursor, per: 15)
|
21
|
+
klass = all.instance_variable_get :@klass
|
22
|
+
CursorScope.new klass, self, cursor, per: per
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Page batching using a cursor
|
27
|
+
#
|
28
|
+
# @option [Integer] batch_size
|
29
|
+
#
|
30
|
+
# Limits the size of each batch
|
31
|
+
#
|
32
|
+
# @yield [cursor_scope] Invokes the block with the cursor_scope for each result.
|
33
|
+
def cursor_batch(batch_size: 1000, &block)
|
34
|
+
current_cursor = nil
|
35
|
+
|
36
|
+
begin
|
37
|
+
cursor = cursor current_cursor, per: batch_size
|
38
|
+
block.call cursor
|
39
|
+
current_cursor = cursor.next_cursor
|
40
|
+
end until cursor.last_page?
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Page batching using a cursor
|
45
|
+
#
|
46
|
+
# @option [Integer] batch_size
|
47
|
+
#
|
48
|
+
# Limits the size of each batch
|
49
|
+
#
|
50
|
+
# @yield [cursor_scope] Invokes the block with the cursor scope and the index for each result.
|
51
|
+
def cursor_batch_with_index(batch_size: 1000, &block)
|
52
|
+
i = 0
|
53
|
+
|
54
|
+
self.cursor_batch batch_size: batch_size do |c|
|
55
|
+
block.call c, i
|
56
|
+
i += 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Find each record using the cursor batch
|
62
|
+
#
|
63
|
+
# @option [Integer] batch_size
|
64
|
+
#
|
65
|
+
# Limits the size of each batch
|
66
|
+
#
|
67
|
+
# @yield [record] Invokes the block with a record for each result.
|
68
|
+
def cursor_find_each(batch_size: 1000, &block)
|
69
|
+
self.cursor_batch batch_size: batch_size do |c|
|
70
|
+
c.each &block
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Find each record using the cursor batch
|
76
|
+
#
|
77
|
+
# @option [Integer] batch_size
|
78
|
+
#
|
79
|
+
# Limits the size of each batch
|
80
|
+
#
|
81
|
+
# @yield [record] Invokes the block with a record and the index for each result.
|
82
|
+
def cursor_find_each_with_index(batch_size: 1000, &block)
|
83
|
+
i = 0
|
84
|
+
|
85
|
+
self.cursor_batch batch_size: batch_size do |c|
|
86
|
+
c.each do |r|
|
87
|
+
block.call r, i
|
88
|
+
i += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|