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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f0bf9b4efedd42ab91e3ad4cc65ceddc323e14482d412e191cd5f999c4b7652
4
- data.tar.gz: '05907e8c002d776c4f06a7dae8352e058eab6790eaf509af22ba612c287c7369'
3
+ metadata.gz: 89a00cae4602011f6cf01aed6e09c6956d7eab352ec7b97c2d6ce712cfba972f
4
+ data.tar.gz: c8164ed441e5ed69dfe1874dcdea8337855b2a2cfc128cb481d013e3bb125a7c
5
5
  SHA512:
6
- metadata.gz: cbf06f52e6d3acb9be08eb0ec8e88a53d33000670783bb9841d5ece1a67b400478bee8ebbf3bf4a1f85c0f549b42834f5e760a740713a8b469833550495f0b2b
7
- data.tar.gz: 1ff2dd7da5bd5ed0e5d63eeee7a41b4d80b0f3ab17dc511843859b63b1b624373ba64c4a84df745cca08bcd043d24ac90d0eb62a75cb55fa7c3151979ccf8426
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
@@ -0,0 +1,7 @@
1
+ ## [0.2.0] - 2024-03-27
2
+
3
+ - Support aliases
4
+
5
+ ## [0.1.0] - 2023-09-21
6
+
7
+ - Initial release
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 toA this library separately
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 fllowing line to your `Gemfile` and execute `bundle install`
16
+ Add the following line to your `Gemfile` and execute `bundle install`
17
17
 
18
18
  ```
19
- gem 'active_record-cursor_paginator', git: 'https://github.com/ssugiyama/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(relarion, direction: :forward, cursor: '...', per_page: 10)
26
+ page = ActiveRecord::CursorPaginator.new(relation, direction: :forward, cursor: '...', per_page: 10)
27
27
  ```
28
28
 
29
- ### parameters of CursorPaginator
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
- ### response of CursorPaginator
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/activerecord-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/activerecord-cursor_paginator/blob/main/CODE_OF_CONDUCT.md).
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/activerecord-cursor_paginator/blob/main/CODE_OF_CONDUCT.md).
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).
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class CursorPaginator
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -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 order [Array, Hash, Symbol, nil]
24
- # Column to order by. If none is provided, will default to ID column.
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
- { o.expr.name => :asc }
49
- when Arel::Nodes::Descending # .order(id: :desc)
50
- { o.expr.name => :desc }
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
- # range では 〜より大きいということが表現できないのでarel_tableを使う
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
- relation = relation.where(relation.arel_table[col].send(:eq, val))
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
- relation.where(relation.arel_table[col].send(op, val))
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.1.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: 2023-09-20 00:00:00.000000000 Z
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