healthie/activerecord_cursor_paginate

Deserialising cursor fails when based solely on a timestamp column

scottmatthewman opened this issue · 0 comments

I have a potentially large table whose ID will be a UUID (so I'm using the master branch of activerecord_cursor_paginate to make use of append_primary_key: false so that the cursor is based solely on created_at.

In my API's index method, I call

paginator = SubmittedUrl.cursor_paginate(
  order: { created_at: :desc },
  after: params[:after],
  append_primary_key: false
)
page = paginator.fetch

The first page of results is fine, and it correctly gives me the page.next_cursor for me to include in the response. However, when I pass that cursor into another request as my :after param, everything explodes.

ActiveRecordCursorPaginate::InvalidCursorError:
  The given cursor `IjBhSVgyXzE3MTU1MTc0OTA1ODM0MTYi` could not be decoded
# /usr/local/bundle/bundler/gems/activerecord_cursor_paginate-0c262804e436/lib/activerecord_cursor_paginate/cursor.rb:34:in `rescue in decode'
# /usr/local/bundle/bundler/gems/activerecord_cursor_paginate-0c262804e436/lib/activerecord_cursor_paginate/cursor.rb:16:in `decode'
# /usr/local/bundle/bundler/gems/activerecord_cursor_paginate-0c262804e436/lib/activerecord_cursor_paginate/paginator.rb:96:in `fetch'

Upon examination, the issue is in the initialize method of ActiveRecordCursorPaginate.

A cursor value of 0aIX2_1715517083528549 correctly decodes to 2024-05-12 12:31:23.528549 UTC, which is created as a Time object. But when a Time instance is passed into Array(args), Ruby splits the object into multiple components (using Time#to_a):

time = Time.now.utc
# => 2024-05-12 13:01:14.646513237 UTC
Array(time)
# => [14, 1, 13, 12, 5, 2024, 0, 133, false, "UTC"]

This means that the values and columns no longer match.

A remedy is to use [values].flatten instead, which keeps the Time object intact. This construction would be destructive for arrays of arrays (e.g., [[:a, :b], [:c, :d]] would resolve to [:a, :b, :c, :d]) but that shouldn't be a problem here.