Skip to content

Commit

Permalink
add support precision of DateTime selectors when encoding/decoding cu…
Browse files Browse the repository at this point in the history
…rsors add spec coverage for timestamp order_by selector in pagination fetch

Bump version to 0.3.1
  • Loading branch information
amandawraymond committed Jan 27, 2023
1 parent 2b30d11 commit 62a40df
Show file tree
Hide file tree
Showing 10 changed files with 1,165 additions and 419 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-18.04
strategy:
matrix:
ruby-version: ['2.6', '2.7', '3.0', '3.1']
ruby-version: ['2.7', '3.0', '3.1']
mysql-version: ['5.7', '8.0']

steps:
Expand All @@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
ruby-version: ['2.6', '2.7', '3.0', '3.1']
ruby-version: ['2.7', '3.0', '3.1']
postgres-version: [14, 13, 12]

steps:
Expand Down
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby-2.7.5
2.7.4
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ These are the latest changes on the project's `master` branch that have not yet
Follow the same format as previous releases by categorizing your feature into "Added", "Changed", "Deprecated", "Removed", "Fixed", or "Security".
--->

## [0.3.1] - 2023-01-13

### Fixed
- Ensure DateTime order_by fields will have expected paginated results by honoring of timestamps down to nanosecond on comparison.

## [0.3.0] - 2022-07-08

### Added
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ gem 'rubocop', '~> 1.31'
gem 'mysql2', '~> 0.5'

gem 'pg', '~> 1.4'

46 changes: 22 additions & 24 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,38 +1,37 @@
PATH
remote: .
specs:
rails_cursor_pagination (0.3.0)
rails_cursor_pagination (0.3.1)
activerecord (>= 5.0)

GEM
remote: https://rubygems.org/
specs:
activemodel (6.1.6)
activesupport (= 6.1.6)
activerecord (6.1.6)
activemodel (= 6.1.6)
activesupport (= 6.1.6)
activesupport (6.1.6)
activemodel (7.0.4.2)
activesupport (= 7.0.4.2)
activerecord (7.0.4.2)
activemodel (= 7.0.4.2)
activesupport (= 7.0.4.2)
activesupport (7.0.4.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
ast (2.4.2)
concurrent-ruby (1.1.10)
concurrent-ruby (1.2.0)
diff-lcs (1.5.0)
i18n (1.10.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
json (2.6.2)
minitest (5.15.0)
mysql2 (0.5.4)
json (2.6.3)
minitest (5.17.0)
mysql2 (0.5.5)
parallel (1.22.1)
parser (3.1.2.0)
parser (3.2.0.0)
ast (~> 2.4.1)
pg (1.4.1)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.5.0)
regexp_parser (2.6.2)
rexml (3.2.5)
rspec (3.11.0)
rspec-core (~> 3.11.0)
Expand All @@ -47,23 +46,22 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-support (3.11.0)
rubocop (1.31.2)
rubocop (1.44.1)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.0.0)
parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.18.0, < 2.0)
rubocop-ast (>= 1.24.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.18.0)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.24.1)
parser (>= 3.1.1.0)
ruby-progressbar (1.11.0)
tzinfo (2.0.4)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unicode-display_width (2.2.0)
zeitwerk (2.5.4)
unicode-display_width (2.4.2)

PLATFORMS
ruby
Expand All @@ -77,4 +75,4 @@ DEPENDENCIES
rubocop (~> 1.31)

BUNDLED WITH
2.1.4
2.2.3
40 changes: 32 additions & 8 deletions lib/rails_cursor_pagination/cursor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,35 @@ def decode(encoded_string:, order_field: :id)
end
new(id: decoded, order_field: :id)
else
unless decoded.is_a?(Array) && decoded.size == 2
raise InvalidCursorError,
"The given cursor `#{encoded_string}` was decoded as " \
"`#{decoded}` but could not be parsed"
end
new(id: decoded[1], order_field: order_field,
order_field_value: decoded[0])
decode_custom_order_field(encoded_string: encoded_string,
decoded: decoded, order_field: order_field)
end
rescue ArgumentError, JSON::ParserError
raise InvalidCursorError,
"The given cursor `#{encoded_string}` could not be decoded"
end

def decode_custom_order_field(encoded_string:, decoded:, order_field:)
unless decoded.is_a?(Array) && decoded.size == 2
raise InvalidCursorError,
"The given cursor `#{encoded_string}` was decoded as " \
"`#{decoded}` but could not be parsed"
end
if decoded[0].is_a?(Hash) && ['seconds', 'nanoseconds'].all? { |key| decoded[0].key? key }
new(
id: decoded[1],
order_field: order_field,
order_field_value: Time.at(
decoded[0]['seconds'],
decoded[0]['nanoseconds'],
:nsec
)
)
else
new(id: decoded[1], order_field: order_field,
order_field_value: decoded[0])
end
end
end

# Initializes the record
Expand Down Expand Up @@ -89,7 +106,14 @@ def initialize(id:, order_field: :id, order_field_value: nil)
def encode
unencoded_cursor =
if custom_order_field?
[@order_field_value, @id]
if @order_field_value.respond_to?(:strftime)
[{
seconds: order_field_value.to_i,
nanoseconds: order_field_value.nsec
}, @id]
else
[@order_field_value, @id]
end
else
@id
end
Expand Down
4 changes: 2 additions & 2 deletions lib/rails_cursor_pagination/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module RailsCursorPagination
VERSION = '0.3.0'
end
VERSION = '0.3.1'
end
51 changes: 51 additions & 0 deletions spec/rails_cursor_pagination/cursor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@
expect(decoded.order_field_value).to eq record.author
end
end

context 'when ordering by created_at' do
subject(:encoded) do
described_class.from_record(record: record, order_field: :created_at).encode
end

it 'produces a valid string' do
expect(encoded).to be_a(String)
end

it 'can be decoded back to the originally encoded value' do
decoded = described_class.decode(encoded_string: encoded,
order_field: :created_at)
expect(decoded.id).to eq record.id
expect(decoded.order_field_value).to eq record.created_at
end
end
end

describe '.from_record' do
Expand Down Expand Up @@ -156,6 +173,40 @@
end
end

context 'when decoding an encoded message with order_field :created_at' do
let(:record) { Post.create! id: 1, author: 'John', content: 'Post 1' }
let(:encoded) do
described_class.from_record(record: record, order_field: :created_at).encode
end

context 'and the order_field to decode is set to :id' do
subject(:decoded) do
described_class.decode(encoded_string: encoded)
end

it 'raises an InvalidCursorError' do
cursor = [{"seconds"=> record.created_at.to_i, "nanoseconds"=> record.created_at.nsec}, record.id]
msg = %Q|The given cursor `#{encoded}` was decoded as `#{cursor}` but could not be parsed|
expect { decoded }.to raise_error(
::RailsCursorPagination::InvalidCursorError,
msg
)
end
end

context 'and the order_field to decode is set to :created_at' do
subject(:decoded) do
described_class.decode(encoded_string: encoded, order_field: :created_at)
end

it 'decodes the string succesfully' do
expect(decoded.id).to eq record.id
expect(decoded.order_field_value).to eq record.created_at
expect(decoded.order_field_value.nsec).to eq record.created_at.nsec
end
end
end

context 'when decoding a message that did not come from a known encoder' do
let(:encoded) { 'SomeGarbageString' }

Expand Down
Loading

0 comments on commit 62a40df

Please sign in to comment.