Skip to content

Refactor cursors to inherit from BaseCursor, scroll with a Mongoid::Scroll::Base64EncodedCursor #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 7, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -7,8 +7,6 @@ jobs:
strategy:
matrix:
entry:
- { ruby: '2.6', mongodb: '4.4', mongoid: '3' }
- { ruby: '2.6', mongodb: '4.4', mongoid: '4' }
- { ruby: '2.6', mongodb: '4.4', mongoid: '5' }
- { ruby: '2.7', mongodb: '4.4', mongoid: '6' }
- { ruby: '2.7', mongodb: '4.4', mongoid: '7' }
38 changes: 19 additions & 19 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,66 +1,67 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2023-03-03 23:41:38 +0100 using RuboCop version 0.49.1.
# on 2023-03-07 08:57:35 -0500 using RuboCop version 0.49.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 6
# Offense count: 4
# Configuration parameters: Include.
# Include: **/Gemfile, **/gems.rb
Bundler/DuplicatedGem:
Exclude:
- 'Gemfile'

# Offense count: 6
# Offense count: 8
Metrics/AbcSize:
Max: 69
Max: 38

# Offense count: 16
# Offense count: 18
# Configuration parameters: CountComments, ExcludedMethods.
Metrics/BlockLength:
Max: 251
Max: 258

# Offense count: 1
# Configuration parameters: CountComments.
Metrics/ClassLength:
Max: 105
Max: 103

# Offense count: 6
# Offense count: 5
Metrics/CyclomaticComplexity:
Max: 11
Max: 13

# Offense count: 155
# Offense count: 146
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 202
Max: 252

# Offense count: 6
# Offense count: 7
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 26
Max: 23

# Offense count: 4
Metrics/PerceivedComplexity:
Max: 12

# Offense count: 10
# Offense count: 11
Style/Documentation:
Exclude:
- 'spec/**/*'
- 'test/**/*'
- 'examples/mongoid_scroll_feed.rb'
- 'lib/mongo/scrollable.rb'
- 'lib/mongoid/criteria/scrollable.rb'
- 'lib/mongoid/scroll/base_cursor.rb'
- 'lib/mongoid/scroll/cursor.rb'
- 'lib/mongoid/scroll/errors/base.rb'
- 'lib/mongoid/scroll/errors/invalid_base64_cursor_error.rb'
- 'lib/mongoid/scroll/errors/invalid_cursor_error.rb'
- 'lib/mongoid/scroll/errors/multiple_sort_fields_error.rb'
- 'lib/mongoid/scroll/errors/no_such_field_error.rb'
- 'lib/mongoid/scroll/errors/unsupported_field_type_error.rb'
- 'lib/moped/scrollable.rb'

# Offense count: 1
# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
@@ -70,12 +71,11 @@ Style/FileName:
- 'lib/mongoid-scroll.rb'

# Offense count: 1
# Configuration parameters: MinBodyLength.
Style/GuardClause:
Style/MultilineTernaryOperator:
Exclude:
- 'lib/moped/scrollable.rb'
- 'lib/mongoid/scroll/cursor.rb'

