mongoid-scroll 0.3.7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +36 -0
  3. data/.github/workflows/danger.yml +17 -0
  4. data/.rubocop_todo.yml +24 -19
  5. data/CHANGELOG.md +10 -0
  6. data/Gemfile +6 -5
  7. data/LICENSE.md +1 -1
  8. data/README.md +59 -46
  9. data/RELEASING.md +1 -1
  10. data/UPGRADING.md +14 -0
  11. data/examples/mongoid_scroll_feed.rb +2 -8
  12. data/lib/config/locales/en.yml +8 -0
  13. data/lib/mongo/scrollable.rb +8 -3
  14. data/lib/mongoid/criteria/scrollable/cursors.rb +18 -0
  15. data/lib/mongoid/criteria/scrollable/fields.rb +21 -0
  16. data/lib/mongoid/criteria/scrollable.rb +13 -12
  17. data/lib/mongoid/scroll/base64_encoded_cursor.rb +40 -0
  18. data/lib/mongoid/scroll/base_cursor.rb +123 -0
  19. data/lib/mongoid/scroll/cursor.rb +24 -87
  20. data/lib/mongoid/scroll/errors/base.rb +1 -1
  21. data/lib/mongoid/scroll/errors/invalid_base64_cursor_error.rb +11 -0
  22. data/lib/mongoid/scroll/errors/invalid_base_cursor_error.rb +8 -0
  23. data/lib/mongoid/scroll/errors/invalid_cursor_error.rb +1 -1
  24. data/lib/mongoid/scroll/errors/mismatched_sort_fields_error.rb +15 -0
  25. data/lib/mongoid/scroll/errors.rb +3 -0
  26. data/lib/mongoid/scroll/version.rb +1 -1
  27. data/lib/mongoid-scroll.rb +4 -1
  28. data/spec/mongo/collection_view_spec.rb +122 -105
  29. data/spec/mongoid/base64_encoded_cursor_spec.rb +233 -0
  30. data/spec/mongoid/criteria_spec.rb +236 -197
  31. data/spec/mongoid/{scroll_cursor_spec.rb → cursor_spec.rb} +48 -12
  32. data/spec/support/feed/item.rb +1 -1
  33. metadata +15 -8
  34. data/.travis.yml +0 -37
  35. data/examples/moped_scroll_feed.rb +0 -45
  36. data/lib/moped/scrollable.rb +0 -38
  37. data/spec/moped/query_spec.rb +0 -126
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 976d59e1ce7d21c024936f21e58be085fa096260c8ee38b4c0d6f70d35145449
4
- data.tar.gz: 389db57d4b31efa2114f9c3f5f86545f691a2db96de1d3aaad5ff995a0b02f45
3
+ metadata.gz: 73c44f9f9dcdc554e3f0ab2701cb4c87822bdf905bd7212e6bb59ea66e6a454d
4
+ data.tar.gz: 6e2abaa7d08e7bf0dfb914fc26b51b1d835526c8d0b1bc75fb0577adf06edb8d
5
5
  SHA512:
