active_record-cursor_paginator 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -39
- data/.ruby-version +1 -0
- data/CHANGELOG.md +7 -0
- data/README.md +49 -8
- data/lib/active_record/cursor_paginator/version.rb +1 -1
- data/lib/active_record/cursor_paginator.rb +45 -13
- metadata +4 -3
- data/Gemfile.lock +0 -88
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89a00cae4602011f6cf01aed6e09c6956d7eab352ec7b97c2d6ce712cfba972f
|
4
|
+
data.tar.gz: c8164ed441e5ed69dfe1874dcdea8337855b2a2cfc128cb481d013e3bb125a7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0df15ecf4719f1ac64b922193042511a03aa2fc642e0fb98d5f9154043c3c388347646e8e6e566a7418e33b650bfd8017dd55fe9fa276c43c42f7af8b148b1fe
|
7
|
+
data.tar.gz: 9fe08024a3cb033d6e32b07187f803e379b2c57735b27f4dfeb75621cc4da9672a36d23885038d79291773ce9eff90771591242477e26304b0e276a70372d00a
|
data/.rubocop.yml
CHANGED
@@ -8,6 +8,7 @@ inherit_mode:
|
|
8
8
|
|
9
9
|
# 自動生成されるものはチェック対象から除外する
|
10
10
|
AllCops:
|
11
|
+
TargetRubyVersion: 2.7
|
11
12
|
SuggestExtensions: false
|
12
13
|
NewCops: enable
|
13
14
|
Exclude:
|
@@ -17,17 +18,6 @@ AllCops:
|
|
17
18
|
- "vendor/**/*"
|
18
19
|
- "bin/*"
|
19
20
|
- "Gemfile*"
|
20
|
-
- "db/schema.rb"
|
21
|
-
- "db/seeds.rb"
|
22
|
-
- 'db/migrate/*_init_schema.rb' # squasher generated
|
23
|
-
- "frontend/**/*"
|
24
|
-
- 'spec/fixtures/**/*'
|
25
|
-
- 'terraform/**/*'
|
26
|
-
- "app/lib/qwan_exception.rb"
|
27
|
-
- "app/controllers/test_data_controller.rb"
|
28
|
-
- "app/apis/api/v1/concerns/errors.rb"
|
29
|
-
- 'docker/**/*'
|
30
|
-
- '**/node_modules/**/*'
|
31
21
|
|
32
22
|
#################### Layout ################################
|
33
23
|
|
@@ -36,13 +26,6 @@ AllCops:
|
|
36
26
|
# のイメージ
|
37
27
|
Layout/LineLength:
|
38
28
|
Max: 160
|
39
|
-
Exclude:
|
40
|
-
- "db/migrate/*.rb"
|
41
|
-
- "app/lib/ngword.rb"
|
42
|
-
- "app/exceptions/qwan_exception/shop_remove_location_despite_has_ticket.rb"
|
43
|
-
- "spec/apis/api/v1/contact_spec.rb"
|
44
|
-
- "spec/ledger/report/tenant_transactions_spec.rb"
|
45
|
-
- "spec/ledger/report/transactions_spec.rb"
|
46
29
|
|
47
30
|
# ガード句と本質を分けるのは良いコードスタイルなので有効化
|
48
31
|
Layout/EmptyLineAfterGuardClause:
|
@@ -171,43 +154,22 @@ Metrics/BlockLength:
|
|
171
154
|
- "**/*.rake"
|
172
155
|
- "spec/**/*.rb"
|
173
156
|
- "Gemfile"
|
174
|
-
- "Guardfile"
|
175
|
-
- "config/environments/*.rb"
|
176
|
-
- "config/routes.rb"
|
177
|
-
- "config/routes/**/*.rb"
|
178
|
-
- "*.gemspec"
|
179
|
-
- "app/apis/**/*.rb"
|
180
|
-
- 'app/models/concerns/firebase_auth_concern.rb'
|
181
|
-
- 'app/models/concerns/region_location_report_concern.rb'
|
182
|
-
- 'db/migrate/*_init_schema.rb' # squasher generated
|
183
157
|
|
184
158
|
# 6 は強すぎるので緩める
|
185
159
|
Metrics/CyclomaticComplexity:
|
186
160
|
Max: 11
|
187
|
-
Exclude:
|
188
|
-
- "app/models/mission_master.rb"
|
189
161
|
|
190
162
|
Metrics/ClassLength:
|
191
163
|
Max: 500
|
192
164
|
CountAsOne: ['array', 'hash', 'heredoc']
|
193
|
-
Exclude:
|
194
|
-
- 'db/migrate/*_init_schema.rb' # squasher generated
|
195
165
|
|
196
166
|
# 20 行超えるのは migration ファイル以外滅多に無い
|
197
167
|
Metrics/MethodLength:
|
198
168
|
Max: 30
|
199
|
-
Exclude:
|
200
|
-
- "db/migrate/*.rb"
|
201
|
-
- "app/models/**/search.rb"
|
202
|
-
- "app/controllers/test_data_controller.rb"
|
203
|
-
- "app/models/mission_master.rb"
|
204
|
-
- "app/models/region_player/status.rb"
|
205
169
|
|
206
170
|
# 分岐の数。ガード句を多用しているとデフォルト 7 だと厳しい
|
207
171
|
Metrics/PerceivedComplexity:
|
208
172
|
Max: 10
|
209
|
-
Exclude:
|
210
|
-
- "app/models/mission_master.rb"
|
211
173
|
|
212
174
|
Metrics/ParameterLists:
|
213
175
|
Max: 8
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-3.3.0
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
This library is an implementation of cursor pagination for ActiveRecord relations based on "https://github.com/xing/rails_cursor_pagination.
|
4
4
|
|
5
5
|
Additional features are:
|
6
|
-
- receives a relation with orders, and it is unnecessary to specify orders
|
6
|
+
- receives a relation with orders, and it is unnecessary to specify orders to this library separately
|
7
7
|
- supports bidirectional pagination.
|
8
8
|
|
9
9
|
## Supported environment
|
@@ -13,25 +13,40 @@ Additional features are:
|
|
13
13
|
|
14
14
|
## Installation
|
15
15
|
|
16
|
-
Add the
|
16
|
+
Add the following line to your `Gemfile` and execute `bundle install`
|
17
17
|
|
18
18
|
```
|
19
|
-
gem 'active_record-cursor_paginator'
|
19
|
+
gem 'active_record-cursor_paginator'
|
20
20
|
```
|
21
21
|
|
22
22
|
## Usage
|
23
23
|
|
24
24
|
```ruby
|
25
25
|
relation = Post.order(...)
|
26
|
-
page = ActiveRecord::CursorPaginator.new(
|
26
|
+
page = ActiveRecord::CursorPaginator.new(relation, direction: :forward, cursor: '...', per_page: 10)
|
27
27
|
```
|
28
28
|
|
29
|
-
###
|
29
|
+
### aliases
|
30
|
+
|
31
|
+
This library supports column aliases as below, and extracts aliases from select values automatically.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
relation = Post.select('posts.*, authors.name author_name').joins(:author).order(author_name: :desc)
|
35
|
+
page = ActiveRecord::CursorPaginator.new(relation, direction: :forward, cursor: '...', per_page: 10)
|
36
|
+
```
|
37
|
+
|
38
|
+
Supported aliases are strings in the format below
|
39
|
+
|
40
|
+
- `some expression [as|AS] *alias*`
|
41
|
+
|
42
|
+
Other aliases such as symbols or arel functions are ignored.
|
43
|
+
|
44
|
+
### Parameters of CursorPaginator
|
30
45
|
- cursor: String - cursor to paginate
|
31
46
|
- per_page: Integer - record count per page (default to 10)
|
32
47
|
- direction: Symbol - direction to paginate. `:forward` (default) or `:backward`
|
33
48
|
|
34
|
-
###
|
49
|
+
### Response of CursorPaginator
|
35
50
|
|
36
51
|
- `page.records`: Array - records splitted per page (Notice: not ActiveRecord::Relation but Array)
|
37
52
|
- `page.start_cursor`: String - cursor of the first record. used for the backward paginate call.
|
@@ -40,6 +55,26 @@ page = ActiveRecord::CursorPaginator.new(relarion, direction: :forward, cursor:
|
|
40
55
|
- `page.next_page?`: Boolean - whether having the next page forward or not
|
41
56
|
- `page.previous_page?`: Boolean - whether having the next page backward or not
|
42
57
|
|
58
|
+
## Notice
|
59
|
+
|
60
|
+
You need to specify `ActiveSupport::JSON::Encoding.time_precision` to use time columns as order keys. It must be equals to the maximum precision of your order keys. Default precision of time columns in Rails is 6. So, please specify as follows in your initializers:
|
61
|
+
|
62
|
+
```
|
63
|
+
ActiveSupport::JSON::Encoding.time_precision = 6
|
64
|
+
```
|
65
|
+
|
66
|
+
## Limitation
|
67
|
+
|
68
|
+
This library does not support the following order expressions
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
Post.order(Arel::Nodes::NamedFunction.new('abs', [Post.arel_table[:display_index]])) # order by arel function
|
72
|
+
Post.order('abs(display_index)') # order by funtion
|
73
|
+
Post.order('abs(display_index) asc') # order by function and direction
|
74
|
+
```
|
75
|
+
|
76
|
+
Use aliases.
|
77
|
+
|
43
78
|
## Development
|
44
79
|
|
45
80
|
### Run test
|
@@ -49,9 +84,15 @@ ADAPTER=mysql bundle exec rspec
|
|
49
84
|
ADAPTER=postgresql bundle exec rspec
|
50
85
|
```
|
51
86
|
|
87
|
+
## ToDo
|
88
|
+
|
89
|
+
This library automatically appends `id` column to sorting and filtering columns, if it is not the last one.
|
90
|
+
This feature may cause unnecessary performance deterioration.
|
91
|
+
So, we plan this feature can be off.
|
92
|
+
|
52
93
|
## Contributing
|
53
94
|
|
54
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/ssugiyama/
|
95
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ssugiyama/active_record-cursor_paginator. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ssugiyama/active_record-cursor_paginator/blob/main/CODE_OF_CONDUCT.md).
|
55
96
|
|
56
97
|
## License
|
57
98
|
|
@@ -59,4 +100,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
59
100
|
|
60
101
|
## Code of Conduct
|
61
102
|
|
62
|
-
Everyone interacting in the Activerecord::CursorPagination project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ssugiyama/
|
103
|
+
Everyone interacting in the Activerecord::CursorPagination project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ssugiyama/active_record-cursor_paginator/blob/main/CODE_OF_CONDUCT.md).
|
@@ -20,8 +20,8 @@ module ActiveRecord
|
|
20
20
|
# Number of records to return.
|
21
21
|
# @param cursor [String, nil]
|
22
22
|
# Cursor to paginate
|
23
|
-
# @param
|
24
|
-
#
|
23
|
+
# @param direction [Symbol]
|
24
|
+
# :forward or :backward
|
25
25
|
def initialize(relation, per_page: nil, cursor: nil, direction: DIRECTION_FORWARD)
|
26
26
|
@is_forward_pagination = direction == DIRECTION_FORWARD
|
27
27
|
relation = relation.order(:id) if relation.order_values.empty?
|
@@ -31,7 +31,9 @@ module ActiveRecord
|
|
31
31
|
@relation = relation.reorder(@fields)
|
32
32
|
@cursor = cursor
|
33
33
|
@page_size = per_page
|
34
|
-
|
34
|
+
aliases = parse_aliases
|
35
|
+
aliases[:id] ||= "#{relation.table_name}.id"
|
36
|
+
@aliases = aliases.with_indifferent_access
|
35
37
|
@memos = {}
|
36
38
|
end
|
37
39
|
|
@@ -44,10 +46,11 @@ module ActiveRecord
|
|
44
46
|
case o
|
45
47
|
when Arel::Attribute # .order(arel_table[:id])
|
46
48
|
{ o.name => :asc }
|
47
|
-
when Arel::Nodes::Ascending # .order(id: :asc), .order(:id)
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
when Arel::Nodes::Ascending, # .order(id: :asc), .order(:id)
|
50
|
+
Arel::Nodes::Descending # .order(id: :desc)
|
51
|
+
key = o.expr.is_a?(Arel::Attributes::Attribute) ? o.expr.name : trim_quote(o.expr)
|
52
|
+
dir = o.direction
|
53
|
+
{ key => dir }
|
51
54
|
when String # .order('id desc')
|
52
55
|
o.split(',').map! do |s|
|
53
56
|
s.strip!
|
@@ -186,8 +189,8 @@ module ActiveRecord
|
|
186
189
|
relation = @relation
|
187
190
|
|
188
191
|
@fields.each do |field|
|
189
|
-
field = field.keys.first
|
190
|
-
relation = relation.select(field) unless @relation.select_values.include?(field)
|
192
|
+
field = trim_quote(field.keys.first)
|
193
|
+
relation = relation.select(field) unless @relation.select_values.include?(field) || @aliases.keys.include?(field)
|
191
194
|
end
|
192
195
|
|
193
196
|
relation
|
@@ -210,6 +213,7 @@ module ActiveRecord
|
|
210
213
|
next sorted_relation if @cursor.blank?
|
211
214
|
|
212
215
|
cursor = decoded_cursor
|
216
|
+
|
213
217
|
unless cursor.length == @fields.length && cursor.map {|field| field.keys.first } == @fields.map {|field| field.keys.first }
|
214
218
|
raise InvalidCursorError, 'The given cursor is mismatched with current query'
|
215
219
|
end
|
@@ -218,8 +222,7 @@ module ActiveRecord
|
|
218
222
|
relation = nil
|
219
223
|
cursor.zip(@fields).each do |cursor_field, field|
|
220
224
|
direction = field.values.first
|
221
|
-
|
222
|
-
op = (direction == :asc) ? :gt : :lt
|
225
|
+
op = (direction == :asc) ? '>' : '<'
|
223
226
|
current_field = [field.keys.first, cursor_field.values.first]
|
224
227
|
new_relation = build_filter_query(sorted_relation, op, current_field, prev_fields)
|
225
228
|
relation = relation.nil? ? new_relation : relation.or(new_relation)
|
@@ -252,10 +255,39 @@ module ActiveRecord
|
|
252
255
|
def build_filter_query(sorted_relation, op, current_field, prev_fields)
|
253
256
|
relation = sorted_relation
|
254
257
|
prev_fields.each do |col, val|
|
255
|
-
|
258
|
+
col = @aliases[col] if @aliases.has_key? col
|
259
|
+
relation = relation.where("#{col} = ?", val)
|
256
260
|
end
|
257
261
|
col, val = current_field
|
258
|
-
|
262
|
+
col = @aliases[col] if @aliases.has_key? col
|
263
|
+
relation.where("#{col} #{op} ?", val)
|
264
|
+
end
|
265
|
+
|
266
|
+
# parse aliases from select values
|
267
|
+
#
|
268
|
+
# @return [Hash]
|
269
|
+
def parse_aliases
|
270
|
+
aliases = {}
|
271
|
+
@relation.select_values.each do |select_value|
|
272
|
+
next unless select_value.is_a? String
|
273
|
+
|
274
|
+
select_value.split(',').each do |expr|
|
275
|
+
expr.strip!
|
276
|
+
# "value [AS|as] alias" という形式に対応する
|
277
|
+
# value: spaceがあってもOK. 最小マッチングのため '?' をつける
|
278
|
+
# 'as' は使わないのでキャプチャしない
|
279
|
+
match = expr.match(/^(?<value>.+?)\s+(?:as\s+)?(?<alias>\S+)$/i)
|
280
|
+
next if match.nil? || match.length < 3
|
281
|
+
|
282
|
+
key = trim_quote(match[:alias])
|
283
|
+
aliases[key] = match[:value]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
aliases
|
287
|
+
end
|
288
|
+
|
289
|
+
def trim_quote(field)
|
290
|
+
field.gsub(/^[`\"]/, '').gsub(/[`\"]$/, '')
|
259
291
|
end
|
260
292
|
end
|
261
293
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record-cursor_paginator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shinichi Sugiyama
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -37,9 +37,10 @@ files:
|
|
37
37
|
- ".rspec"
|
38
38
|
- ".rubocop.yml"
|
39
39
|
- ".rubocop_todo.yml"
|
40
|
+
- ".ruby-version"
|
41
|
+
- CHANGELOG.md
|
40
42
|
- CODE_OF_CONDUCT.md
|
41
43
|
- Gemfile
|
42
|
-
- Gemfile.lock
|
43
44
|
- README.md
|
44
45
|
- Rakefile
|
45
46
|
- lib/active_record/cursor_paginator.rb
|
data/Gemfile.lock
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
active_record-cursor_paginator (0.1.0)
|
5
|
-
activerecord (>= 6.0)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
activemodel (7.0.8)
|
11
|
-
activesupport (= 7.0.8)
|
12
|
-
activerecord (7.0.8)
|
13
|
-
activemodel (= 7.0.8)
|
14
|
-
activesupport (= 7.0.8)
|
15
|
-
activesupport (7.0.8)
|
16
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
|
-
i18n (>= 1.6, < 2)
|
18
|
-
minitest (>= 5.1)
|
19
|
-
tzinfo (~> 2.0)
|
20
|
-
ast (2.4.2)
|
21
|
-
base64 (0.1.1)
|
22
|
-
concurrent-ruby (1.2.2)
|
23
|
-
diff-lcs (1.5.0)
|
24
|
-
i18n (1.14.1)
|
25
|
-
concurrent-ruby (~> 1.0)
|
26
|
-
json (2.6.3)
|
27
|
-
language_server-protocol (3.17.0.3)
|
28
|
-
minitest (5.20.0)
|
29
|
-
mysql2 (0.5.5)
|
30
|
-
parallel (1.23.0)
|
31
|
-
parser (3.2.2.3)
|
32
|
-
ast (~> 2.4.1)
|
33
|
-
racc
|
34
|
-
pg (1.5.4)
|
35
|
-
racc (1.7.1)
|
36
|
-
rainbow (3.1.1)
|
37
|
-
rake (13.0.6)
|
38
|
-
regexp_parser (2.8.1)
|
39
|
-
rexml (3.2.6)
|
40
|
-
rspec (3.12.0)
|
41
|
-
rspec-core (~> 3.12.0)
|
42
|
-
rspec-expectations (~> 3.12.0)
|
43
|
-
rspec-mocks (~> 3.12.0)
|
44
|
-
rspec-core (3.12.2)
|
45
|
-
rspec-support (~> 3.12.0)
|
46
|
-
rspec-expectations (3.12.3)
|
47
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
-
rspec-support (~> 3.12.0)
|
49
|
-
rspec-mocks (3.12.6)
|
50
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
51
|
-
rspec-support (~> 3.12.0)
|
52
|
-
rspec-support (3.12.1)
|
53
|
-
rubocop (1.56.3)
|
54
|
-
base64 (~> 0.1.1)
|
55
|
-
json (~> 2.3)
|
56
|
-
language_server-protocol (>= 3.17.0)
|
57
|
-
parallel (~> 1.10)
|
58
|
-
parser (>= 3.2.2.3)
|
59
|
-
rainbow (>= 2.2.2, < 4.0)
|
60
|
-
regexp_parser (>= 1.8, < 3.0)
|
61
|
-
rexml (>= 3.2.5, < 4.0)
|
62
|
-
rubocop-ast (>= 1.28.1, < 2.0)
|
63
|
-
ruby-progressbar (~> 1.7)
|
64
|
-
unicode-display_width (>= 2.4.0, < 3.0)
|
65
|
-
rubocop-ast (1.29.0)
|
66
|
-
parser (>= 3.2.1.0)
|
67
|
-
ruby-progressbar (1.13.0)
|
68
|
-
temping (4.0.0)
|
69
|
-
activerecord (>= 5.2, < 7.1)
|
70
|
-
activesupport (>= 5.2, < 7.1)
|
71
|
-
tzinfo (2.0.6)
|
72
|
-
concurrent-ruby (~> 1.0)
|
73
|
-
unicode-display_width (2.4.2)
|
74
|
-
|
75
|
-
PLATFORMS
|
76
|
-
arm64-darwin-22
|
77
|
-
|
78
|
-
DEPENDENCIES
|
79
|
-
active_record-cursor_paginator!
|
80
|
-
mysql2 (~> 0.5)
|
81
|
-
pg (>= 1.2, < 2.0)
|
82
|
-
rake (~> 13.0)
|
83
|
-
rspec (~> 3.0)
|
84
|
-
rubocop (~> 1.21)
|
85
|
-
temping
|
86
|
-
|
87
|
-
BUNDLED WITH
|
88
|
-
2.4.10
|