active_record-cursor_paginator 0.3.0 → 0.3.2
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +12 -0
- data/lib/active_record/cursor_paginator/version.rb +1 -1
- data/lib/active_record/cursor_paginator.rb +46 -3
- metadata +1 -2
- data/active_record-cursor_paginator.gemspec +0 -45
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2b976c754e305df50a8c5eb4889bf98a6041a3e4c8fa73b7c6d6a4566068037a
|
|
4
|
+
data.tar.gz: 680ed7b82dd56e0c1a72db977350b701bdd9c90666053e7399f9dd9d280afae6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7097e691c6a902bb42d0c2b8a7cb55a22d768b3d7bedefe344d05500efa6e665145d5e6ed8263bc3cbc313dd256bfab294a2651aa6fc1b230c910a8bdabd7abb
|
|
7
|
+
data.tar.gz: e1508a1aed8100c86f8f389a16b35996a4d633fb371a0e7a918ab728ee7e236f57b22df4d9d7ab6a1b8fe9785294bd0c5cc804f2f6d0e712d2ef4e88b746de84
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [0.3.2] - 2026-06-03
|
|
2
|
+
|
|
3
|
+
- Fix timezone offset in datetime cursor values being ignored in WHERE clause
|
|
4
|
+
- Warn when a datetime cursor column is a join table alias, as type inference may be inaccurate
|
|
5
|
+
|
|
6
|
+
## [0.3.1] - 2026-03-06
|
|
7
|
+
|
|
8
|
+
- Add support for Rails enum columns
|
|
9
|
+
|
|
1
10
|
## [0.3.0] - 2025-12-10
|
|
2
11
|
|
|
3
12
|
- Fix ambiguous column error
|
data/README.md
CHANGED
|
@@ -65,6 +65,18 @@ ActiveSupport::JSON::Encoding.time_precision = 6
|
|
|
65
65
|
|
|
66
66
|
## Limitation
|
|
67
67
|
|
|
68
|
+
### Datetime cursor values on join table columns
|
|
69
|
+
|
|
70
|
+
When ordering by a datetime column from a joined table via an alias (e.g. `authors.created_at as author_created_at`), the column type is inferred from the root model. If the joined table's column type differs from the root model's column with the same name, timezone conversion in cursor values may be inaccurate.
|
|
71
|
+
|
|
72
|
+
A warning is logged when this situation is detected:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
[CursorPaginator] Cursor column 'author_created_at' resolves to 'authors.created_at'
|
|
76
|
+
(join column). Type is inferred from Post and may be inaccurate.
|
|
77
|
+
Timezone-aware datetime cursors on joined tables may not work correctly.
|
|
78
|
+
```
|
|
79
|
+
|
|
68
80
|
This library does not support the following order expressions
|
|
69
81
|
|
|
70
82
|
```ruby
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'active_record'
|
|
4
|
+
require 'set'
|
|
4
5
|
require_relative 'cursor_paginator/version'
|
|
5
6
|
|
|
6
7
|
module ActiveRecord
|
|
@@ -160,10 +161,23 @@ module ActiveRecord
|
|
|
160
161
|
# value from this other field as well as the records ID to resolve the order
|
|
161
162
|
# of duplicates in the non-ID field.
|
|
162
163
|
#
|
|
164
|
+
# For enum columns, we store the raw integer value instead of the string
|
|
165
|
+
# representation to ensure proper SQL comparison in WHERE clauses.
|
|
166
|
+
#
|
|
163
167
|
# @param record [ActiveRecord] Model instance for which we want the cursor
|
|
164
168
|
# @return [String]
|
|
165
169
|
def cursor_for_record(record)
|
|
166
|
-
unencoded_cursor = @fields.map
|
|
170
|
+
unencoded_cursor = @fields.map do |field|
|
|
171
|
+
field_name = field.keys.first
|
|
172
|
+
value = if record.class.defined_enums.has_key?(field_name.to_s)
|
|
173
|
+
# For enum columns, get the raw integer value from the database
|
|
174
|
+
record.read_attribute_before_type_cast(field_name)
|
|
175
|
+
else
|
|
176
|
+
# For regular columns, get the typed value
|
|
177
|
+
record[field_name]
|
|
178
|
+
end
|
|
179
|
+
{ field_name => value }
|
|
180
|
+
end
|
|
167
181
|
Base64.strict_encode64(unencoded_cursor.to_json)
|
|
168
182
|
end
|
|
169
183
|
|
|
@@ -255,14 +269,43 @@ module ActiveRecord
|
|
|
255
269
|
def build_filter_query(sorted_relation, op, current_field, prev_fields)
|
|
256
270
|
relation = sorted_relation
|
|
257
271
|
prev_fields.each do |col, val|
|
|
272
|
+
col_key = col
|
|
258
273
|
col = @aliases[col] if @aliases.has_key? col
|
|
259
274
|
col = qualify_field_if_needed(col)
|
|
260
|
-
relation = relation.where("#{col} = ?", val)
|
|
275
|
+
relation = relation.where("#{col} = ?", cast_cursor_value(col_key, val))
|
|
261
276
|
end
|
|
262
277
|
col, val = current_field
|
|
278
|
+
col_key = col
|
|
263
279
|
col = @aliases[col] if @aliases.has_key? col
|
|
264
280
|
col = qualify_field_if_needed(col)
|
|
265
|
-
relation.where("#{col} #{op} ?", val)
|
|
281
|
+
relation.where("#{col} #{op} ?", cast_cursor_value(col_key, val))
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def cast_cursor_value(col_name, val)
|
|
285
|
+
return val unless val.is_a?(String)
|
|
286
|
+
|
|
287
|
+
resolved_expr = @aliases[col_name.to_s]
|
|
288
|
+
resolved_col = (resolved_expr || col_name.to_s).split('.').last
|
|
289
|
+
type = @relation.klass.type_for_attribute(resolved_col)
|
|
290
|
+
|
|
291
|
+
return val unless %i[datetime time timestamp].include?(type.type)
|
|
292
|
+
|
|
293
|
+
warn_join_column_cast(col_name, resolved_expr) if resolved_expr&.match?(/\A\w+\.\w+\z/)
|
|
294
|
+
|
|
295
|
+
type.cast(val)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def warn_join_column_cast(alias_name, expression)
|
|
299
|
+
@_warned_join_casts ||= Set.new
|
|
300
|
+
return if @_warned_join_casts.include?(alias_name)
|
|
301
|
+
|
|
302
|
+
@_warned_join_casts << alias_name
|
|
303
|
+
|
|
304
|
+
msg = "[CursorPaginator] Cursor column '#{alias_name}' resolves to '#{expression}' " \
|
|
305
|
+
"(join column). Type is inferred from #{@relation.klass.name} and may be " \
|
|
306
|
+
'inaccurate. Timezone-aware datetime cursors on joined tables may not work correctly.'
|
|
307
|
+
logger = ActiveRecord::Base.logger
|
|
308
|
+
logger ? logger.warn(msg) : Kernel.warn(msg)
|
|
266
309
|
end
|
|
267
310
|
|
|
268
311
|
# parse aliases from select values
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_record-cursor_paginator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shinichi Sugiyama
|
|
@@ -42,7 +42,6 @@ files:
|
|
|
42
42
|
- Gemfile
|
|
43
43
|
- README.md
|
|
44
44
|
- Rakefile
|
|
45
|
-
- active_record-cursor_paginator.gemspec
|
|
46
45
|
- lib/active_record/cursor_paginator.rb
|
|
47
46
|
- lib/active_record/cursor_paginator/version.rb
|
|
48
47
|
homepage: https://github.com/ssugiyama/active_record-cursor_paginator
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'lib/active_record/cursor_paginator/version'
|
|
4
|
-
|
|
5
|
-
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name = 'active_record-cursor_paginator'
|
|
7
|
-
spec.version = ActiveRecord::CursorPaginator::VERSION
|
|
8
|
-
spec.authors = ['Shinichi Sugiyama']
|
|
9
|
-
spec.email = ['sugiyama-shinichi@kayac.com']
|
|
10
|
-
|
|
11
|
-
spec.summary = 'cursor pagination for ActiveRecord'
|
|
12
|
-
spec.description =
|
|
13
|
-
'This library is an implementation of cursor pagination for ActiveRecord relations ' \
|
|
14
|
-
'based on "https://github.com/xing/rails_cursor_pagination". ' \
|
|
15
|
-
'Additional features are: ' \
|
|
16
|
-
'- receives a relation with orders, and it is unnecessary to specify orders to this library separately.' \
|
|
17
|
-
'- supports bidirectional pagination.'
|
|
18
|
-
|
|
19
|
-
spec.homepage = 'https://github.com/ssugiyama/active_record-cursor_paginator'
|
|
20
|
-
spec.license = 'MIT'
|
|
21
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
|
|
22
|
-
|
|
23
|
-
spec.metadata['homepage_uri'] = spec.homepage
|
|
24
|
-
spec.metadata['source_code_uri'] = spec.homepage
|
|
25
|
-
spec.metadata['changelog_uri'] = "#{spec.homepage}/Changelog.md"
|
|
26
|
-
|
|
27
|
-
# Specify which files should be added to the gem when it is released.
|
|
28
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
29
|
-
spec.files = Dir.chdir(__dir__) do
|
|
30
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
|
31
|
-
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
spec.bindir = 'exe'
|
|
35
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) {|f| File.basename(f) }
|
|
36
|
-
spec.require_paths = ['lib']
|
|
37
|
-
|
|
38
|
-
# Uncomment to register a new dependency of your gem
|
|
39
|
-
# spec.add_dependency "example-gem", "~> 1.0"
|
|
40
|
-
spec.add_dependency 'activerecord', '>= 6.0'
|
|
41
|
-
|
|
42
|
-
# For more information and examples about making a new gem, check out our
|
|
43
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
|
44
|
-
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
45
|
-
end
|