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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ec58ef3cae3354d8ae2bde159b228728f072aa4f79bd57ea7d950cfd7a56dd7c
|
4
|
+
data.tar.gz: 1b5d74a9eb2c66cd08219688f97c697f5df984551bee64a83c763a964971357e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5440057c5689b27043c534e49048579c7a2a21108f4b7c91e39cf3bb1f1ee08bbd7f366ce14e25156a55fcbff3cebd5b8274803c86e16ce952bfd54c40a1991e
|
7
|
+
data.tar.gz: '0219a04ac75387cbe73056447dfdbfb6cbf7cf3cc7bf0118f35aab50128910560f72134786c695de93de7834acb5fac6240316db3863c951cf9bce54f9c7a5a7'
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 James Fawks
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+

|
2
|
+
|
3
|
+
# ActiverecordCursorPagination
|
4
|
+
|
5
|
+
ActiveRecord plugin for cursor based pagination using a serialized representation of the pages to paginate your content.
|
6
|
+
|
7
|
+
The main advantage to cursor based pagination over the traditional (`limit` & `offset`) is that the cursors are not
|
8
|
+
impacted by changes to the query (i.e. new records or records that no longer fit the query conditions).
|
9
|
+
|
10
|
+
The advantage of `ActiverecordCursorPagination` over other gems is their is no requirement to define a row key (usually `id`) to sort
|
11
|
+
the records. This allows for more complex queries to include joins or subqueries, and for ordering to also include
|
12
|
+
table aliases or complex operations.
|
13
|
+
|
14
|
+
## Motivation
|
15
|
+
|
16
|
+
I needed a cursor pagination method that was key agnostic and where I can order the records in any method I wish;
|
17
|
+
including using complex queries or table aliases. This was especially important when building out user feeds.
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'activerecord_cursor_pagination'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle install
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install activerecord_cursor_pagination
|
34
|
+
|
35
|
+
## The `cursor` Basics
|
36
|
+
|
37
|
+
By default, `ActiverecordCursorPagination` defaults to 15 results per page.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# Imagine there are 60 *total* posts (at 10 results/page, that is 6 pages)
|
41
|
+
cursor = Posts.where(published: true)
|
42
|
+
.order(published_at: :desc)
|
43
|
+
.cursor(nil)
|
44
|
+
|
45
|
+
cursor.per_page # => 10
|
46
|
+
|
47
|
+
# scoped to the whole query
|
48
|
+
cursor.scope_size # => 60
|
49
|
+
cursor.scope_empty? # => false
|
50
|
+
cursor.scope_any? # => true
|
51
|
+
cursor.scope_one? # => false
|
52
|
+
cursor.scope_many? # => true
|
53
|
+
|
54
|
+
# scoped to the current page
|
55
|
+
cursor.size # => 10
|
56
|
+
cursor.empty? # => false
|
57
|
+
cursor.any? # => true
|
58
|
+
cursor.one? # => false
|
59
|
+
cursor.many? # => true
|
60
|
+
|
61
|
+
# pagination
|
62
|
+
cursor.current_page # => "serialized cursor..."
|
63
|
+
cursor.first_page? # => true
|
64
|
+
cursor.last_page? # => false
|
65
|
+
cursor.next_page? # => true
|
66
|
+
cursor.next_page # => "serialized cursor..."
|
67
|
+
cursor.previous_page? # => false
|
68
|
+
cursor.previous_cursor # => ""
|
69
|
+
```
|
70
|
+
|
71
|
+
To retrieve the next page of results, pass the next page cursor.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
cursor = Posts.where(published: true)
|
75
|
+
.order(published_at: :desc)
|
76
|
+
.cursor("next page serialized cursor...")
|
77
|
+
|
78
|
+
cursor.per_page # => 10
|
79
|
+
|
80
|
+
# scoped to the whole query
|
81
|
+
cursor.scope_size # => 60
|
82
|
+
cursor.scope_empty? # => false
|
83
|
+
cursor.scope_any? # => true
|
84
|
+
cursor.scope_one? # => false
|
85
|
+
cursor.scope_many? # => true
|
86
|
+
|
87
|
+
# scoped to the current page
|
88
|
+
cursor.size # => 10
|
89
|
+
cursor.empty? # => false
|
90
|
+
cursor.any? # => true
|
91
|
+
cursor.one? # => false
|
92
|
+
cursor.many? # => true
|
93
|
+
|
94
|
+
# pagination
|
95
|
+
cursor.current_page # => "serialized cursor..."
|
96
|
+
cursor.first_page? # => false
|
97
|
+
cursor.last_page? # => false
|
98
|
+
cursor.next_page? # => true
|
99
|
+
cursor.next_page # => "serialized cursor..."
|
100
|
+
cursor.previous_page? # => true
|
101
|
+
cursor.previous_cursor # => "serialized cursor..."
|
102
|
+
```
|
103
|
+
|
104
|
+
You can iterate through the current page of results.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
cursor.each { |record| /* do something */ }
|
108
|
+
cursor.each_with_index { |record, index| /* do something */ }
|
109
|
+
mapped = cursor.map { |record| /* do something */ }
|
110
|
+
mapped = cursor.map_with_index { |record, index| /* do something */ }
|
111
|
+
```
|
112
|
+
|
113
|
+
A custom number of results per page can be specified by passing the `per` option.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
cursor = Posts.where(published: true)
|
117
|
+
.order(published_at: :desc)
|
118
|
+
.cursor(nil, per: 50)
|
119
|
+
```
|
120
|
+
|
121
|
+
## PagerView Helpers
|
122
|
+
|
123
|
+
Lets image you have a pager view that displays one `Post` at a time and you have left and right errors to go to
|
124
|
+
the next or previous record.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
post = Post.find(10)
|
128
|
+
|
129
|
+
cursor = Posts.where(published: true)
|
130
|
+
.order(published_at: :desc)
|
131
|
+
.cursor(post, per: 1)
|
132
|
+
|
133
|
+
cursor.next_cursor_record # => [Post] Next published post
|
134
|
+
cursor.previous_cursor_record # => [Post] Previous published post
|
135
|
+
```
|
136
|
+
|
137
|
+
**Make sure to set `per` to `1` or you will get a `NotSingleRecordError`**
|
138
|
+
|
139
|
+
If no record can be found, `next_cursor_record` and `previous_cursor_record` will return `nil`.
|
140
|
+
|
141
|
+
## Configuration
|
142
|
+
|
143
|
+
Configure `ActiverecordCursorPagination` using the `setup` method.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
ActiverecordCursorPagination.setup do |config|
|
147
|
+
config.secret_key = 'your super secret key'
|
148
|
+
config.serializer = YourCustomSerializer
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
## Custom Cursor Serializer
|
153
|
+
|
154
|
+
To create a custom cursor serializer, you need to override `ActiverecordCursorPagination::Serializer`.
|
155
|
+
Call `secret_key` in your custom class to get the configured cursor key.
|
156
|
+
|
157
|
+
**If you secure your database with external ids, make sure to encrypt the tokens so you don't
|
158
|
+
expose the internal database ids.**
|
159
|
+
|
160
|
+
For instance, to create a `JWT` serializer:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
class JwtCursorSerializer < ActiverecordCursorPagination::Serializer
|
164
|
+
def deserialize(str)
|
165
|
+
data = JWT.decode str,
|
166
|
+
secret_key,
|
167
|
+
true,
|
168
|
+
{ algorithm: 'HS256' }
|
169
|
+
|
170
|
+
data.first.symbolize_keys
|
171
|
+
end
|
172
|
+
|
173
|
+
def serialize(hash)
|
174
|
+
JWT.encode hash, secret_key,'HS256'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
Make sure to configure `ActiverecordCursorPagination` by setting the `serializer`
|
180
|
+
configuration option with your new serializer.
|
181
|
+
|
182
|
+
## Known Issues/Limitations
|
183
|
+
|
184
|
+
- There is no known public method to call to get the order values. Currently calls `order_values` to get a list of all order values in the current query scope.
|
185
|
+
- When using a sub query or `CASE` statement as an order value, you have to use single quote strings.
|
186
|
+
|
187
|
+
## Development
|
188
|
+
|
189
|
+
After checking out the repo, run `bin/setup` to install dependencies.
|
190
|
+
Then, run `rake spec` to run the tests.
|
191
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
192
|
+
|
193
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
194
|
+
|
195
|
+
## Testing
|
196
|
+
|
197
|
+
Run `rake rspec` to run all the tests or you can run:
|
198
|
+
- `rake rspec [path]` to run all the tests in a given directory,
|
199
|
+
- or `rake rspec [file]` to run a specific file.
|
200
|
+
|
201
|
+
## Contributing
|
202
|
+
|
203
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jefawks3/activerecord_cursor_pagination.
|
204
|
+
|
205
|
+
I hope that you will consider contributing to ActiverecordCursorPagination.
|
206
|
+
You can contribute in many ways. For example, you might:
|
207
|
+
- add documentation and “how-to” articles to the README or Wiki.
|
208
|
+
- hack on ActiverecordCursorPagination itself by fixing bugs you've found in the GitHub Issue tracker or adding new features to ActiverecordCursorPagination.
|
209
|
+
|
210
|
+
When contributing to ActiverecordCursorPagination, we ask that you:
|
211
|
+
- let me know what you plan in the GitHub Issue tracker so I can provide feedback.
|
212
|
+
- provide tests and documentation whenever possible. It is very unlikely that I will accept new features or functionality into ActiverecordCursorPagination without the proper testing and documentation. When fixing a bug, provide a failing test case that your patch solves.
|
213
|
+
- open a GitHub Pull Request with your patches and I will review your contribution and respond as quickly as possible.
|
214
|
+
|
215
|
+
Keep in mind that this is an open source project, and it may take me some time to get back to you.
|
216
|
+
Your patience is very much appreciated.
|
217
|
+
|
218
|
+
## License
|
219
|
+
|
220
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "activerecord_cursor_pagination"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class JwtCursorSerializer < ActiverecordCursorPagination::Serializer
|
2
|
+
def deserialize(str)
|
3
|
+
data = JWT.decode str,
|
4
|
+
secret_key,
|
5
|
+
true,
|
6
|
+
{ algorithm: 'HS256' }
|
7
|
+
|
8
|
+
data.first.symbolize_keys
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize(hash)
|
12
|
+
JWT.encode hash, secret_key,'HS256'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiverecordCursorPagination
|
2
|
+
class AscendingOrder < OrderBase
|
3
|
+
def direction
|
4
|
+
:asc
|
5
|
+
end
|
6
|
+
|
7
|
+
def reverse
|
8
|
+
order = DescendingOrder.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,19 @@
|
|
1
|
+
module ActiverecordCursorPagination
|
2
|
+
class ClassFormatter
|
3
|
+
##
|
4
|
+
# Format the class name
|
5
|
+
#
|
6
|
+
# @param [String, Symbol, Class] klass_or_name
|
7
|
+
#
|
8
|
+
# @return [String, nil] The formatted class name
|
9
|
+
def format(klass_or_name)
|
10
|
+
if klass_or_name.nil? || klass_or_name.is_a?(String)
|
11
|
+
klass_or_name
|
12
|
+
elsif klass_or_name.is_a? Symbol
|
13
|
+
klass_or_name.to_s.camelcase
|
14
|
+
else
|
15
|
+
klass_or_name.name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ActiverecordCursorPagination
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :serializer, :secret_key
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
setup_defaults
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# Gets the secret key base for secure cursor implementations.
|
11
|
+
#
|
12
|
+
# If Rails is defined, +secret_key+ will try to find the implementation of the default +secret_key_base+
|
13
|
+
# in the application.
|
14
|
+
#
|
15
|
+
# @raise [NoSecretKeyError] If no key is set or found.
|
16
|
+
#
|
17
|
+
# @return [String] The secret key.
|
18
|
+
def secret_key
|
19
|
+
raise NoSecretKeyError, 'No secret key is defined' if @secret_key.nil? || @secret_key.empty?
|
20
|
+
@secret_key
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Get an instance of the serializer
|
25
|
+
#
|
26
|
+
# @return [Serializer]
|
27
|
+
def serializer_instance
|
28
|
+
serializer.new
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def setup_defaults
|
34
|
+
@secret_key = find_secret_key
|
35
|
+
@serializer = SecureCursorSerializer
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_secret_key
|
39
|
+
return nil unless defined?(Rails) && Rails.respond_to?(:application)
|
40
|
+
|
41
|
+
finder = SecretKeyFinder.new
|
42
|
+
finder.find_in Rails.application
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module ActiverecordCursorPagination
|
2
|
+
class Cursor
|
3
|
+
attr_reader :klass_name, :signed_sql, :per_page, :start_id, :end_id
|
4
|
+
|
5
|
+
##
|
6
|
+
# Initialize a cursor
|
7
|
+
#
|
8
|
+
# @param [Class, String] klass_or_name The model class
|
9
|
+
# @param [ActiveRecord::Relation, String] sql_or_signed_sql The active record SQL relation
|
10
|
+
# @param [Integer] per_page The number of records per page
|
11
|
+
# @param [Integer] start_id The ID of the first record in the page
|
12
|
+
# @param [Integer] end_id The ID of the last record in the page
|
13
|
+
def initialize(klass_or_name, sql_or_signed_sql, per_page, start_id, end_id)
|
14
|
+
@signed_sql = sql_or_signed_sql.is_a?(String) ? sql_or_signed_sql : sql_signer.sign(sql_or_signed_sql)
|
15
|
+
@klass_name = class_formatter.format klass_or_name
|
16
|
+
@per_page = per_page
|
17
|
+
@start_id = start_id
|
18
|
+
@end_id = end_id
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Is the cursor not empty
|
23
|
+
#
|
24
|
+
# @return [Boolean]
|
25
|
+
def present?
|
26
|
+
!empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Is the cursor empty
|
31
|
+
#
|
32
|
+
# @return [Boolean]
|
33
|
+
def empty?
|
34
|
+
@start_id.nil? || @end_id.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Gets the hash representation of the cursor
|
39
|
+
#
|
40
|
+
# @return [Hash]
|
41
|
+
def to_hash
|
42
|
+
{
|
43
|
+
start: @start_id,
|
44
|
+
end: @end_id,
|
45
|
+
per_page: @per_page,
|
46
|
+
model: @klass_name,
|
47
|
+
sql: @signed_sql
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Get the string representation of the cursor
|
53
|
+
#
|
54
|
+
# @return [String] The serialized cursor
|
55
|
+
def to_s
|
56
|
+
serializer.serialize to_hash
|
57
|
+
end
|
58
|
+
|
59
|
+
alias_method :to_param, :to_s
|
60
|
+
|
61
|
+
##
|
62
|
+
# Validates the cursor
|
63
|
+
#
|
64
|
+
# @param [Class] klass The model class
|
65
|
+
# @param [ActiveRecord::Relation] sql The active record SQL relation
|
66
|
+
# @param [Integer] per_page The number of records per page
|
67
|
+
#
|
68
|
+
# @raise [InvalidCursorError] If cursor is not valid
|
69
|
+
def validate!(klass, sql, per_page)
|
70
|
+
raise InvalidCursorError.new('Invalid cursor', self) unless valid?(klass, sql, per_page)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
delegate :class_formatter, :sql_signer, :serializer, to: :class
|
76
|
+
|
77
|
+
def valid?(klass, sql, per_page)
|
78
|
+
formatted_class = class_formatter.format klass
|
79
|
+
signed_sql = sql.is_a?(String) ? sql : sql_signer.sign(sql)
|
80
|
+
|
81
|
+
@klass_name === formatted_class &&
|
82
|
+
@signed_sql === signed_sql &&
|
83
|
+
@per_page === per_page
|
84
|
+
end
|
85
|
+
|
86
|
+
class << self
|
87
|
+
##
|
88
|
+
# Get sql signer instance
|
89
|
+
#
|
90
|
+
# @return [SqlSigner]
|
91
|
+
def sql_signer
|
92
|
+
SqlSigner.new
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Get class formatter
|
97
|
+
#
|
98
|
+
# @return [ClassFormatter]
|
99
|
+
def class_formatter
|
100
|
+
ClassFormatter.new
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Get cursor serializer instance
|
105
|
+
#
|
106
|
+
# @return [Serializer]
|
107
|
+
def serializer
|
108
|
+
ActiverecordCursorPagination.configuration.serializer_instance
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Parse the cursor string
|
113
|
+
#
|
114
|
+
# @param [String] str Cursor serialized string.
|
115
|
+
#
|
116
|
+
# @return [Cursor, EmptyCursor] Instance of Cursor.
|
117
|
+
def parse(str)
|
118
|
+
return EmptyCursor.new if str.nil? || str.empty?
|
119
|
+
|
120
|
+
hash = serializer.deserialize str
|
121
|
+
|
122
|
+
new hash[:model],
|
123
|
+
hash[:sql],
|
124
|
+
hash[:per_page],
|
125
|
+
hash[:start],
|
126
|
+
hash[:end]
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Serialize the cursor
|
131
|
+
#
|
132
|
+
# @param [Class, String] klass_or_name The model class
|
133
|
+
# @param [ActiveRecord::Relation, String] sql_or_signed_sql The active record SQL relation
|
134
|
+
# @param [Integer] per_page The number of records per page
|
135
|
+
# @param [Integer] start_id The ID of the first record in the page
|
136
|
+
# @param [Integer] end_id The ID of the last record in the page
|
137
|
+
#
|
138
|
+
# @return [String] The serialized cursor string
|
139
|
+
def to_param(klass_or_name, sql_or_signed_sql, per_page, start_id, end_id)
|
140
|
+
new(klass_or_name, sql_or_signed_sql, per_page, start_id, end_id).to_param
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|