From 1a9c75aacd6074ccb52e77c896bc7a8e50e51850 Mon Sep 17 00:00:00 2001 From: "Daniel (dB.) Doubrovkine" Date: Thu, 15 Oct 2026 08:13:13 -0400 Subject: [PATCH] Raise NoSuchFieldError for missing sort and cursor fields. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 1 + README.md | 2 ++ lib/mongoid/criteria/scrollable.rb | 2 +- lib/mongoid/criteria/scrollable/fields.rb | 6 +++++ spec/mongoid/criteria_spec.rb | 31 +++++++++++++++++++++++ 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a643986..fa2eee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### 2.0.1 (Next) +* [#48](https://github.com/mongoid/mongoid-scroll/issues/48): Raise `NoSuchFieldError` when a sort or cursor refers to a field no longer defined on the model - [@dblock](https://github.com/dblock). * [#51](https://github.com/mongoid/mongoid-scroll/pull/51): Report SimpleCov coverage to Coveralls once per build without duplicate gem uploads and refresh CI and coverage badges - [@dblock](https://github.com/dblock). * [#50](https://github.com/mongoid/mongoid-scroll/pull/50): Modernize CI through Ruby 4.0, Mongoid 9 and MongoDB 8; fix Mongoid 9 dependency selection, database cleaner compatibility and invalid Danger workflow references - [@dblock](https://github.com/dblock). * [#49](https://github.com/mongoid/mongoid-scroll/pull/49): Migrate Danger to danger-pr-comment workflow - [@dblock](https://github.com/dblock). diff --git a/README.md b/README.md index 46cf1e4..2030e1d 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,8 @@ end Resume iterating using the saved cursor and save the cursor to go backwards. +If the sort field or a saved cursor's field is no longer defined on the model, `scroll` raises `Mongoid::Scroll::Errors::NoSuchFieldError`. A defined field with a `nil` value is not considered missing. + ```ruby Feed::Item.desc(:position).limit(5).scroll(saved_iterator.next_cursor) do |record, iterator| # each record, one-by-one diff --git a/lib/mongoid/criteria/scrollable.rb b/lib/mongoid/criteria/scrollable.rb index 175fe78..8fa5933 100644 --- a/lib/mongoid/criteria/scrollable.rb +++ b/lib/mongoid/criteria/scrollable.rb @@ -92,7 +92,7 @@ def cursor_from_record(cursor_type, record, cursor_options) def scroll_field_type(criteria) scroll_field = scroll_field(criteria) - field = criteria.klass.fields[scroll_field.to_s] + field = scroll_model_field(scroll_field) field.foreign_key? && field.object_id_field? ? BSON::ObjectId : field.type end end diff --git a/lib/mongoid/criteria/scrollable/fields.rb b/lib/mongoid/criteria/scrollable/fields.rb index 0bc3d6e..d4b0c34 100644 --- a/lib/mongoid/criteria/scrollable/fields.rb +++ b/lib/mongoid/criteria/scrollable/fields.rb @@ -5,12 +5,18 @@ module Scrollable module Fields private + def scroll_model_field(field_name) + klass.fields[field_name.to_s] || + raise(Mongoid::Scroll::Errors::NoSuchFieldError.new(field: field_name)) + end + def raise_mismatched_sort_fields_error!(cursor, criteria_cursor_options) diff = cursor.sort_options.reject { |k, v| criteria_cursor_options[k] == v } raise Mongoid::Scroll::Errors::MismatchedSortFieldsError.new(diff: diff) end def different_sort_fields?(cursor, criteria_cursor_options) + scroll_model_field(cursor.field_name) criteria_cursor_options[:field_type] = criteria_cursor_options[:field_type].to_s criteria_cursor_options[:field_name] = criteria_cursor_options[:field_name].to_s criteria_cursor_options != cursor.sort_options diff --git a/spec/mongoid/criteria_spec.rb b/spec/mongoid/criteria_spec.rb index ae8d56d..0a69551 100644 --- a/spec/mongoid/criteria_spec.rb +++ b/spec/mongoid/criteria_spec.rb @@ -42,6 +42,37 @@ end end + context 'with a missing field' do + it 'rejects a sort field that is not defined on the model' do + expect do + Feed::Item.asc(:removed_field).scroll(cursor_type) + end.to raise_error Mongoid::Scroll::Errors::NoSuchFieldError, /does not exist: removed_field/ + end + + it 'rejects a cursor whose field is no longer defined on the model' do + cursor = cursor_type.new(nil, field_name: 'removed_field', field_type: String) + expect do + Feed::Item.asc(:name).scroll(cursor) + end.to raise_error Mongoid::Scroll::Errors::NoSuchFieldError, /does not exist: removed_field/ + end + + it 'does not confuse a nil field value with a missing field' do + record = Feed::Item.create! + cursor = cursor_type.from_record(record, field: record.fields['name']) + expect { Feed::Item.asc(:name).scroll(cursor) }.not_to raise_error + end + + if cursor_type == Mongoid::Scroll::Base64EncodedCursor + it 'rejects a serialized cursor whose field is no longer defined on the model' do + cursor = cursor_type.new(nil, field_name: 'removed_field', field_type: String) + saved_cursor = cursor_type.new(cursor.to_s) + expect do + Feed::Item.asc(:name).scroll(saved_cursor) + end.to raise_error Mongoid::Scroll::Errors::NoSuchFieldError, /does not exist: removed_field/ + end + end + end + context 'with data' do before do 10.times do |i|