Skip to content

Commit

Permalink
Change cursor encoding to url safe base64 (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
JProgrammer committed May 20, 2024
1 parent 7ad9771 commit 742180a
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## master (unreleased)

- Change cursor encoding to url safe base64
- Fix `next_cursor`/`previous_cursor` for empty pages
- Fix iterating using only a timestamp column

Expand Down
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ page = paginator.fetch
page.records # => [#<Post:0x00007fd7071b2ea8 @id=1>, #<Post:0x00007fd7071bb738 @id=2>, ..., #<Post:0x00007fd707238260 @id=10>]
page.count # => 10
page.empty? # => false
page.cursors # => ["MQ==", "Mg==", ..., "MTA="]
| |
| |
page.previous_cursor # => "MQ==" |
page.next_cursor # => "MTA=" ------------------|
page.cursors # => ["MQ", "Mg", ..., "MTA"]
| |
| |
page.previous_cursor # => "MQ" |
page.next_cursor # => "MTA" -------------|
page.has_previous? # => false
page.has_next? # => true
```
Expand All @@ -63,24 +63,24 @@ Take a look at the next section _"Ordering"_ to see how you can have an order di
To then get the next result page, you simply need to pass the last cursor of the returned page item via:

```ruby
paginator = posts.cursor_paginate(after: "MTA=")
paginator = posts.cursor_paginate(after: "MTA")
```

This will then fetch the next result page.
You can also just as easily paginate to previous pages by using `before` instead of `after` and using the first cursor of the current page.

```ruby
paginator = posts.cursor_paginate(before: "MQ==")
paginator = posts.cursor_paginate(before: "MQ")
```

By default, this will always return up to 10 results. But you can also specify how many records should be returned via `limit` parameter.

```ruby
paginator = posts.cursor_paginate(after: "MTA=", limit: 2)
paginator = posts.cursor_paginate(after: "MTA", limit: 2)
```

```ruby
paginator = posts.cursor_paginate(before: "MQ==", limit: 2)
paginator = posts.cursor_paginate(before: "MQ", limit: 2)
```

You can also easily iterate over the whole relation:
Expand Down Expand Up @@ -255,11 +255,11 @@ no custom order is defined, each item in the returned collection will have a
cursor that only encodes the record's ID.

If we want to now request the next page, we can pass in the cursor of record
#2 which would be `"Mg=="` (can get via `page.cursor`). So now we can request
#2 which would be `"Mg"` (can get via `page.cursor`). So now we can request
the next page by calling:

```ruby
paginator = relation.cursor_paginate(limit: 2, after: "Mg==")
paginator = relation.cursor_paginate(limit: 2, after: "Mg")
page = paginator.fetch
```

Expand All @@ -275,10 +275,10 @@ LIMIT 2

Which would return posts #3 and #4. If we now want to paginate back, we can
request the posts that came before the first post, whose cursor would be
`"Mw=="` (can get via `page.previous_cursor`):
`"Mw"` (can get via `page.previous_cursor`):

```ruby
paginator = relation.cursor_paginate(limit: 2, before: "Mw==")
paginator = relation.cursor_paginate(limit: 2, before: "Mw")
page = paginator.fetch
```

Expand Down Expand Up @@ -331,12 +331,12 @@ data, the first record being the custom order column followed by the
record's ID.

Therefore, the cursor of record #4 will encode `['Jane', 4]`, which yields
this cursor: `"WyJKYW5lIiw0XQ=="`.
this cursor: `"WyJKYW5lIiw0XQ"`.

If we now want to request the next page via:

```ruby
paginator = relation.cursor_paginate(order: :author, limit: 2, after: "WyJKYW5lIiw0XQ==")
paginator = relation.cursor_paginate(order: :author, limit: 2, after: "WyJKYW5lIiw0XQ")
page = paginator.fetch
```

Expand Down
4 changes: 2 additions & 2 deletions lib/activerecord_cursor_paginate/cursor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def from_record(record, columns:)
end

def decode(cursor_string:, columns:)
decoded = JSON.parse(Base64.strict_decode64(cursor_string))
decoded = JSON.parse(Base64.urlsafe_decode64(cursor_string))

if (columns.size == 1 && decoded.is_a?(Array)) ||
(decoded.is_a?(Array) && decoded.size != columns.size)
Expand Down Expand Up @@ -64,7 +64,7 @@ def encode
end
end
unencoded_cursor = (serialized_values.size == 1 ? serialized_values.first : serialized_values)
Base64.strict_encode64(unencoded_cursor.to_json)
Base64.urlsafe_encode64(unencoded_cursor.to_json, padding: false)
end

TIMESTAMP_PREFIX = "0aIX2_" # something random
Expand Down
2 changes: 1 addition & 1 deletion lib/activerecord_cursor_paginate/extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Extension
# @see ActiveRecordCursorPaginate::Paginator#initialize
#
# @example
# paginator = Post.cursor_paginate(limit: 2, after: "Mg==")
# paginator = Post.cursor_paginate(limit: 2, after: "Mg")
# page = paginator.fetch
#
def cursor_paginate(after: nil, before: nil, limit: nil, order: nil, append_primary_key: true)
Expand Down
4 changes: 2 additions & 2 deletions lib/activerecord_cursor_paginate/paginator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ module ActiveRecordCursorPaginate
#
# @example Iterating one page at a time
# ActiveRecordCursorPaginate::Paginator
# .new(relation, order: :author, limit: 2, after: "WyJKYW5lIiw0XQ==")
# .new(relation, order: :author, limit: 2, after: "WyJKYW5lIiw0XQ")
# .fetch
#
# @example Iterating over the whole relation
# paginator = ActiveRecordCursorPaginate::Paginator
# .new(relation, order: :author, limit: 2, after: "WyJKYW5lIiw0XQ==")
# .new(relation, order: :author, limit: 2, after: "WyJKYW5lIiw0XQ")
#
# # Will lazily iterate over the pages.
# paginator.pages.each do |page|
Expand Down

0 comments on commit 742180a

Please sign in to comment.