6
- metadata.gz: f4929b559f25512c5ebe9a66519f018e834a2678e60c4062047bbdeb466d87b1c99be3a23b157f1c331c61d39bb5627f2ff4eef13158c697596755adf9eea927
7
- data.tar.gz: bd8df34a05b1861472d6710e96c58104af9f0873fffc6cbb4191c91d4eab2c42bb739c0c8a522a92d61eb8898f3ace07adbccd48b40396f2330831cb4c014426
6
+ metadata.gz: ea72567b1edb2d8df27dc309d685804a8f199ff060ad93cb64ef01b6fd098a4ae7f677242375a89875bbdb8f8104f238cb6d79fb667799b5e90c297ce96d0e6f
7
+ data.tar.gz: ab22160efc873bf42430292047661de3b532a0629326dbfdc93a2ef49bc64207725b405472410171e96e6f997e5a23fc5914698b488a4fd1cacb29021bd70261
@@ -0,0 +1,36 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+
4
+ jobs:
5
+ test:
6
+ runs-on: ubuntu-latest
7
+ strategy:
8
+ matrix:
9
+ entry:
10
+ - { ruby: '2.6', mongodb: '4.4', mongoid: '5' }
11
+ - { ruby: '3.2', mongodb: '6.0', mongoid: '5' }
12
+ - { ruby: '2.7', mongodb: '4.4', mongoid: '6' }
13
+ - { ruby: '2.7', mongodb: '4.4', mongoid: '7' }
14
+ - { ruby: '3.0', mongodb: '4.4', mongoid: '6' }
15
+ - { ruby: '3.0', mongodb: '4.4', mongoid: '7' }
16
+ - { ruby: '3.2', mongodb: '5.0', mongoid: '7' }
17
+ - { ruby: '3.2', mongodb: '6.0', mongoid: '7' }
18
+ - { ruby: '3.1', mongodb: '4.4', mongoid: '8' }
19
+ - { ruby: '3.2', mongodb: '5.0', mongoid: '8' }
20
+ - { ruby: '3.2', mongodb: '6.0', mongoid: '8' }
21
+ name: test (ruby=${{ matrix.entry.ruby }}, mongodb=${{ matrix.entry.mongodb }}), mongoid=${{ matrix.entry.mongoid }})
22
+ env:
23
+ MONGOID_VERSION: ${{ matrix.entry.mongoid }}
24
+ steps:
25
+ - name: Set up MongoDB ${{ matrix.entry.mongodb }}
26
+ uses: supercharge/mongodb-github-action@1.8.0
27
+ with:
28
+ mongodb-version: ${{ matrix.entry.mongodb }}
29
+ - uses: actions/checkout@v3
30
+ - name: Set up Ruby
31
+ uses: ruby/setup-ruby@v1
32
+ with:
33
+ ruby-version: ${{ matrix.entry.ruby }}
34
+ bundler-cache: true
35
+ - name: Run tests
36
+ run: bundle exec rspec
@@ -0,0 +1,17 @@
1
+ name: PR Linter
2
+ on: [pull_request]
3
+ jobs:
4
+ danger:
5
+ runs-on: ubuntu-latest
6
+ steps:
7
+ - uses: actions/checkout@v3
8
+ with:
9
+ fetch-depth: 0
10
+ - uses: ruby/setup-ruby@v1
11
+ with:
12
+ ruby-version: 2.7
13
+ bundler-cache: true
14
+ - run: |
15
+ # Personal access token for dangerpr-bot - public, but base64 encoded to avoid tripping up GitHub
16
+ TOKEN=$(echo -n Z2hwX0xNQ3VmanBFeTBvYkZVTWh6NVNqVFFBOEUxU25abzBqRUVuaAo= | base64 --decode)
17
+ DANGER_GITHUB_API_TOKEN=$TOKEN bundle exec danger --verbose
data/.rubocop_todo.yml CHANGED
@@ -1,47 +1,52 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2018-03-13 23:29:51 +0100 using RuboCop version 0.49.1.
3
+ # on 2023-03-07 08:58:13 -0500 using RuboCop version 0.49.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 6
9
+ # Offense count: 5
10
10
  # Configuration parameters: Include.
11
11
  # Include: **/Gemfile, **/gems.rb
12
12
  Bundler/DuplicatedGem:
13
13
  Exclude:
14
14
  - 'Gemfile'
15
15
 
16
- # Offense count: 6
16
+ # Offense count: 8
17
17
  Metrics/AbcSize:
18
- Max: 67
18
+ Max: 38
19
19
 
20
- # Offense count: 14
20
+ # Offense count: 18
21
21
  # Configuration parameters: CountComments, ExcludedMethods.
22
22
  Metrics/BlockLength:
23
- Max: 216
23
+ Max: 258
24
+
25
+ # Offense count: 1
26
+ # Configuration parameters: CountComments.
27
+ Metrics/ClassLength:
28
+ Max: 103
24
29
 
25
30
  # Offense count: 5
26
31
  Metrics/CyclomaticComplexity:
27
- Max: 11
32
+ Max: 13
28
33
 
