diff --git a/.rubocop.yml b/.rubocop.yml index dcfdb5b..9765852 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,11 @@ inherit_from: .rubocop_todo.yml +require: + - rubocop-rake + - rubocop-rspec + AllCops: + NewCops: enable Exclude: - vendor/**/* - gemfiles/vendor/**/* diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index cea232b..201186e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,58 +1,159 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2023-03-09 01:14:57 +0100 using RuboCop version 0.49.1. +# on 2024-09-07 13:11:15 UTC using RuboCop version 1.66.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: 5 -# Configuration parameters: Include. -# Include: **/Gemfile, **/gems.rb -Bundler/DuplicatedGem: +# Offense count: 1 +# Configuration parameters: Severity, Include. +# Include: **/*.gemspec +Gemspec/RequiredRubyVersion: Exclude: - - 'Gemfile' + - 'mongoid-scroll.gemspec' -# Offense count: 8 -Metrics/AbcSize: - Max: 38 +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Lint/NonDeterministicRequireOrder: + Exclude: + - 'spec/spec_helper.rb' -# Offense count: 18 -# Configuration parameters: CountComments, ExcludedMethods. -Metrics/BlockLength: - Max: 274 +# Offense count: 7 +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. +Metrics/AbcSize: + Max: 28 # Offense count: 1 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: - Max: 103 + Max: 108 # Offense count: 5 +# Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/CyclomaticComplexity: Max: 13 -# Offense count: 153 -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Metrics/LineLength: - Max: 252 - # Offense count: 7 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: - Max: 23 + Max: 24 -# Offense count: 4 +# Offense count: 3 +# Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: - Max: 12 + Max: 13 + +# Offense count: 1 +# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms. +# CheckDefinitionPathHierarchyRoots: lib, spec, test, src +# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS +Naming/FileName: + Exclude: + - 'Rakefile.rb' + - 'lib/mongoid-scroll.rb' + +# Offense count: 3 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 +Naming/VariableNumber: + Exclude: + - 'spec/mongoid/criteria_spec.rb' + +# Offense count: 34 +# Configuration parameters: Prefixes, AllowedPatterns. +# Prefixes: when, with, without +RSpec/ContextWording: + Exclude: + - 'spec/mongoid/base64_encoded_cursor_spec.rb' + - 'spec/mongoid/criteria_spec.rb' + - 'spec/mongoid/cursor_spec.rb' + +# Offense count: 34 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. +# SupportedStyles: described_class, explicit +RSpec/DescribedClass: + Exclude: + - 'spec/mongoid/base64_encoded_cursor_spec.rb' + - 'spec/mongoid/cursor_spec.rb' + +# Offense count: 17 +# Configuration parameters: CountAsOne. +RSpec/ExampleLength: + Max: 18 + +# Offense count: 3 +# Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns. +RSpec/IndexedLet: + Exclude: + - 'spec/mongoid/criteria_spec.rb' + +# Offense count: 2 +# Configuration parameters: AssignmentOnly. +RSpec/InstanceVariable: + Exclude: + - 'spec/mongoid/criteria_spec.rb' # Offense count: 11 +RSpec/MultipleExpectations: + Max: 4 + +# Offense count: 4 +# Configuration parameters: EnforcedStyle, IgnoreSharedExamples. +# SupportedStyles: always, named_only +RSpec/NamedSubject: + Exclude: + - 'spec/mongoid/criteria_spec.rb' + +# Offense count: 3 +# Configuration parameters: AllowedGroups. +RSpec/NestedGroups: + Max: 4 + +# Offense count: 3 +# Configuration parameters: AllowedPatterns. +# AllowedPatterns: ^expect_, ^assert_ +RSpec/NoExpectationExample: + Exclude: + - 'spec/mongoid/base64_encoded_cursor_spec.rb' + +# Offense count: 4 +RSpec/RepeatedDescription: + Exclude: + - 'spec/mongoid/base64_encoded_cursor_spec.rb' + +# Offense count: 4 +RSpec/RepeatedExample: + Exclude: + - 'spec/mongoid/base64_encoded_cursor_spec.rb' + +# Offense count: 3 +# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata. +# Include: **/*_spec.rb +RSpec/SpecFilePathFormat: + Exclude: + - '**/spec/routing/**/*' + - 'spec/mongoid/base64_encoded_cursor_spec.rb' + - 'spec/mongoid/cursor_spec.rb' + - 'spec/mongoid/scroll_spec.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Rake/Desc: + Exclude: + - 'Rakefile' + +# Offense count: 12 +# Configuration parameters: AllowedConstants. Style/Documentation: Exclude: - 'spec/**/*' - 'test/**/*' - 'examples/feed.rb' - 'lib/mongoid/criteria/scrollable.rb' + - 'lib/mongoid/criteria/scrollable/iterator.rb' - 'lib/mongoid/scroll/base_cursor.rb' - 'lib/mongoid/scroll/cursor.rb' - 'lib/mongoid/scroll/errors/base.rb' @@ -61,22 +162,40 @@ Style/Documentation: - '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/mongoid/scroll/errors/unsupported_type_error.rb' -# Offense count: 1 -# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms. -# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS -Style/FileName: - Exclude: - - 'lib/mongoid-scroll.rb' +# Offense count: 34 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: always, always_true, never +Style/FrozenStringLiteralComment: + Enabled: false # Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). Style/MultilineTernaryOperator: Exclude: - 'lib/mongoid/scroll/cursor.rb' # Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: SupportedStyles. +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedCompactTypes. # SupportedStyles: compact, exploded Style/RaiseArgs: EnforcedStyle: compact + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. +# AllowedMethods: present?, blank?, presence, try, try! +Style/SafeNavigation: + Exclude: + - 'lib/mongoid/scroll/errors/mismatched_sort_fields_error.rb' + - 'lib/mongoid/scroll/errors/multiple_sort_fields_error.rb' + +# Offense count: 36 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 252 diff --git a/CHANGELOG.md b/CHANGELOG.md index 13089fb..a21cbe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * [#43](https://github.com/mongoid/mongoid-scroll/pull/43): Add `current_cursor` - [@GCorbel](https://github.com/GCorbel). * [#44](https://github.com/mongoid/mongoid-scroll/pull/44): Drop support for Mogoid 5 and Mongo Ruby Driver - [@dblock](https://github.com/dblock). * [#45](https://github.com/mongoid/mongoid-scroll/pull/45): Add support for Mogoid 9 - [@dblock](https://github.com/dblock). +* [#46](https://github.com/mongoid/mongoid-scroll/pull/46): Upgrade RuboCop to 1.66.1 - [@dblock](https://github.com/dblock). * Your contribution here. ### 1.0.1 (2023/03/15) diff --git a/Gemfile b/Gemfile index ceab4af..27204e2 100644 --- a/Gemfile +++ b/Gemfile @@ -18,5 +18,7 @@ group :development, :test do gem 'rake' gem 'rspec', '~> 3.0' gem 'rspec-its' - gem 'rubocop', '0.49.1' + gem 'rubocop', '1.66.1' + gem 'rubocop-rake' + gem 'rubocop-rspec' end diff --git a/Rakefile b/Rakefile index 925c576..ad043e4 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,13 @@ require 'rubygems' require 'bundler/gem_tasks' -require File.expand_path('../lib/mongoid/scroll/version', __FILE__) +require File.expand_path('lib/mongoid/scroll/version', __dir__) begin Bundler.setup(:default, :development) rescue Bundler::BundlerError => e - $stderr.puts e.message - $stderr.puts 'Run `bundle install` to install missing gems' + warn e.message + warn 'Run `bundle install` to install missing gems' exit e.status_code end diff --git a/examples/feed.rb b/examples/feed.rb index 91fc20b..6c617f5 100644 --- a/examples/feed.rb +++ b/examples/feed.rb @@ -24,7 +24,7 @@ class Item scroll_by = 7 # insert items with a position out-of-order -rands = (0..total_items).to_a.sort { rand }[0..total_items] +rands = (0..total_items).to_a.sort { |_l, _r| rand }[0..total_items] total_items.times do |_i| Feed::Item.create! title: Faker::Lorem.sentence, position: rands.pop end @@ -42,6 +42,7 @@ class Item total_shown += 1 end break unless next_cursor + # destroy an item just for the heck of it, scroll is not affected Feed::Item.asc(:position).first.destroy end diff --git a/lib/mongoid/criteria/scrollable.rb b/lib/mongoid/criteria/scrollable.rb index 724074c..175fe78 100644 --- a/lib/mongoid/criteria/scrollable.rb +++ b/lib/mongoid/criteria/scrollable.rb @@ -10,7 +10,7 @@ def scroll(cursor_or_type = nil, &_block) criteria = dup criteria.merge!(default_sort) if no_sort_option? cursor_options = build_cursor_options(criteria) - cursor = cursor.is_a?(cursor_type) ? cursor : new_cursor(cursor_type, cursor, cursor_options) + cursor = new_cursor(cursor_type, cursor, cursor_options) unless cursor.is_a?(cursor_type) raise_mismatched_sort_fields_error!(cursor, cursor_options) if different_sort_fields?(cursor, cursor_options) records = find_records(criteria, cursor) if block_given? @@ -99,4 +99,4 @@ def scroll_field_type(criteria) end end -Mongoid::Criteria.send(:include, Mongoid::Criteria::Scrollable) +Mongoid::Criteria.include Mongoid::Criteria::Scrollable diff --git a/lib/mongoid/scroll/base64_encoded_cursor.rb b/lib/mongoid/scroll/base64_encoded_cursor.rb index 28c5d3b..3297220 100644 --- a/lib/mongoid/scroll/base64_encoded_cursor.rb +++ b/lib/mongoid/scroll/base64_encoded_cursor.rb @@ -10,19 +10,19 @@ def initialize(value, options = {}) if value begin parsed = ::JSON.parse(::Base64.strict_decode64(value)) - rescue + rescue StandardError raise Mongoid::Scroll::Errors::InvalidBase64CursorError.new(cursor: value) end - super parse_field_value(parsed['field_type'], parsed['field_name'], parsed['value']), { + super(parse_field_value(parsed['field_type'], parsed['field_name'], parsed['value']), { field_type: parsed['field_type'], field_name: parsed['field_name'], direction: parsed['direction'], include_current: parsed['include_current'], tiebreak_id: parsed['tiebreak_id'] && !parsed['tiebreak_id'].empty? ? BSON::ObjectId.from_string(parsed['tiebreak_id']) : nil, type: parsed['type'].try(:to_sym) - } + }) else - super nil, options + super(nil, options) end end diff --git a/lib/mongoid/scroll/base_cursor.rb b/lib/mongoid/scroll/base_cursor.rb index ce69d42..d784e4f 100644 --- a/lib/mongoid/scroll/base_cursor.rb +++ b/lib/mongoid/scroll/base_cursor.rb @@ -12,7 +12,7 @@ def initialize(value, options = {}) @include_current = options[:include_current] || false @type = options[:type] || :next - raise Mongoid::Scroll::Errors::UnsupportedTypeError.new(type: @type) if ![:previous, :next].include?(@type) + raise Mongoid::Scroll::Errors::UnsupportedTypeError.new(type: @type) unless %i[previous next].include?(@type) end def criteria @@ -72,8 +72,7 @@ def transform_field_value(field_type, field_name, value) return nil unless value case field_type.to_s - when 'BSON::ObjectId' then value.to_s - when 'String' then value.to_s + when 'BSON::ObjectId', 'String' then value.to_s when 'Date' then Time.utc(value.year, value.month, value.day).to_i when 'DateTime', 'Time' then value.utc.to_f.round(3) when 'Float' then value.to_f diff --git a/lib/mongoid/scroll/cursor.rb b/lib/mongoid/scroll/cursor.rb index a140c90..4006a6e 100644 --- a/lib/mongoid/scroll/cursor.rb +++ b/lib/mongoid/scroll/cursor.rb @@ -4,20 +4,20 @@ class Cursor < BaseCursor def initialize(value = nil, options = {}) options = extract_field_options(options) raise ArgumentError.new 'Missing options[:field_name] and/or options[:field_type].' unless options + if value parts = value.split(':') if value - unless parts && parts.length >= 2 - raise Mongoid::Scroll::Errors::InvalidCursorError.new(cursor: value) - end + raise Mongoid::Scroll::Errors::InvalidCursorError.new(cursor: value) unless parts && parts.length >= 2 + value = parse_field_value( options[:field_type], options[:field_name], parts[0...-1].join(':') ) options[:tiebreak_id] = BSON::ObjectId.from_string(parts[-1]) - super value, options + super else - super nil, options + super(nil, options) end end diff --git a/lib/mongoid/scroll/errors/base.rb b/lib/mongoid/scroll/errors/base.rb index fab01ab..a8cb774 100644 --- a/lib/mongoid/scroll/errors/base.rb +++ b/lib/mongoid/scroll/errors/base.rb @@ -21,13 +21,13 @@ def compose_message(key, attributes = {}) @resolution = create_resolution(key, attributes) "\nProblem:\n #{@problem}" \ - "\nSummary:\n #{@summary}" \ - "\nResolution:\n #{@resolution}" + "\nSummary:\n #{@summary}" \ + "\nResolution:\n #{@resolution}" end private - BASE_KEY = 'mongoid.scroll.errors.messages'.freeze #:nodoc: + BASE_KEY = 'mongoid.scroll.errors.messages'.freeze # :nodoc: # Given the key of the specific error and the options hash, translate the # message. @@ -38,7 +38,7 @@ def compose_message(key, attributes = {}) # # Returns a localized error message string. def translate(key, options) - ::I18n.translate("#{BASE_KEY}.#{key}", **{ locale: :en }.merge(options)).strip + ::I18n.translate("#{BASE_KEY}.#{key}", locale: :en, **options).strip end # Create the problem. diff --git a/lib/mongoid/scroll/errors/mismatched_sort_fields_error.rb b/lib/mongoid/scroll/errors/mismatched_sort_fields_error.rb index 5f8e0e0..3c46a10 100644 --- a/lib/mongoid/scroll/errors/mismatched_sort_fields_error.rb +++ b/lib/mongoid/scroll/errors/mismatched_sort_fields_error.rb @@ -4,9 +4,7 @@ module Errors # Raised when the original sort params and the cursor sort params are different class MismatchedSortFieldsError < Mongoid::Scroll::Errors::Base def initialize(opts = {}) - if opts[:diff] && opts[:diff].is_a?(Hash) - opts = opts.merge(diff: opts[:diff].keys.join(', ')) - end + opts = opts.merge(diff: opts[:diff].keys.join(', ')) if opts[:diff] && opts[:diff].is_a?(Hash) super(compose_message('mismatched_sort_fields', opts)) end end diff --git a/lib/mongoid/scroll/errors/multiple_sort_fields_error.rb b/lib/mongoid/scroll/errors/multiple_sort_fields_error.rb index ee3ced2..b957c8d 100644 --- a/lib/mongoid/scroll/errors/multiple_sort_fields_error.rb +++ b/lib/mongoid/scroll/errors/multiple_sort_fields_error.rb @@ -3,9 +3,7 @@ module Scroll module Errors class MultipleSortFieldsError < Mongoid::Scroll::Errors::Base def initialize(opts = {}) - if opts[:sort] && opts[:sort].is_a?(Hash) - opts = opts.merge(sort: opts[:sort].keys.join(', ')) - end + opts = opts.merge(sort: opts[:sort].keys.join(', ')) if opts[:sort] && opts[:sort].is_a?(Hash) super(compose_message('multiple_sort_fields', opts)) end end diff --git a/mongoid-scroll.gemspec b/mongoid-scroll.gemspec index f1ae2a3..8e4e64c 100644 --- a/mongoid-scroll.gemspec +++ b/mongoid-scroll.gemspec @@ -1,4 +1,4 @@ -$LOAD_PATH.push File.expand_path('../lib', __FILE__) +$LOAD_PATH.push File.expand_path('lib', __dir__) require 'mongoid/scroll/version' Gem::Specification.new do |s| @@ -13,7 +13,8 @@ Gem::Specification.new do |s| s.homepage = 'http://github.com/mongoid/mongoid-scroll' s.licenses = ['MIT'] s.summary = 'Mongoid extensions to enable infinite scroll.' + s.add_dependency 'i18n' s.add_dependency 'mongoid', '>= 6.0' s.add_dependency 'mongoid-compatibility' - s.add_dependency 'i18n' + s.metadata['rubygems_mfa_required'] = 'true' end diff --git a/spec/mongoid/base64_encoded_cursor_spec.rb b/spec/mongoid/base64_encoded_cursor_spec.rb index a373130..a78111e 100644 --- a/spec/mongoid/base64_encoded_cursor_spec.rb +++ b/spec/mongoid/base64_encoded_cursor_spec.rb @@ -3,17 +3,24 @@ describe Mongoid::Scroll::Base64EncodedCursor do context 'new' do context 'an empty cursor' do - let(:base64_string) { 'eyJ2YWx1ZSI6bnVsbCwiZmllbGRfdHlwZSI6IlN0cmluZyIsImZpZWxkX25hbWUiOiJhX3N0cmluZyIsImRpcmVjdGlvbiI6MSwiaW5jbHVkZV9jdXJyZW50IjpmYWxzZSwidGllYnJlYWtfaWQiOm51bGwsInR5cGUiOiJuZXh0In0=' } subject do Mongoid::Scroll::Base64EncodedCursor.new base64_string end - its(:tiebreak_id) { should be_nil } - its(:value) { should be_nil } - its(:criteria) { should eq({}) } - its(:type) { should eq(:next) } - its(:to_s) { should eq(base64_string) } + + let(:base64_string) { 'eyJ2YWx1ZSI6bnVsbCwiZmllbGRfdHlwZSI6IlN0cmluZyIsImZpZWxkX25hbWUiOiJhX3N0cmluZyIsImRpcmVjdGlvbiI6MSwiaW5jbHVkZV9jdXJyZW50IjpmYWxzZSwidGllYnJlYWtfaWQiOm51bGwsInR5cGUiOiJuZXh0In0=' } + + its(:tiebreak_id) { is_expected.to be_nil } + its(:value) { is_expected.to be_nil } + its(:criteria) { is_expected.to eq({}) } + its(:type) { is_expected.to eq(:next) } + its(:to_s) { is_expected.to eq(base64_string) } end + context 'a string field cursor' do + subject do + Mongoid::Scroll::Base64EncodedCursor.new base64_string + end + let(:base64_string) { 'eyJ2YWx1ZSI6ImEgc3RyaW5nIiwiZmllbGRfdHlwZSI6IlN0cmluZyIsImZpZWxkX25hbWUiOiJhX3N0cmluZyIsImRpcmVjdGlvbiI6MSwiaW5jbHVkZV9jdXJyZW50IjpmYWxzZSwidGllYnJlYWtfaWQiOiI2NDA2M2RmODA5NDQzNDE3YzdkMmIxMDIiLCJ0eXBlIjoibmV4dCJ9' } let(:a_value) { 'a string' } let(:tiebreak_id) { BSON::ObjectId.from_string('64063df809443417c7d2b102') } @@ -25,19 +32,24 @@ ] } end - subject do - Mongoid::Scroll::Base64EncodedCursor.new base64_string - end - its(:value) { should eq a_value } + + its(:value) { is_expected.to eq a_value } its(:tiebreak_id) { tiebreak_id } - its(:value) { should eq a_value } - its(:tiebreak_id) { should eq tiebreak_id } - its(:criteria) { should eq(criteria) } - its(:type) { should eq(:next) } - its(:to_s) { should eq(base64_string) } + its(:value) { is_expected.to eq a_value } + its(:tiebreak_id) { is_expected.to eq tiebreak_id } + its(:criteria) { is_expected.to eq(criteria) } + its(:type) { is_expected.to eq(:next) } + its(:to_s) { is_expected.to eq(base64_string) } end + context 'an id field cursor' do - let(:base64_string) { 'eyJ2YWx1ZSI6IjY0MDY0NTg0MDk0NDM0MjgxZmE3MWFiMiIsImZpZWxkX3R5cGUiOiJCU09OOjpPYmplY3RJZCIsImZpZWxkX25hbWUiOiJpZCIsImRpcmVjdGlvbiI6MSwiaW5jbHVkZV9jdXJyZW50IjpmYWxzZSwidGllYnJlYWtfaWQiOiI2NDA2NDU4NDA5NDQzNDI4MWZhNzFhYjIiLCJ0eXBlIjoibmV4dCJ9' } + subject do + Mongoid::Scroll::Base64EncodedCursor.new base64_string + end + + let(:base64_string) do + 'eyJ2YWx1ZSI6IjY0MDY0NTg0MDk0NDM0MjgxZmE3MWFiMiIsImZpZWxkX3R5cGUiOiJCU09OOjpPYmplY3RJZCIsImZpZWxkX25hbWUiOiJpZCIsImRpcmVjdGlvbiI6MSwiaW5jbHVkZV9jdXJyZW50IjpmYWxzZSwidGllYnJlYWtfaWQiOiI2NDA2NDU4NDA5NDQzNDI4MWZhNzFhYjIiLCJ0eXBlIjoibmV4dCJ9' + end let(:a_value) { BSON::ObjectId('64064584094434281fa71ab2') } let(:tiebreak_id) { a_value } let(:criteria) do @@ -48,16 +60,19 @@ ] } end + + its(:value) { is_expected.to eq a_value } + its(:tiebreak_id) { is_expected.to eq tiebreak_id } + its(:criteria) { is_expected.to eq(criteria) } + its(:type) { is_expected.to eq(:next) } + its(:to_s) { is_expected.to eq(base64_string) } + end + + context 'an integer field cursor' do subject do Mongoid::Scroll::Base64EncodedCursor.new base64_string end - its(:value) { should eq a_value } - its(:tiebreak_id) { should eq tiebreak_id } - its(:criteria) { should eq(criteria) } - its(:type) { should eq(:next) } - its(:to_s) { should eq(base64_string) } - end - context 'an integer field cursor' do + let(:base64_string) { 'eyJ2YWx1ZSI6MTAsImZpZWxkX3R5cGUiOiJJbnRlZ2VyIiwiZmllbGRfbmFtZSI6ImFfaW50ZWdlciIsImRpcmVjdGlvbiI6MSwiaW5jbHVkZV9jdXJyZW50IjpmYWxzZSwidGllYnJlYWtfaWQiOiI2NDA2M2RmODA5NDQzNDE3YzdkMmIxMDgiLCJ0eXBlIjoibmV4dCJ9' } let(:a_value) { 10 } let(:tiebreak_id) { BSON::ObjectId('64063df809443417c7d2b108') } @@ -69,18 +84,21 @@ ] } end - subject do - Mongoid::Scroll::Base64EncodedCursor.new base64_string - end - its(:value) { should eq a_value } + + its(:value) { is_expected.to eq a_value } its(:tiebreak_id) { tiebreak_id } - its(:value) { should eq a_value } - its(:tiebreak_id) { should eq tiebreak_id } - its(:criteria) { should eq(criteria) } - its(:type) { should eq(:next) } - its(:to_s) { should eq(base64_string) } + its(:value) { is_expected.to eq a_value } + its(:tiebreak_id) { is_expected.to eq tiebreak_id } + its(:criteria) { is_expected.to eq(criteria) } + its(:type) { is_expected.to eq(:next) } + its(:to_s) { is_expected.to eq(base64_string) } end + context 'a date/time field cursor' do + subject do + Mongoid::Scroll::Base64EncodedCursor.new base64_string + end + let(:base64_string) { 'eyJ2YWx1ZSI6MTM4NzU5MDEyMy4wLCJmaWVsZF90eXBlIjoiRGF0ZVRpbWUiLCJmaWVsZF9uYW1lIjoiYV9kYXRldGltZSIsImRpcmVjdGlvbiI6MSwiaW5jbHVkZV9jdXJyZW50IjpmYWxzZSwidGllYnJlYWtfaWQiOiI2NDA2NDNhNzA5NDQzNDIzOWYyZGJmODYiLCJ0eXBlIjoibmV4dCJ9' } let(:a_value) { DateTime.new(2013, 12, 21, 1, 42, 3, 'UTC') } let(:tiebreak_id) { BSON::ObjectId('640643a7094434239f2dbf86') } @@ -92,16 +110,19 @@ ] } end + + its(:value) { is_expected.to eq a_value } + its(:tiebreak_id) { is_expected.to eq tiebreak_id } + its(:criteria) { is_expected.to eq(criteria) } + its(:type) { is_expected.to eq(:next) } + its(:to_s) { is_expected.to eq(base64_string) } + end + + context 'a date field cursor' do subject do Mongoid::Scroll::Base64EncodedCursor.new base64_string end - its(:value) { should eq a_value } - its(:tiebreak_id) { should eq tiebreak_id } - its(:criteria) { should eq(criteria) } - its(:type) { should eq(:next) } - its(:to_s) { should eq(base64_string) } - end - context 'a date field cursor' do + let(:base64_string) { 'eyJ2YWx1ZSI6MTM4NzU4NDAwMCwiZmllbGRfdHlwZSI6IkRhdGUiLCJmaWVsZF9uYW1lIjoiYV9kYXRlIiwiZGlyZWN0aW9uIjoxLCJpbmNsdWRlX2N1cnJlbnQiOmZhbHNlLCJ0aWVicmVha19pZCI6IjY0MDY0MmM5MDk0NDM0MjEyYzRkNDQyMCIsInR5cGUiOiJuZXh0In0=' } let(:tiebreak_id) { BSON::ObjectId('640642c9094434212c4d4420') } let(:a_value) { Date.new(2013, 12, 21) } @@ -113,16 +134,19 @@ ] } end + + its(:value) { is_expected.to eq a_value } + its(:tiebreak_id) { is_expected.to eq tiebreak_id } + its(:criteria) { is_expected.to eq(criteria) } + its(:type) { is_expected.to eq(:next) } + its(:to_s) { is_expected.to eq(base64_string) } + end + + context 'a time field cursor' do subject do Mongoid::Scroll::Base64EncodedCursor.new base64_string end - its(:value) { should eq a_value } - its(:tiebreak_id) { should eq tiebreak_id } - its(:criteria) { should eq(criteria) } - its(:type) { should eq(:next) } - its(:to_s) { should eq(base64_string) } - end - context 'a time field cursor' do + let(:base64_string) { 'eyJ2YWx1ZSI6MTM4NzYwNTcyMy4wLCJmaWVsZF90eXBlIjoiVGltZSIsImZpZWxkX25hbWUiOiJhX3RpbWUiLCJkaXJlY3Rpb24iOjEsImluY2x1ZGVfY3VycmVudCI6ZmFsc2UsInRpZWJyZWFrX2lkIjoiNjQwNjNkNGEwOTQ0MzQxNjZiZDA1M2VkIiwidHlwZSI6Im5leHQifQ==' } let(:item_id) { BSON::ObjectId('640636f209443407333b46d4') } let(:a_value) { Time.new(2013, 12, 21, 6, 2, 3, '+00:00').utc } @@ -135,16 +159,15 @@ ] } end - subject do - Mongoid::Scroll::Base64EncodedCursor.new base64_string - end - its(:value) { should eq a_value } + + its(:value) { is_expected.to eq a_value } its(:tiebreak_id) { tiebreak_id } - its(:tiebreak_id) { should eq tiebreak_id } - its(:criteria) { should eq(criteria) } - its(:type) { should eq(:next) } - its(:to_s) { should eq(base64_string) } + its(:tiebreak_id) { is_expected.to eq tiebreak_id } + its(:criteria) { is_expected.to eq(criteria) } + its(:type) { is_expected.to eq(:next) } + its(:to_s) { is_expected.to eq(base64_string) } end + context 'an invalid field cursor' do it 'raises ArgumentError' do expect do @@ -152,6 +175,7 @@ end.to raise_error Mongoid::Scroll::Errors::InvalidBase64CursorError end end + context 'an invalid cursor' do it 'raises a Mongoid::Scroll::Errors::InvalidBase64CursorError with an invalid Base64 string' do expect { Mongoid::Scroll::Base64EncodedCursor.new 'invalid' }.to raise_error Mongoid::Scroll::Errors::InvalidBase64CursorError, /The cursor supplied is invalid: invalid./ @@ -162,74 +186,94 @@ end end end + context 'from_record' do context 'a string field cursor' do + subject do + Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: field_name, field_type: field_type + end + let(:field_type) { String } let(:field_value) { 'a string' } let(:field_name) { 'a_string' } let(:feed_item) { Feed::Item.create!(field_name => field_value) } + + its(:value) { is_expected.to eq field_value } + its(:field_name) { is_expected.to eq field_name } + its(:field_type) { is_expected.to eq field_type.to_s } + end + + context 'an id field cursor' do subject do Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: field_name, field_type: field_type end - its(:value) { should eq field_value } - its(:field_name) { should eq field_name } - its(:field_type) { should eq field_type.to_s } - end - context 'an id field cursor' do + let(:field_type) { BSON::ObjectId } let(:field_name) { 'id' } let(:feed_item) { Feed::Item.create! } + + its(:value) { is_expected.to eq feed_item._id } + its(:field_type) { is_expected.to eq field_type.to_s } + end + + context 'an integer field cursor' do subject do Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: field_name, field_type: field_type end - its(:value) { should eq feed_item._id } - its(:field_type) { should eq field_type.to_s } - end - context 'an integer field cursor' do + let(:field_type) { Integer } let(:field_value) { 10 } let(:field_name) { 'a_integer' } let(:feed_item) { Feed::Item.create!(field_name => field_value) } + + its(:value) { is_expected.to eq field_value } + its(:field_type) { is_expected.to eq field_type.to_s } + end + + context 'a date/time field cursor' do subject do Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: field_name, field_type: field_type end - its(:value) { should eq field_value } - its(:field_type) { should eq field_type.to_s } - end - context 'a date/time field cursor' do + let(:field_type) { DateTime } let(:field_value) { DateTime.new(2013, 12, 21, 1, 42, 3, 'UTC') } let(:field_name) { 'a_datetime' } let(:feed_item) { Feed::Item.create!(field_name => field_value) } + + its(:value) { is_expected.to eq field_value } + its(:field_type) { is_expected.to eq field_type.to_s } + end + + context 'a date field cursor' do subject do Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: field_name, field_type: field_type end - its(:value) { should eq field_value } - its(:field_type) { should eq field_type.to_s } - end - context 'a date field cursor' do + let(:field_type) { Date } let(:field_value) { Date.new(2013, 12, 21) } let(:field_name) { 'a_date' } let(:feed_item) { Feed::Item.create!(field_name => field_value) } + + its(:value) { is_expected.to eq field_value } + end + + context 'a time field cursor' do subject do Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: field_name, field_type: field_type end - its(:value) { should eq field_value } - end - context 'a time field cursor' do + let(:field_type) { Time } let(:field_value) { Time.new(2013, 12, 21, 1, 2, 3) } let(:field_name) { 'a_time' } let(:feed_item) { Feed::Item.create!(field_name => field_value) } - subject do - Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: field_name, field_type: field_type - end - its(:value) { should eq field_value } - its(:field_type) { should eq field_type.to_s } + + its(:value) { is_expected.to eq field_value } + its(:field_type) { is_expected.to eq field_type.to_s } end + context 'an array field cursor' do let(:feed_item) { Feed::Item.create!(a_array: %w[x y]) } + it 'is not supported' do expect do Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: 'a_array', field_type: Array @@ -242,16 +286,19 @@ cursor = Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: 'id', field_type: BSON::ObjectId, type: :previous expect(Mongoid::Scroll::Base64EncodedCursor.new(cursor.to_s).type).to eq(:previous) end + context 'a cursor with previous set to true' do - let(:field_type) { BSON::ObjectId } - let(:field_name) { 'id' } - let(:feed_item) { Feed::Item.create! } subject do Mongoid::Scroll::Base64EncodedCursor.from_record feed_item, field_name: field_name, field_type: field_type, type: :previous end - its(:value) { should eq feed_item._id } - its(:field_type) { should eq field_type.to_s } - its(:type) { should eq(:previous) } + + let(:field_type) { BSON::ObjectId } + let(:field_name) { 'id' } + let(:feed_item) { Feed::Item.create! } + + its(:value) { is_expected.to eq feed_item._id } + its(:field_type) { is_expected.to eq field_type.to_s } + its(:type) { is_expected.to eq(:previous) } end end end diff --git a/spec/mongoid/criteria_spec.rb b/spec/mongoid/criteria_spec.rb index 2b30853..ae8d56d 100644 --- a/spec/mongoid/criteria_spec.rb +++ b/spec/mongoid/criteria_spec.rb @@ -7,19 +7,23 @@ subject do Feed::Item.desc(:name).asc(:value) end + it ':scroll' do expect(subject).to respond_to(:scroll) end + it 'raises Mongoid::Scroll::Errors::MultipleSortFieldsError' do expect do subject.scroll(cursor_type) end.to raise_error Mongoid::Scroll::Errors::MultipleSortFieldsError, /You're attempting to scroll over data with a sort order that includes multiple fields: name, value./ end end + context 'with different sort fields between the cursor and the criteria' do subject do Feed::Item.desc(:name) end + it 'raises Mongoid::Scroll::Errors::MismatchedSortFieldsError' do record = Feed::Item.create! cursor = cursor_type.from_record(record, field: record.fields['a_string']) @@ -27,16 +31,19 @@ expect { subject.scroll(cursor) }.to raise_error Mongoid::Scroll::Errors::MismatchedSortFieldsError, error_string end end + context 'with no sort' do subject do Feed::Item.all end + it 'adds a default sort by _id' do expect(subject.scroll(cursor_type).options[:sort]).to eq('_id' => 1) end end + context 'with data' do - before :each do + before do 10.times do |i| Feed::Item.create!( name: i.to_s, @@ -48,6 +55,7 @@ ) end end + context 'default' do it 'scrolls all' do records = [] @@ -57,6 +65,7 @@ expect(records.size).to eq 10 expect(records).to eq Feed::Item.all.to_a end + it 'does not change original criteria' do criteria = Feed::Item.where(:a_time.gt => Time.new(2013, 7, 22, 1, 2, 3)) original_criteria = criteria.dup @@ -72,6 +81,7 @@ expect(criteria).to eq original_criteria end end + context 'with a foreign key' do it 'sorts by object id' do records = [] @@ -79,16 +89,18 @@ expect(records).not_to be_empty end end + { a_string: String, a_integer: Integer, a_date: Date, a_datetime: DateTime }.each_pair do |field_name, field_type| context field_type do it 'scrolls all with a block' do records = [] - Feed::Item.asc(field_name).scroll(cursor_type) do |record, iterator| + Feed::Item.asc(field_name).scroll(cursor_type) do |record, _iterator| records << record end expect(records.size).to eq 10 expect(records).to eq Feed::Item.all.to_a end + it 'scrolls all with a break' do records = [] cursor = nil @@ -104,6 +116,7 @@ expect(records.size).to eq 10 expect(records).to eq Feed::Item.all.to_a end + it 'scrolls from a cursor' do last_record = nil cursor = nil @@ -115,6 +128,7 @@ from_item = Feed::Item.asc(field_name).scroll(cursor).to_a.first expect(from_item).to eq sixth_item end + it 'includes the current record when Mongoid::Scroll::Cursor#include_current is true' do last_record = nil cursor = nil @@ -127,6 +141,7 @@ from_item = Feed::Item.asc(field_name).scroll(cursor).to_a.first expect(from_item).to eq fifth_item end + it 'scrolls in descending order' do records = [] Feed::Item.desc(field_name).limit(3).scroll(cursor_type) do |record, _iterator| @@ -135,14 +150,16 @@ expect(records.size).to eq 3 expect(records).to eq Feed::Item.desc(field_name).limit(3).to_a end + it 'map' do record = Feed::Item.desc(field_name).limit(3).scroll(cursor_type).map { |r| r }.last - expect(record).to_not be nil + expect(record).not_to be_nil cursor = cursor_type.from_record(record, field_type: field_type, field_name: field_name) - expect(cursor).to_not be nil + expect(cursor).not_to be_nil expect(cursor.tiebreak_id).to eq record.id expect(cursor.value).to eq record.send(field_name) end + it 'can be reused' do ids = Feed::Item.asc(field_name).limit(2).map(&:id) Feed::Item.asc(field_name).limit(2).scroll(cursor_type) do |_, iterator| @@ -151,6 +168,7 @@ break end end + it 'can be re-created and reused' do ids = Feed::Item.asc(field_name).limit(2).map(&:id) Feed::Item.asc(field_name).limit(2).scroll(cursor_type) do |_, iterator| @@ -160,6 +178,7 @@ break end end + it 'can scroll back with the previous cursor' do first_iterator = nil second_iterator = nil @@ -181,6 +200,7 @@ expect(Feed::Item.asc(field_name).limit(2).scroll(second_iterator.previous_cursor)).to eq(records.limit(2)) expect(Feed::Item.asc(field_name).limit(2).scroll(third_iterator.previous_cursor)).to eq(records.skip(2).limit(2)) end + it 'can loop over the same records with the current cursor' do current_cursor = nil cursor = cursor_type.from_record Feed::Item.skip(4).first, field_name: field_name, field_type: field_type, include_current: true @@ -191,6 +211,7 @@ expect(Feed::Item.asc(field_name).limit(2).scroll(current_cursor).to_a).to eq(Feed::Item.asc(field_name).skip(4).limit(2).to_a) end + it 'can loop over the first records with the first page cursor' do first_cursor = nil @@ -203,8 +224,9 @@ end end end + context 'with logic in initial criteria' do - before :each do + before do 3.times do |i| Feed::Item.create!( name: "Feed Item #{i}", @@ -224,6 +246,7 @@ a_time: Time.new(2014, 2, 2, 1, 2, 3) ) end + it 'respects original criteria with OR logic' do criteria = Feed::Item.where( '$or' => [{ :a_time.gt => Time.new(2015, 7, 22, 1, 2, 3) }, { :a_time.lte => Time.new(2015, 7, 22, 1, 2, 3), :a_date.gte => Date.new(2015, 7, 30) }] @@ -245,11 +268,13 @@ expect(records.map(&:name)).to eq ['Feed Item 2'] end end + context 'with embeddable objects' do before do @item = Feed::Item.create! a_integer: 1, name: 'item' @embedded_item = Feed::EmbeddedItem.create! name: 'embedded', item: @item end + it 'respects embedded queries' do records = [] criteria = @item.embedded_items.limit(2) @@ -260,17 +285,20 @@ expect(records.map(&:name)).to eq ['embedded'] end end + context 'with overlapping data', if: MongoDB.mmapv1? do - before :each do + before do 3.times { Feed::Item.create! a_integer: 5 } Feed::Item.first.update_attributes!(name: Array(1000).join('a')) end + it 'natural order is different from order by id' do # natural order isn't necessarily going to be the same as _id order # if a document is updated and grows in size, it may need to be relocated and # thus cause the natural order to change - expect(Feed::Item.order_by('$natural' => 1).to_a).to_not eq(Feed::Item.order_by(_id: 1).to_a) + expect(Feed::Item.order_by('$natural' => 1).to_a).not_to eq(Feed::Item.order_by(_id: 1).to_a) end + [{ a_integer: 1 }, { a_integer: -1 }].each do |sort_order| it "scrolls by #{sort_order}" do records = [] @@ -288,10 +316,12 @@ end end end + context 'with several records having the same value' do - before :each do + before do 3.times { Feed::Item.create! a_integer: 5 } end + it 'returns records from the current one when Mongoid::Scroll::Cursor#include_current is true' do _first_item, second_item, third_item = Feed::Item.asc(:a_integer).to_a cursor = Mongoid::Scroll::Cursor.from_record(second_item, field: Feed::Item.fields['a_integer']) @@ -300,6 +330,7 @@ expect(items).to eq([second_item, third_item]) end end + context 'with DateTime with a milisecond precision' do let!(:item_1) { Feed::Item.create!(a_datetime: DateTime.new(2013, 1, 21, 1, 42, 3.1, 'UTC')) } let!(:item_2) { Feed::Item.create!(a_datetime: DateTime.new(2013, 1, 21, 1, 42, 3.2, 'UTC')) } diff --git a/spec/mongoid/cursor_spec.rb b/spec/mongoid/cursor_spec.rb index 4e59771..4fa0c70 100644 --- a/spec/mongoid/cursor_spec.rb +++ b/spec/mongoid/cursor_spec.rb @@ -5,125 +5,158 @@ subject do Mongoid::Scroll::Cursor.new nil, field_name: 'a_string', field_type: String end - its(:tiebreak_id) { should be_nil } - its(:value) { should be_nil } - its(:criteria) { should eq({}) } + + its(:tiebreak_id) { is_expected.to be_nil } + its(:value) { is_expected.to be_nil } + its(:criteria) { is_expected.to eq({}) } end + context 'an invalid cursor' do it 'raises InvalidCursorError' do expect { Mongoid::Scroll::Cursor.new 'invalid', field_name: 'a_string', field_type: String }.to raise_error Mongoid::Scroll::Errors::InvalidCursorError, /The cursor supplied is invalid: invalid./ end end + context 'an id field cursor' do - let(:feed_item) { Feed::Item.create!(a_string: 'astring') } subject do Mongoid::Scroll::Cursor.new "#{feed_item.id}:#{feed_item.id}", field_name: '_id', field_type: BSON::ObjectId, direction: 1 end - its(:value) { should eq feed_item.id } - its(:tiebreak_id) { should eq feed_item.id } + + let(:feed_item) { Feed::Item.create!(a_string: 'astring') } + + its(:value) { is_expected.to eq feed_item.id } + its(:tiebreak_id) { is_expected.to eq feed_item.id } + its(:criteria) do - should eq('$or' => [ - { '_id' => { '$gt' => BSON::ObjectId(feed_item.id.to_s) } } - ]) + is_expected.to eq('$or' => [ + { '_id' => { '$gt' => BSON::ObjectId(feed_item.id.to_s) } } + ]) end end + context 'a string field cursor' do - let(:feed_item) { Feed::Item.create!(a_string: 'astring') } subject do Mongoid::Scroll::Cursor.new "#{feed_item.a_string}:#{feed_item.id}", field_name: 'a_string', field_type: String end - its(:value) { should eq feed_item.a_string } - its(:tiebreak_id) { should eq feed_item.id } + + let(:feed_item) { Feed::Item.create!(a_string: 'astring') } + + its(:value) { is_expected.to eq feed_item.a_string } + its(:tiebreak_id) { is_expected.to eq feed_item.id } + its(:criteria) do - should eq('$or' => [ - { 'a_string' => { '$gt' => feed_item.a_string } }, - { 'a_string' => feed_item.a_string, '_id' => { '$gt' => feed_item.id } } - ]) + is_expected.to eq('$or' => [ + { 'a_string' => { '$gt' => feed_item.a_string } }, + { 'a_string' => feed_item.a_string, '_id' => { '$gt' => feed_item.id } } + ]) end end + context 'an integer field cursor' do - let(:feed_item) { Feed::Item.create!(a_integer: 10) } subject do Mongoid::Scroll::Cursor.new "#{feed_item.a_integer}:#{feed_item.id}", field_name: 'a_integer', field_type: Integer end - its(:value) { should eq feed_item.a_integer } - its(:tiebreak_id) { should eq feed_item.id } + + let(:feed_item) { Feed::Item.create!(a_integer: 10) } + + its(:value) { is_expected.to eq feed_item.a_integer } + its(:tiebreak_id) { is_expected.to eq feed_item.id } + its(:criteria) do - should eq('$or' => [ - { 'a_integer' => { '$gt' => feed_item.a_integer } }, - { 'a_integer' => feed_item.a_integer, '_id' => { '$gt' => feed_item.id } } - ]) + is_expected.to eq('$or' => [ + { 'a_integer' => { '$gt' => feed_item.a_integer } }, + { 'a_integer' => feed_item.a_integer, '_id' => { '$gt' => feed_item.id } } + ]) end end + context 'a date/time field cursor' do - let(:feed_item) { Feed::Item.create!(a_datetime: DateTime.new(2013, 12, 21, 1, 42, 3, 'UTC')) } subject do Mongoid::Scroll::Cursor.new "#{feed_item.a_datetime.utc.to_f.round(3)}:#{feed_item.id}", field_name: 'a_datetime', field_type: DateTime end - its(:value) { should eq feed_item.a_datetime } - its(:tiebreak_id) { should eq feed_item.id } - its(:to_s) { should eq "#{feed_item.a_datetime.utc.to_f.round(3)}:#{feed_item.id}" } + + let(:feed_item) { Feed::Item.create!(a_datetime: DateTime.new(2013, 12, 21, 1, 42, 3, 'UTC')) } + + its(:value) { is_expected.to eq feed_item.a_datetime } + its(:tiebreak_id) { is_expected.to eq feed_item.id } + its(:to_s) { is_expected.to eq "#{feed_item.a_datetime.utc.to_f.round(3)}:#{feed_item.id}" } + its(:criteria) do - should eq('$or' => [ - { 'a_datetime' => { '$gt' => feed_item.a_datetime } }, - { 'a_datetime' => feed_item.a_datetime, '_id' => { '$gt' => feed_item.id } } - ]) + is_expected.to eq('$or' => [ + { 'a_datetime' => { '$gt' => feed_item.a_datetime } }, + { 'a_datetime' => feed_item.a_datetime, '_id' => { '$gt' => feed_item.id } } + ]) end end + context 'a date field cursor' do - let(:feed_item) { Feed::Item.create!(a_date: Date.new(2013, 12, 21)) } subject do Mongoid::Scroll::Cursor.new "#{feed_item.a_date.to_datetime.to_i}:#{feed_item.id}", field_name: 'a_date', field_type: Date end - its(:value) { should eq feed_item.a_date } - its(:tiebreak_id) { should eq feed_item.id } - its(:to_s) { should eq "#{feed_item.a_date.to_datetime.to_i}:#{feed_item.id}" } + + let(:feed_item) { Feed::Item.create!(a_date: Date.new(2013, 12, 21)) } + + its(:value) { is_expected.to eq feed_item.a_date } + its(:tiebreak_id) { is_expected.to eq feed_item.id } + its(:to_s) { is_expected.to eq "#{feed_item.a_date.to_datetime.to_i}:#{feed_item.id}" } + its(:criteria) do - should eq('$or' => [ - { 'a_date' => { '$gt' => feed_item.a_date.to_datetime } }, - { 'a_date' => feed_item.a_date.to_datetime, '_id' => { '$gt' => feed_item.id } } - ]) + is_expected.to eq('$or' => [ + { 'a_date' => { '$gt' => feed_item.a_date.to_datetime } }, + { 'a_date' => feed_item.a_date.to_datetime, '_id' => { '$gt' => feed_item.id } } + ]) end end + context 'a time field cursor' do - let(:feed_item) { Feed::Item.create!(a_time: Time.new(2013, 12, 21, 1, 2, 3)) } subject do Mongoid::Scroll::Cursor.new "#{feed_item.a_time.to_f.round(3)}:#{feed_item.id}", field_name: 'a_time', field_type: Time end - its(:value) { should eq feed_item.a_time } - its(:tiebreak_id) { should eq feed_item.id } - its(:to_s) { should eq "#{feed_item.a_time.to_f.round(3)}:#{feed_item.id}" } + + let(:feed_item) { Feed::Item.create!(a_time: Time.new(2013, 12, 21, 1, 2, 3)) } + + its(:value) { is_expected.to eq feed_item.a_time } + its(:tiebreak_id) { is_expected.to eq feed_item.id } + its(:to_s) { is_expected.to eq "#{feed_item.a_time.to_f.round(3)}:#{feed_item.id}" } + its(:criteria) do - should eq('$or' => [ - { 'a_time' => { '$gt' => feed_item.a_time } }, - { 'a_time' => feed_item.a_time, '_id' => { '$gt' => feed_item.id } } - ]) + is_expected.to eq('$or' => [ + { 'a_time' => { '$gt' => feed_item.a_time } }, + { 'a_time' => feed_item.a_time, '_id' => { '$gt' => feed_item.id } } + ]) end end + context 'a time field cursor with a field option' do - let(:feed_item) { Feed::Item.create!(a_time: Time.new(2013, 12, 21, 1, 2, 3)) } subject do Mongoid::Scroll::Cursor.new "#{feed_item.a_time.to_f.round(3)}:#{feed_item.id}", field: Feed::Item.fields['a_time'] end - its(:value) { should eq feed_item.a_time } - its(:tiebreak_id) { should eq feed_item.id } - its(:to_s) { should eq "#{feed_item.a_time.to_f.round(3)}:#{feed_item.id}" } + + let(:feed_item) { Feed::Item.create!(a_time: Time.new(2013, 12, 21, 1, 2, 3)) } + + its(:value) { is_expected.to eq feed_item.a_time } + its(:tiebreak_id) { is_expected.to eq feed_item.id } + its(:to_s) { is_expected.to eq "#{feed_item.a_time.to_f.round(3)}:#{feed_item.id}" } + its(:criteria) do - should eq('$or' => [ - { 'a_time' => { '$gt' => feed_item.a_time } }, - { 'a_time' => feed_item.a_time, '_id' => { '$gt' => feed_item.id } } - ]) + is_expected.to eq('$or' => [ + { 'a_time' => { '$gt' => feed_item.a_time } }, + { 'a_time' => feed_item.a_time, '_id' => { '$gt' => feed_item.id } } + ]) end end + context 'an array field cursor' do let(:feed_item) { Feed::Item.create!(a_array: %w[x y]) } + it 'is not supported' do expect do Mongoid::Scroll::Cursor.from_record feed_item, field_name: 'a_array', field_type: Array end.to raise_error Mongoid::Scroll::Errors::UnsupportedFieldTypeError, /The type of the field 'a_array' is not supported: Array./ end end + context 'an invalid field cursor' do it 'raises ArgumentError' do expect do @@ -131,31 +164,38 @@ end.to raise_error ArgumentError end end + context 'an invalid type cursor' do let(:feed_item) { Feed::Item.create!(a_string: 'astring') } + it 'raises Mongoid::Scroll::Errors::UnsupportedTypeError' do expect do Mongoid::Scroll::Cursor.new "#{feed_item.a_string}:#{feed_item.id}", field_name: 'a_string', field_type: String, include_current: true, type: :invalid end.to raise_error Mongoid::Scroll::Errors::UnsupportedTypeError, /The type supplied in the cursor is not supported: invalid./ end end + context 'a cursor with include_current set to true' do - let(:feed_item) { Feed::Item.create!(a_string: 'astring') } subject do Mongoid::Scroll::Cursor.new "#{feed_item.a_string}:#{feed_item.id}", field_name: 'a_string', field_type: String, include_current: true end - its(:value) { should eq 'astring' } - its(:tiebreak_id) { should eq feed_item.id } + + let(:feed_item) { Feed::Item.create!(a_string: 'astring') } + + its(:value) { is_expected.to eq 'astring' } + its(:tiebreak_id) { is_expected.to eq feed_item.id } + its(:criteria) do - should eq('$or' => [ - { 'a_string' => { '$gt' => 'astring' } }, - { '_id' => { '$gte' => BSON::ObjectId(feed_item.id.to_s) }, 'a_string' => 'astring' } - ]) + is_expected.to eq('$or' => [ + { 'a_string' => { '$gt' => 'astring' } }, + { '_id' => { '$gte' => BSON::ObjectId(feed_item.id.to_s) }, 'a_string' => 'astring' } + ]) end end + context 'criteria' do context 'with data' do - before :each do + before do 3.times do |i| Feed::Item.create!( name: "Feed Item #{i}", @@ -167,6 +207,7 @@ a_time: Time.new(2014, 2, 2, 1, 2, 3) ) end + it 'merges cursor criteria when no sort field is given' do criteria = Feed::Item.where(:a_time.gt => Time.new(2013, 7, 22, 1, 2, 3)) feed_item = Feed::Item.where(name: 'Feed Item 1').first diff --git a/spec/mongoid/scroll_spec.rb b/spec/mongoid/scroll_spec.rb index c13788b..a7d395b 100644 --- a/spec/mongoid/scroll_spec.rb +++ b/spec/mongoid/scroll_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' describe Mongoid::Scroll::VERSION do - it { should_not be_nil } + it { is_expected.not_to be_nil } end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4b62501..797e782 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -22,7 +22,7 @@ Mongoid.logger.level = Logger::INFO Mongo::Logger.logger.level = Logger::INFO if Mongoid::Compatibility::Version.mongoid5_or_newer? end - config.before :each do + config.before do DatabaseCleaner.clean end end