# Offense count: 1
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: SupportedStyles.
# SupportedStyles: compact, exploded
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
* [#29](https://github.com/mongoid/mongoid-scroll/pull/29): Add ability to include the current record to the cursor - [@FabienChaynes](https://github.com/FabienChaynes).
* [#30](https://github.com/mongoid/mongoid-scroll/pull/30): Prevent discrepancy between the original sort and the cursor sort - [@FabienChaynes](https://github.com/FabienChaynes).
* [#32](https://github.com/mongoid/mongoid-scroll/pull/32): Add Base64 serialization for cursors - [@FabienChaynes](https://github.com/FabienChaynes).
* [#33](https://github.com/mongoid/mongoid-scroll/pull/33): Removed support for Mongoid 3, 4 and Moped - [@dblock](https://github.com/dblock).
* Your contribution here.

### 0.3.7 (2021/06/01)
8 changes: 4 additions & 4 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -6,10 +6,10 @@ case version = ENV['MONGOID_VERSION'] || '~> 7.0'
when 'HEAD' then gem 'mongoid', github: 'mongodb/mongoid'
when /7/ then gem 'mongoid', '~> 7.0'
when /6/ then gem 'mongoid', '~> 6.0'
when /5/ then gem 'mongoid', '~> 5.0'
when /4/ then gem 'mongoid', '~> 4.0'
when /3/ then gem 'mongoid', '~> 3.1'
else gem 'mongoid', version
when /5/ then
gem 'bigdecimal', '1.3.5'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gem 'mongoid', '~> 5.0'
else gem 'mongoid', version
end

group :development, :test do
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2013-2015 Daniel Doubrovkine, Artsy Inc.
Copyright (c) 2013-2023 Daniel Doubrovkine, Artsy Inc.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
97 changes: 36 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
Mongoid::Scroll
===============
- [Mongoid::Scroll](#mongoidscroll)
- [Compatibility](#compatibility)
- [Demo](#demo)
- [The Problem](#the-problem)
- [Installation](#installation)
- [Usage](#usage)
- [Mongoid](#mongoid)
- [Mongo-Ruby-Driver (Mongoid 5)](#mongo-ruby-driver-mongoid-5)
- [Indexes and Performance](#indexes-and-performance)
- [Cursors](#cursors)
- [Standard Cursor](#standard-cursor)
- [Base64 Encoded Cursor](#base64-encoded-cursor)
- [Contributing](#contributing)
- [Copyright and License](#copyright-and-license)

# Mongoid::Scroll

[![Gem Version](https://badge.fury.io/rb/mongoid-scroll.svg)](https://badge.fury.io/rb/mongoid-scroll)
[![Build Status](https://github.com/mongoid/mongoid-scroll/actions/workflows/ci.yml/badge.svg)](https://github.com/mongoid/mongoid-scroll/actions/workflows/ci.yml)
[![Dependency Status](https://gemnasium.com/mongoid/mongoid-scroll.svg)](https://gemnasium.com/mongoid/mongoid-scroll)
[![Code Climate](https://codeclimate.com/github/mongoid/mongoid-scroll.svg)](https://codeclimate.com/github/mongoid/mongoid-scroll)

Mongoid extension that enables infinite scrolling for `Mongoid::Criteria`, `Moped::Query` and `Mongo::Collection::View`.
Mongoid extension that enables infinite scrolling for `Mongoid::Criteria` and `Mongo::Collection::View`.

Compatibility
-------------
## Compatibility

This gem supports Mongoid 3, 4, 5, 6, 7, Moped and Mongo-Ruby-Driver.
This gem supports Mongoid 5, 6, and 7.

Demo
----
## Demo

Check out [shows on artsy.net](http://artsy.net/shows). Keep scrolling down.

There're also two code samples for Mongoid and Moped in [examples](examples). Run `bundle exec ruby examples/mongoid_scroll_feed.rb`.
There're also two code samples for Mongoid in [examples](examples). Run `bundle exec ruby examples/mongoid_scroll_feed.rb`.

The Problem
-----------
## The Problem

Traditional pagination does not work when data changes between paginated requests, which makes it unsuitable for infinite scroll behaviors.

@@ -30,17 +41,15 @@ Traditional pagination does not work when data changes between paginated request

The solution implemented by the `scroll` extension paginates data using a cursor, giving you the ability to restart pagination where you left it off. This is a non-trivial problem when combined with sorting over non-unique record fields, such as timestamps.

Installation
------------
## Installation

Add the gem to your Gemfile and run `bundle install`.

```ruby
gem 'mongoid-scroll'
```

Usage
-----
## Usage

### Mongoid

@@ -84,27 +93,6 @@ Feed::Item.desc(:position).scroll(saved_cursor) do |record, next_cursor|
end
```

### Moped (Mongoid 3 and 4)

Scroll a `Moped::Query` and save a cursor to the last item. You must also supply a `field_type` of the sort criteria.

```ruby
saved_cursor = nil
session[:feed_items].find.sort(position: -1).limit(5).scroll(nil, { field_type: DateTime }) do |record, next_cursor|
# each record, one-by-one
saved_cursor = next_cursor
end
```

Resume iterating using the previously saved cursor.

```ruby
session[:feed_items].find.sort(position: -1).limit(5).scroll(saved_cursor, { field_type: DateTime }) do |record, next_cursor|
# each record, one-by-one
saved_cursor = next_cursor
end
```

### Mongo-Ruby-Driver (Mongoid 5)

Scroll a `Mongo::Collection::View` and save a cursor to the last item. You must also supply a `field_type` of the sort criteria.
@@ -126,8 +114,7 @@ session[:feed_items].find.sort(position: -1).limit(5).scroll(saved_cursor, { fie
end
```

Indexes and Performance
-----------------------
## Indexes and Performance

A query without a cursor is identical to a query without a scroll.

@@ -159,8 +146,7 @@ module Feed
end
```

Cursors
-------
## Cursors

You can use `Mongoid::Scroll::Cursor.from_record` to generate a cursor. A cursor points at the last record of the previous iteration and unlike MongoDB cursors will not expire.

@@ -191,37 +177,26 @@ cursor = Mongoid::Scroll::Cursor.from_record(record, { field_type: DateTime, fie
Feed::Item.desc(:created_at).scroll(cursor) # Raises a Mongoid::Scroll::Errors::MismatchedSortFieldsError
```

### Base64 encoded cursors
### Standard Cursor

`Mongoid::Scroll::Base64EncodedCursor` can be used to generate a Base64 encoded string (using RFC 4648) containing all the information needed to rebuild a cursor.
The `Mongoid::Scroll::Cursor` encodes a value and a tiebreak ID separated by `:`, and does not include other options, such as scroll direction. Take extra care not to pass a cursor into a scroll with different options.

A `Mongoid::Scroll::Cursor` can be mutated into a `Mongoid::Scroll::Base64EncodedCursor` by using the `Mongoid::Scroll::Base64EncodedCursor.from_cursor` method:
### Base64 Encoded Cursor

```ruby
saved_cursor = nil
Feed::Item.desc(:position).limit(5).scroll do |record, next_cursor|
# each record, one-by-one
saved_cursor = next_cursor
end
base64_cursor = Mongoid::Scroll::Base64EncodedCursor.from_cursor(saved_cursor)
```

The `Mongoid::Scroll::Base64EncodedCursor#to_s` method will return the cursor encoded as a Base64 string, which you'll be able to use subsequently to rebuild the cursor using `Mongoid::Scroll::Base64EncodedCursor.new`. In this case, you won't have to pass the `:field`, `:field_type` or `:field_name` options:
The `Mongoid::Scroll::Base64EncodedCursor` can be used instead of `Mongoid::Scroll::Cursor` to generate a base64-encoded string (using RFC 4648) containing all the information needed to rebuild a cursor.

```ruby
base64_cursor = Mongoid::Scroll::Base64EncodedCursor.from_cursor(saved_cursor)
base64_string = base64_cursor.to_s
Mongoid::Scroll::Base64EncodedCursor.new(base64_string)
Feed::Item.desc(:position).limit(5).scroll(Mongoid::Scroll::Base64EncodedCursor) do |record, next_cursor|
# next_cursor is of type Mongoid::Scroll::Base64EncodedCursor
end
```

Contributing
------------
## Contributing

Fork the project. Make your feature addition or bug fix with tests. Send a pull request. Bonus points for topic branches.

Copyright and License
---------------------
## Copyright and License

MIT License, see [LICENSE](http://github.com/mongoid/mongoid-scroll/raw/master/LICENSE.md) for details.

(c) 2013-2015 [Daniel Doubrovkine](http://github.com/dblock), based on code by [Frank Macreery](http://github.com/macreery), [Artsy Inc.](http://artsy.net)
(c) 2013-2023 [Daniel Doubrovkine](http://github.com/dblock), based on code by [Frank Macreery](http://github.com/macreery), [Artsy Inc.](http://artsy.net)
15 changes: 4 additions & 11 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -4,18 +4,11 @@

### Mismatched Sort Fields

`Mongoid::Criteria::Scrollable#scroll`, `Moped::Scrollable` and `Mongo::Scrollable` now raise a `Mongoid::Scroll::Errors::MismatchedSortFieldsError` when there are discrepancies between the cursor sort options and the original sort options.
Make sure to avoid this case or to handle the new exception.
Both `Mongoid::Criteria::Scrollable#scroll` and `Mongo::Scrollable` now raise a `Mongoid::Scroll::Errors::MismatchedSortFieldsError` when there are discrepancies between the cursor sort options and the original sort options.

```ruby
cursor.field_name = "position" # Avoid this, it'll raise because on the following line the sort is by created_at
Feed::Item.desc(:created_at).scroll(cursor)
```
For example, the following code will now raise a `MismatchedSortFieldsError` because we set a different field name (`position`) from the `created_at` field used to sort in `scroll`.

```ruby
begin
Feed::Item.desc(:created_at).scroll(cursor)
rescue Mongoid::Scroll::Errors::MismatchedSortFieldsError
# If cursor can be modified externally, handle the exception
end
cursor.field_name = "position"
Feed::Item.desc(:created_at).scroll(cursor)
```
10 changes: 2 additions & 8 deletions examples/mongoid_scroll_feed.rb
Original file line number Diff line number Diff line change
@@ -4,14 +4,8 @@
require 'mongoid-scroll'
require 'faker'

if defined?(Moped)
Moped.logger = Logger.new($stdout)
Moped.logger.level = Logger::DEBUG
else
Mongoid.logger.level = Logger::INFO
Mongo::Logger.logger.level = Logger::INFO if Mongoid::Compatibility::Version.mongoid5?
end

Mongoid.logger.level = Logger::INFO
Mongo::Logger.logger.level = Logger::INFO if Mongoid::Compatibility::Version.mongoid5?
Mongoid.connect_to 'mongoid_scroll_demo'
Mongoid.purge!

45 changes: 0 additions & 45 deletions examples/moped_scroll_feed.rb

This file was deleted.

Loading