29
- # Offense count: 121
34
+ # Offense count: 146
30
35
  # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
31
36
  # URISchemes: http, https
32
37
  Metrics/LineLength:
33
- Max: 172
38
+ Max: 252
34
39
 
35
- # Offense count: 6
40
+ # Offense count: 7
36
41
  # Configuration parameters: CountComments.
37
42
  Metrics/MethodLength:
38
- Max: 25
43
+ Max: 23
39
44
 
40
- # Offense count: 3
45
+ # Offense count: 4
41
46
  Metrics/PerceivedComplexity:
42
- Max: 11
47
+ Max: 12
43
48
 
44
- # Offense count: 10
49
+ # Offense count: 11
45
50
  Style/Documentation:
46
51
  Exclude:
47
52
  - 'spec/**/*'
@@ -49,13 +54,14 @@ Style/Documentation:
49
54
  - 'examples/mongoid_scroll_feed.rb'
50
55
  - 'lib/mongo/scrollable.rb'
51
56
  - 'lib/mongoid/criteria/scrollable.rb'
57
+ - 'lib/mongoid/scroll/base_cursor.rb'
52
58
  - 'lib/mongoid/scroll/cursor.rb'
53
59
  - 'lib/mongoid/scroll/errors/base.rb'
60
+ - 'lib/mongoid/scroll/errors/invalid_base64_cursor_error.rb'
54
61
  - 'lib/mongoid/scroll/errors/invalid_cursor_error.rb'
55
62
  - 'lib/mongoid/scroll/errors/multiple_sort_fields_error.rb'
56
63
  - 'lib/mongoid/scroll/errors/no_such_field_error.rb'
57
64
  - 'lib/mongoid/scroll/errors/unsupported_field_type_error.rb'
58
- - 'lib/moped/scrollable.rb'
59
65
 
60
66
  # Offense count: 1
61
67
  # Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
@@ -65,12 +71,11 @@ Style/FileName:
65
71
  - 'lib/mongoid-scroll.rb'
66
72
 
67
73
  # Offense count: 1
68
- # Configuration parameters: MinBodyLength.
69
- Style/GuardClause:
74
+ Style/MultilineTernaryOperator:
70
75
  Exclude:
71
- - 'lib/moped/scrollable.rb'
76
+ - 'lib/mongoid/scroll/cursor.rb'
72
77
 
73
- # Offense count: 1
78
+ # Offense count: 3
74
79
  # Cop supports --auto-correct.
75
80
  # Configuration parameters: SupportedStyles.
