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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19564a0a0f62ea77cf5876fc9d125f27474aba8fec598a6a84e6b215b13edb22
4
- data.tar.gz: e9856e7730229847a6d345c7b2cab49062b2351216fb02a775c1a4d3d8c19ca6
3
+ metadata.gz: 2b976c754e305df50a8c5eb4889bf98a6041a3e4c8fa73b7c6d6a4566068037a
4
+ data.tar.gz: 680ed7b82dd56e0c1a72db977350b701bdd9c90666053e7399f9dd9d280afae6
5
5
  SHA512:
6
- metadata.gz: c899b2ea56ffd2a65edabbbd06421bc695263555488b8068cf0c753d4cf84e6ca1c57837b339ed7237d47ab8d200c6242d52ded55ffc991c3df784a0c0056ec6
7
- data.tar.gz: 3f037675aa41403536d136e99faa7c08ec0184c5756fd91cabb88e6d72bdbec3f2be3cc1cbe83ab8efa2538218d35121e05c8c312e0683c26332ad16cbb8bc8c
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class CursorPaginator
5
- VERSION = '0.3.0'
5
+ VERSION = '0.3.2'
6
6
  end
7
7
  end
@@ -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 {|field| { field.keys.first => record[field.keys.first] } }
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.0
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