Skip to content

Commit d58d8e1

Browse files
authored
MONGOID-5785 merging the default scope with named scopes is a bug (#5832)
1 parent 08547c3 commit d58d8e1

File tree

3 files changed

+104
-86
lines changed

3 files changed

+104
-86
lines changed

lib/mongoid/config.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ module Config
171171
# See https://jira.mongodb.org/browse/MONGOID-5658 for more details.
172172
option :around_callbacks_for_embeds, default: false
173173

174+
# When this flag is false, named scopes cannot unset a default scope.
175+
# This is the traditional (and default) behavior in Mongoid 9 and earlier.
176+
#
177+
# Setting this flag to true will allow named scopes to unset the default
178+
# scope. This will be the default in Mongoid 10.
179+
#
180+
# See https://jira.mongodb.org/browse/MONGOID-5785 for more details.
181+
option :allow_scopes_to_unset_default_scope, default: false
182+
174183
# Returns the Config singleton, for use in the configure DSL.
175184
#
176185
# @return [ self ] The Config singleton.

lib/mongoid/scopable.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,13 @@ def define_scope_method(name)
294294
scope = instance_exec(*args, **kwargs, &scoping[:scope])
295295
extension = scoping[:extension]
296296
to_merge = scope || queryable
297-
criteria = to_merge.empty_and_chainable? ? to_merge : with_default_scope.merge(to_merge)
297+
298+
criteria = if Mongoid.allow_scopes_to_unset_default_scope
299+
to_merge
300+
else
301+
to_merge.empty_and_chainable? ? to_merge : with_default_scope.merge(to_merge)
302+
end
303+
298304
criteria.extend(extension)
299305
criteria
300306
end

spec/mongoid/scopable_spec.rb

Lines changed: 88 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33

44
require "spec_helper"
55

6+
# Retrieve the singleton class for the given class.
7+
def singleton_class_for(klass)
8+
class <<klass; self; end
9+
end
10+
11+
# Helper method for removing a declared scope
12+
def remove_scope(klass, scope)
13+
if klass._declared_scopes[scope]
14+
singleton_class_for(klass).remove_method(scope)
15+
klass._declared_scopes.delete(scope)
16+
end
17+
end
18+
619
describe Mongoid::Scopable do
720

821
describe ".default_scope" do
@@ -349,6 +362,10 @@ def self.default_scope
349362
Band.create!(name: 'testing')
350363
end
351364

365+
after do
366+
remove_scope(Band, :tests)
367+
end
368+
352369
it 'applies the collation' do
353370
expect(Band.tests.first['name']).to eq('testing')
354371
end
@@ -365,10 +382,7 @@ def add_origin
365382
end
366383

367384
after do
368-
class << Band
369-
undef_method :active
370-
end
371-
Band._declared_scopes.clear
385+
remove_scope(Band, :active)
372386
end
373387

374388
let(:scope) do
@@ -390,10 +404,7 @@ class << Band
390404
end
391405

392406
after do
393-
class << Record
394-
undef_method :tool
395-
end
396-
Record._declared_scopes.clear
407+
remove_scope(Record, :tool)
397408
end
398409

399410
context "when calling the scope" do
@@ -423,10 +434,7 @@ class << Record
423434
end
424435

425436
after do
426-
class << Band
427-
undef_method :active
428-
end
429-
Band._declared_scopes.clear
437+
remove_scope(Band, :active)
430438
end
431439

432440
it "adds a method for the scope" do
@@ -461,10 +469,7 @@ class << Band
461469
end
462470

463471
after do
464-
class << Band
465-
undef_method :english
466-
end
467-
Band._declared_scopes.clear
472+
remove_scope(Band, :english)
468473
end
469474

470475
let(:scope) do
@@ -527,10 +532,7 @@ class << Band
527532
config_override :scope_overwrite_exception, true
528533

529534
after do
530-
class << Band
531-
undef_method :active
532-
end
533-
Band._declared_scopes.clear
535+
remove_scope(Band, :active)
534536
end
535537

536538
it "raises an exception" do
@@ -545,10 +547,7 @@ class << Band
545547
config_override :scope_overwrite_exception, false
546548

547549
after do
548-
class << Band
549-
undef_method :active
550-
end
551-
Band._declared_scopes.clear
550+
remove_scope(Band, :active)
552551
end
553552

554553
it "raises no exception" do
@@ -589,10 +588,7 @@ def add_origin
589588
end
590589

591590
after do
592-
class << Band
593-
undef_method :active
594-
end
595-
Band._declared_scopes.clear
591+
remove_scope(Band, :active)
596592
end
597593

598594
let(:scope) do
@@ -652,11 +648,8 @@ class << Band
652648
end
653649

654650
after do
655-
class << Band
656-
undef_method :active
657-
undef_method :named_by
658-
end
659-
Band._declared_scopes.clear
651+
remove_scope(Band, :active)
652+
remove_scope(Band, :named_by)
660653
end
661654

662655
it "adds a method for the scope" do
@@ -698,10 +691,7 @@ class << Band
698691
end
699692

700693
after do
701-
class << Band
702-
undef_method :english
703-
end
704-
Band._declared_scopes.clear
694+
remove_scope(Band, :english)
705695
end
706696

707697
let(:scope) do
@@ -775,15 +765,9 @@ class << Band
775765
end
776766

777767
after do
778-
class << Article
779-
undef_method :is_public
780-
undef_method :authored
781-
end
782-
Article._declared_scopes.clear
783-
class << Author
784-
undef_method :author
785-
end
786-
Author._declared_scopes.clear
768+
remove_scope(Article, :is_public)
769+
remove_scope(Article, :authored)
770+
remove_scope(Author, :author)
787771
end
788772

789773
context "when calling another model's scope from within a scope" do
@@ -816,10 +800,7 @@ class << Author
816800
config_override :scope_overwrite_exception, true
817801

818802
after do
819-
class << Band
820-
undef_method :active
821-
end
822-
Band._declared_scopes.clear
803+
remove_scope(Band, :active)
823804
end
824805

825806
it "raises an exception" do
@@ -834,10 +815,7 @@ class << Band
834815
config_override :scope_overwrite_exception, false
835816

836817
after do
837-
class << Band
838-
undef_method :active
839-
end
840-
Band._declared_scopes.clear
818+
remove_scope(Band, :active)
841819
end
842820

843821
it "raises no exception" do
@@ -867,11 +845,8 @@ class << Band
867845
end
868846

869847
after do
870-
class << Band
871-
undef_method :xxx
872-
undef_method :yyy
873-
end
874-
Band._declared_scopes.clear
848+
remove_scope(Band, :xxx)
849+
remove_scope(Band, :yyy)
875850
end
876851

877852
let(:criteria) do
@@ -901,15 +876,8 @@ class << Band
901876
end
902877

903878
after do
904-
class << Shape
905-
undef_method :located_at
906-
end
907-
Shape._declared_scopes.clear
908-
909-
class << Circle
910-
undef_method :with_radius
911-
end
912-
Circle._declared_scopes.clear
879+
remove_scope(Shape, :located_at)
880+
remove_scope(Circle, :with_radius)
913881
end
914882

915883
let(:shape_scope_keys) do
@@ -950,16 +918,9 @@ class << Circle
950918
end
951919

952920
after do
953-
class << Shape
954-
undef_method :visible
955-
undef_method :large
956-
end
957-
Shape._declared_scopes.clear
958-
959-
class << Circle
960-
undef_method :large
961-
end
962-
Circle._declared_scopes.clear
921+
remove_scope(Shape, :visible)
922+
remove_scope(Shape, :large)
923+
remove_scope(Circle, :large)
963924
end
964925

965926
it "uses subclass context for all the other used scopes" do
@@ -1099,9 +1060,7 @@ class U1 < Kaleidoscope; end
10991060
end
11001061

11011062
after do
1102-
class << Band
1103-
undef_method :active
1104-
end
1063+
remove_scope(Band, :active)
11051064
end
11061065

11071066
let(:unscoped) do
@@ -1143,10 +1102,7 @@ class U2 < Band; end
11431102
end
11441103

11451104
after do
1146-
class << Band
1147-
undef_method :skipped
1148-
end
1149-
Band._declared_scopes.clear
1105+
remove_scope(Band, :skipped)
11501106
end
11511107

11521108
it "does not allow the default scope to be applied" do
@@ -1290,4 +1246,51 @@ class << Band
12901246
end
12911247
end
12921248
end
1249+
1250+
describe "scoped queries" do
1251+
context "with a default scope" do
1252+
let(:criteria) do
1253+
Band.where(name: "Depeche Mode")
1254+
end
1255+
1256+
before do
1257+
Band.default_scope ->{ criteria }
1258+
Band.scope :unscoped_everyone, -> { unscoped }
1259+
Band.scope :removed_default, -> { scoped.remove_scoping(all) }
1260+
1261+
Band.create name: 'Depeche Mode'
1262+
Band.create name: 'They Might Be Giants'
1263+
end
1264+
1265+
after do
1266+
Band.default_scoping = nil
1267+
remove_scope Band, :unscoped_everyone
1268+
remove_scope Band, :removed_default
1269+
end
1270+
1271+
context "when allow_scopes_to_unset_default_scope == false" do # default for <= 9
1272+
config_override :allow_scopes_to_unset_default_scope, false
1273+
1274+
it 'merges the default scope into the query with unscoped' do
1275+
expect(Band.unscoped_everyone.selector).to include('name' => 'Depeche Mode')
1276+
end
1277+
1278+
it 'merges the default scope into the query with remove_scoping' do
1279+
expect(Band.removed_default.selector).to include('name' => 'Depeche Mode')
1280+
end
1281+
end
1282+
1283+
context "when allow_scopes_to_unset_default_scope == true" do # default for >= 10
1284+
config_override :allow_scopes_to_unset_default_scope, true
1285+
1286+
it 'does not merge the default scope into the query with unscoped' do
1287+
expect(Band.unscoped_everyone.selector).not_to include('name' => 'Depeche Mode')
1288+
end
1289+
1290+
it 'does not merge merges the default scope into the query with remove_scoping' do
1291+
expect(Band.removed_default.selector).not_to include('name' => 'Depeche Mode')
1292+
end
1293+
end
1294+
end
1295+
end
12931296
end

0 commit comments

Comments
 (0)