76
81
  # SupportedStyles: compact, exploded
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ### 1.0.0 (2023/03/08)
2
+
3
+ * [#25](https://github.com/mongoid/mongoid-scroll/pull/25): Compatibility with Ruby 3 - [@leamotta](https://github.com/leamotta).
4
+ * [#25](https://github.com/mongoid/mongoid-scroll/pull/25): Replace Travis CI with GHA - [@leamotta](https://github.com/leamotta).
5
+ * [#29](https://github.com/mongoid/mongoid-scroll/pull/29): Add ability to include the current record to the cursor - [@FabienChaynes](https://github.com/FabienChaynes).
6
+ * [#30](https://github.com/mongoid/mongoid-scroll/pull/30): Prevent discrepancy between the original sort and the cursor sort - [@FabienChaynes](https://github.com/FabienChaynes).
7
+ * [#32](https://github.com/mongoid/mongoid-scroll/pull/32): Add Base64 serialization for cursors - [@FabienChaynes](https://github.com/FabienChaynes).
8
+ * [#33](https://github.com/mongoid/mongoid-scroll/pull/33): Removed support for Mongoid 3, 4 and Moped - [@dblock](https://github.com/dblock).
9
+ * [#34](https://github.com/mongoid/mongoid-scroll/pull/34): Added support for Mongoid 8 - [@dblock](https://github.com/dblock).
10
+
1
11
  ### 0.3.7 (2021/06/01)
2
12
 
3
13
  * [#22](https://github.com/mongoid/mongoid-scroll/pull/22): Compatibility with Mongoid 7 - [@asgerb](https://github.com/asgerb).
data/Gemfile CHANGED
@@ -4,19 +4,20 @@ gemspec
4
4
 
5
5
  case version = ENV['MONGOID_VERSION'] || '~> 7.0'
6
6
  when 'HEAD' then gem 'mongoid', github: 'mongodb/mongoid'
7
+ when /8/ then gem 'mongoid', '~> 8.0'
7
8
  when /7/ then gem 'mongoid', '~> 7.0'
8
9
  when /6/ then gem 'mongoid', '~> 6.0'
9
- when /5/ then gem 'mongoid', '~> 5.0'
10
- when /4/ then gem 'mongoid', '~> 4.0'
11
- when /3/ then gem 'mongoid', '~> 3.1'
12
- else gem 'mongoid', version
10
+ when /5/ then
11
+ gem 'bigdecimal', '1.3.5'
12
+ gem 'mongoid', '~> 5.0'
13
+ else gem 'mongoid', version
13
14
  end
14
15
 
15
16
  group :development, :test do
16
17
  gem 'bundler'
17
18
  gem 'database_cleaner', '~> 1.8.5'
18
19
  gem 'faker'
19
- gem 'mongoid-danger', '~> 0.1.0', require: false
20
+ gem 'mongoid-danger', '~> 0.2.0', require: false
20
21
  gem 'rake'
21
22
  gem 'rspec', '~> 3.0'
22
23
  gem 'rspec-its'
data/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2013-2015 Daniel Doubrovkine, Artsy Inc.
3
+ Copyright (c) 2013-2023 Daniel Doubrovkine, Artsy Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,27 +1,38 @@
1
- Mongoid::Scroll
2
- ===============
1
+ - [Mongoid::Scroll](#mongoidscroll)
2
+ - [Compatibility](#compatibility)
3
+ - [Demo](#demo)
4
+ - [The Problem](#the-problem)
5
+ - [Installation](#installation)
6
+ - [Usage](#usage)
7
+ - [Mongoid](#mongoid)
8
+ - [Mongo-Ruby-Driver (Mongoid 5)](#mongo-ruby-driver-mongoid-5)
9
+ - [Indexes and Performance](#indexes-and-performance)
10
+ - [Cursors](#cursors)
11
+ - [Standard Cursor](#standard-cursor)
12
+ - [Base64 Encoded Cursor](#base64-encoded-cursor)
13
+ - [Contributing](#contributing)
14
+ - [Copyright and License](#copyright-and-license)
15
+
16
+ # Mongoid::Scroll
3
17
 
4
18
  [![Gem Version](https://badge.fury.io/rb/mongoid-scroll.svg)](https://badge.fury.io/rb/mongoid-scroll)
5
- [![Build Status](https://travis-ci.org/mongoid/mongoid-scroll.svg?branch=master)](https://travis-ci.org/mongoid/mongoid-scroll)
19
+ [![Build Status](https://github.com/mongoid/mongoid-scroll/actions/workflows/ci.yml/badge.svg)](https://github.com/mongoid/mongoid-scroll/actions/workflows/ci.yml)
6
20
  [![Dependency Status](https://gemnasium.com/mongoid/mongoid-scroll.svg)](https://gemnasium.com/mongoid/mongoid-scroll)
7
21
  [![Code Climate](https://codeclimate.com/github/mongoid/mongoid-scroll.svg)](https://codeclimate.com/github/mongoid/mongoid-scroll)
8
22
 
9
- Mongoid extension that enables infinite scrolling for `Mongoid::Criteria`, `Moped::Query` and `Mongo::Collection::View`.
23
+ Mongoid extension that enables infinite scrolling for `Mongoid::Criteria` and `Mongo::Collection::View`.
10
24
 
11
- Compatibility
12
- -------------
25
+ ## Compatibility
13
26
 
14
- This gem supports Mongoid 3, 4, 5, 6, 7, Moped and Mongo-Ruby-Driver.
27
+ This gem supports Mongoid 5, 6, 7 and 8.
15
28
 
16
- Demo
17
- ----
29
+ ## Demo
18
30
 
19
31
  Check out [shows on artsy.net](http://artsy.net/shows). Keep scrolling down.
20
32
 
21
- There're also two code samples for Mongoid and Moped in [examples](examples). Run `bundle exec ruby examples/mongoid_scroll_feed.rb`.
33
+ There're also two code samples for Mongoid in [examples](examples). Run `bundle exec ruby examples/mongoid_scroll_feed.rb`.
22
34
 
23
- The Problem
24
- -----------
35
+ ## The Problem
25
36
 
26
37
  Traditional pagination does not work when data changes between paginated requests, which makes it unsuitable for infinite scroll behaviors.
27
38
 
@@ -30,8 +41,7 @@ Traditional pagination does not work when data changes between paginated request
30
41
 
31
42
  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.
32
43
 
33
- Installation
34
- ------------
44
+ ## Installation
35
45
 
36
46
  Add the gem to your Gemfile and run `bundle install`.
37
47
 
@@ -39,8 +49,7 @@ Add the gem to your Gemfile and run `bundle install`.
39
49
  gem 'mongoid-scroll'
40
50
  ```
41
51
 
42
- Usage
43
- -----
52
+ ## Usage
44
53
 
45
54
  ### Mongoid
46
55
 
@@ -84,27 +93,6 @@ Feed::Item.desc(:position).scroll(saved_cursor) do |record, next_cursor|
84
93
  end
85
94
  ```
86
95
 
87
- ### Moped (Mongoid 3 and 4)
88
-
89
- Scroll a `Moped::Query` and save a cursor to the last item. You must also supply a `field_type` of the sort criteria.
90
-
91
- ```ruby
92
- saved_cursor = nil
93
- session[:feed_items].find.sort(position: -1).limit(5).scroll(nil, { field_type: DateTime }) do |record, next_cursor|
94
- # each record, one-by-one
95
- saved_cursor = next_cursor
96
- end
97
- ```
98
-
99
- Resume iterating using the previously saved cursor.
100
-
101
- ```ruby
102
- session[:feed_items].find.sort(position: -1).limit(5).scroll(saved_cursor, { field_type: DateTime }) do |record, next_cursor|
103
- # each record, one-by-one
104
- saved_cursor = next_cursor
105
- end
106
- ```
107
-
108
96
  ### Mongo-Ruby-Driver (Mongoid 5)
109
97
 
110
98
  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
126
114
  end
127
115
  ```
128
116
 
129
- Indexes and Performance
130
- -----------------------
117
+ ## Indexes and Performance
131
118
 
132
119
  A query without a cursor is identical to a query without a scroll.
133
120
 
@@ -159,8 +146,7 @@ module Feed
159
146
  end
160
147
  ```
161
148
 
162
- Cursors
163
- -------
149
+ ## Cursors
164
150
 
165
151
  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.
166
152
 
@@ -176,14 +162,41 @@ You can also a `field_name` and `field_type` instead of a Mongoid field.
176
162
  cursor = Mongoid::Scroll::Cursor.from_record(record, { field_type: DateTime, field_name: "position" })
177
163
  ```
178
164
 
179
- Contributing
180
- ------------
165
+ When the `include_current` option is set to `true`, the cursor will include the record it points to:
166
+
167
+ ```ruby
168
+ record = Feed::Item.desc(:position).limit(3).last
169
+ cursor = Mongoid::Scroll::Cursor.from_record(record, { field: Feed::Item.fields["position"], include_current: true })
170
+ Feed::Item.asc(:position).limit(1).scroll(cursor).first # record
171
+ ```
172
+
173
+ If the `field_name`, `field_type` or `direction` options you specify when creating the cursor are different from the original criteria, a `Mongoid::Scroll::Errors::MismatchedSortFieldsError` will be raised.
174
+
175
+ ```ruby
176
+ cursor = Mongoid::Scroll::Cursor.from_record(record, { field_type: DateTime, field_name: "position" })
177
+ Feed::Item.desc(:created_at).scroll(cursor) # Raises a Mongoid::Scroll::Errors::MismatchedSortFieldsError
178
+ ```
179
+
180
+ ### Standard Cursor
181
+
182
+ 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.
183
+
184
+ ### Base64 Encoded Cursor
185
+
186
+ 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.
187
+
188
+ ```ruby
189
+ Feed::Item.desc(:position).limit(5).scroll(Mongoid::Scroll::Base64EncodedCursor) do |record, next_cursor|
190
+ # next_cursor is of type Mongoid::Scroll::Base64EncodedCursor
191
+ end
192
+ ```
193
+
194
+ ## Contributing
181
195
 
182
196
  Fork the project. Make your feature addition or bug fix with tests. Send a pull request. Bonus points for topic branches.
183
197
 
184
- Copyright and License
185
- ---------------------
198
+ ## Copyright and License
186
199
 
187
200
  MIT License, see [LICENSE](http://github.com/mongoid/mongoid-scroll/raw/master/LICENSE.md) for details.
188
201
 
189
- (c) 2013-2015 [Daniel Doubrovkine](http://github.com/dblock), based on code by [Frank Macreery](http://github.com/macreery), [Artsy Inc.](http://artsy.net)
202
+ (c) 2013-2023 [Daniel Doubrovkine](http://github.com/dblock), based on code by [Frank Macreery](http://github.com/macreery), [Artsy Inc.](http://artsy.net)
data/RELEASING.md CHANGED
@@ -12,7 +12,7 @@ bundle install
12
12
  rake
13
13
  ```
14
14
 
15
- Check that the last build succeeded in [Travis CI](https://travis-ci.org/mongoid/mongoid-scroll) for all supported platforms.
15
+ Check that the last build succeeded in [Github Actions](https://github.com/mongoid/mongoid-scroll/actions) for all supported platforms.
16
16
 
17
17
  Increment the version, modify [lib/mongoid/scroll/version.rb](lib/mongoid/scroll/version.rb).
18
18
 
data/UPGRADING.md ADDED
@@ -0,0 +1,14 @@
1
+ # Upgrading
2
+
3
+ ## Upgrading to >= 1.0.0
4
+
5
+ ### Mismatched Sort Fields
6
+
7
+ 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.
8
+
9
+ 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`.
10
+
11
+ ```ruby
12
+ cursor.field_name = "position"
13
+ Feed::Item.desc(:created_at).scroll(cursor)
14
+ ```
@@ -4,14 +4,8 @@ Bundler.setup(:default, :development)
4
4
  require 'mongoid-scroll'
5
5
  require 'faker'
6
6
 
7
- if defined?(Moped)
8
- Moped.logger = Logger.new($stdout)
9
- Moped.logger.level = Logger::DEBUG
10
- else
11
- Mongoid.logger.level = Logger::INFO
12
- Mongo::Logger.logger.level = Logger::INFO if Mongoid::Compatibility::Version.mongoid5?
13
- end
14
-
7
+ Mongoid.logger.level = Logger::INFO
8
+ Mongo::Logger.logger.level = Logger::INFO if Mongoid::Compatibility::Version.mongoid5?
15
9
  Mongoid.connect_to 'mongoid_scroll_demo'
16
10
  Mongoid.purge!
17
11
 
@@ -7,10 +7,18 @@ en:
7
7
  message: "Scrolling over a criteria with multiple fields is not supported."
8
8
  summary: "You're attempting to scroll over data with a sort order that includes multiple fields: %{sort}."
9
9
  resolution: "Simplify the sort order to a single field."
10
+ mismatched_sort_fields:
11
+ message: "Specifying different sort fields than the original sort is not supported."
12
+ summary: "You're attempting to scroll over data with a sort order that differs between the cursor and the original criteria: %{diff}."
13
+ resolution: "Don't update the cursor sort options."
10
14
  invalid_cursor:
11
15
  message: "The cursor supplied is invalid."
12
16
  summary: "The cursor supplied is invalid: %{cursor}."
13
17
  resolution: "Cursors must be in the form 'value:tiebreak_id'."
18
+ invalid_base64_cursor:
19
+ message: "The cursor supplied is invalid."
20
+ summary: "The cursor supplied is invalid: %{cursor}."
21
+ resolution: "Cursors must be a base64-encoded string."
14
22
  no_such_field:
15
23
  message: "Invalid field."
16
24
  summary: "The field supplied in the cursor does not exist: %{field}."
@@ -1,6 +1,10 @@
1
1
  module Mongo
2
2
  module Scrollable
3
- def scroll(cursor = nil, options = nil, &_block)
3
+ include Mongoid::Criteria::Scrollable::Fields
4
+ include Mongoid::Criteria::Scrollable::Cursors
5
+
6
+ def scroll(cursor_or_type = nil, options = nil, &_block)
7
+ cursor, cursor_type = cursor_and_type(cursor_or_type)
4
8
  view = self
5
9
  # we don't support scrolling over a view with multiple fields
6
10
  raise Mongoid::Scroll::Errors::MultipleSortFieldsError.new(sort: view.sort) if view.sort && view.sort.keys.size != 1
@@ -10,7 +14,8 @@ module Mongo
10
14
  # scroll cursor from the parameter, with value and tiebreak_id
11
15
  options = { field_type: BSON::ObjectId } unless options
12
16
  cursor_options = { field_name: scroll_field, direction: scroll_direction }.merge(options)
13
- cursor = cursor.is_a?(Mongoid::Scroll::Cursor) ? cursor : Mongoid::Scroll::Cursor.new(cursor, cursor_options)
17
+ cursor = cursor && cursor.is_a?(cursor_type) ? cursor : cursor_type.new(cursor, cursor_options)
18
+ raise_mismatched_sort_fields_error!(cursor, cursor_options) if different_sort_fields?(cursor, cursor_options)
14
19
  # make a view
15
20
  view = Mongo::Collection::View.new(
16
21
  view.collection,
@@ -22,7 +27,7 @@ module Mongo
22
27
  # scroll
23
28
  if block_given?
24
29
  view.each do |record|
25
- yield record, Mongoid::Scroll::Cursor.from_record(record, cursor_options)
30
+ yield record, cursor_type.from_record(record, cursor_options)
26
31
  end
27
32
  else
28
33
  view
@@ -0,0 +1,18 @@
1
+ module Mongoid
2
+ class Criteria
3
+ module Scrollable
4
+ # Shared by *::Scrollable modules
5
+ module Cursors
6
+ private
7
+
8
+ def cursor_and_type(cursor_or_type)
9
+ cursor = cursor_or_type.is_a?(Class) ? nil : cursor_or_type
10
+ cursor_type = cursor_or_type.is_a?(Class) ? cursor_or_type : nil
11
+ cursor_type ||= cursor.class if cursor.is_a?(Mongoid::Scroll::BaseCursor)
12
+ cursor_type ||= Mongoid::Scroll::Cursor
13
+ [cursor, cursor_type]
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module Mongoid
2
+ class Criteria
3
+ module Scrollable
4
+ # Shared by *::Scrollable modules
5
+ module Fields
6
+ private
7
+
8
+ def raise_mismatched_sort_fields_error!(cursor, criteria_cursor_options)
9
+ diff = cursor.sort_options.reject { |k, v| criteria_cursor_options[k] == v }
10
+ raise Mongoid::Scroll::Errors::MismatchedSortFieldsError.new(diff: diff)
11
+ end
12
+
13
+ def different_sort_fields?(cursor, criteria_cursor_options)
14
+ criteria_cursor_options[:field_type] = criteria_cursor_options[:field_type].to_s
15
+ criteria_cursor_options[:field_name] = criteria_cursor_options[:field_name].to_s
16
+ criteria_cursor_options != cursor.sort_options
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,16 +1,21 @@
1
1
  module Mongoid
2
2
  class Criteria
3
3
  module Scrollable
4
- def scroll(cursor = nil, &_block)
4
+ include Mongoid::Criteria::Scrollable::Fields
5
+ include Mongoid::Criteria::Scrollable::Cursors
6
+
7
+ def scroll(cursor_or_type = nil, &_block)
8
+ cursor, cursor_type = cursor_and_type(cursor_or_type)
5
9
  raise_multiple_sort_fields_error if multiple_sort_fields?
6
10
  criteria = dup
7
11
  criteria.merge!(default_sort) if no_sort_option?
8
12
  cursor_options = build_cursor_options(criteria)
9
- cursor = cursor.is_a?(Mongoid::Scroll::Cursor) ? cursor : new_cursor(cursor, cursor_options)
13
+ cursor = cursor.is_a?(cursor_type) ? cursor : new_cursor(cursor_type, cursor, cursor_options)
14
+ raise_mismatched_sort_fields_error!(cursor, cursor_options) if different_sort_fields?(cursor, cursor_options)
10
15
  cursor_criteria = build_cursor_criteria(criteria, cursor)
11
16
  if block_given?
12
17
  cursor_criteria.order_by(_id: scroll_direction(criteria)).each do |record|
13
- yield record, cursor_from_record(record, cursor_options)
18
+ yield record, cursor_from_record(cursor_type, record, cursor_options)
14
19
  end
15
20
  else
16
21
  cursor_criteria
@@ -51,8 +56,8 @@ module Mongoid
51
56
  }
52
57
  end
53
58
 
54
- def new_cursor(cursor, cursor_options)
55
- Mongoid::Scroll::Cursor.new(cursor, cursor_options)
59
+ def new_cursor(cursor_type, cursor, cursor_options)
60
+ cursor_type.new(cursor, cursor_options)
56
61
  end
57
62
 
58
63
  def build_cursor_criteria(criteria, cursor)
@@ -61,18 +66,14 @@ module Mongoid
61
66
  cursor_criteria
62
67
  end
63
68
 
64
- def cursor_from_record(record, cursor_options)
65
- Mongoid::Scroll::Cursor.from_record(record, cursor_options)
69
+ def cursor_from_record(cursor_type, record, cursor_options)
70
+ cursor_type.from_record(record, cursor_options)
66
71
  end
67
72
 
68
73
  def scroll_field_type(criteria)
69
74
  scroll_field = scroll_field(criteria)
70
75
  field = criteria.klass.fields[scroll_field.to_s]
71
- field.foreign_key? && field.object_id_field? ? bson_type : field.type
72
- end
73
-
74
- def bson_type
75
- Mongoid::Compatibility::Version.mongoid3? ? Moped::BSON::ObjectId : BSON::ObjectId
76
+ field.foreign_key? && field.object_id_field? ? BSON::ObjectId : field.type
76
77
  end
77
78
  end
78
79
  end
@@ -0,0 +1,40 @@
1
+ require 'base64'
2
+ require 'json'
3
+
4
+ module Mongoid
5
+ module Scroll
6
+ # Allows to serializer/deserialize the cursor using RFC 4648
7
+ class Base64EncodedCursor < BaseCursor
8
+ def initialize(value, options = {})
9
+ options = extract_field_options(options)
10
+ if value
11
+ begin
12
+ parsed = ::JSON.parse(::Base64.strict_decode64(value))
13
+ rescue
14
+ raise Mongoid::Scroll::Errors::InvalidBase64CursorError.new(cursor: value)
15
+ end
16
+ super parse_field_value(parsed['field_type'], parsed['field_name'], parsed['value']), {
17
+ field_type: parsed['field_type'],
18
+ field_name: parsed['field_name'],
19
+ direction: parsed['direction'],
20
+ include_current: parsed['include_current'],
21
+ tiebreak_id: parsed['tiebreak_id'] && !parsed['tiebreak_id'].empty? ? BSON::ObjectId.from_string(parsed['tiebreak_id']) : nil
22
+ }
23
+ else
24
+ super nil, options
25
+ end
26
+ end
27
+
28
+ def to_s
29
+ Base64.strict_encode64({
30
+ value: transform_field_value(field_type, field_name, value),
31
+ field_type: field_type.to_s,
32
+ field_name: field_name,
33
+ direction: direction,
34
+ include_current: include_current,
35
+ tiebreak_id: tiebreak_id && tiebreak_id.to_s
36
+ }.to_json)
37
+ end
38
+ end
39
+ end
40
+ end