mongoid 9.0.1 → 9.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/config/locales/en.yml +16 -0
  3. data/lib/mongoid/association/accessors.rb +7 -2
  4. data/lib/mongoid/association/nested/one.rb +14 -1
  5. data/lib/mongoid/association/referenced/belongs_to/binding.rb +7 -1
  6. data/lib/mongoid/association/referenced/belongs_to/buildable.rb +1 -1
  7. data/lib/mongoid/association/referenced/belongs_to.rb +15 -0
  8. data/lib/mongoid/association/referenced/has_many.rb +9 -8
  9. data/lib/mongoid/association/referenced/has_one/buildable.rb +3 -8
  10. data/lib/mongoid/association/referenced/with_polymorphic_criteria.rb +41 -0
  11. data/lib/mongoid/attributes/nested.rb +2 -1
  12. data/lib/mongoid/clients/options.rb +14 -1
  13. data/lib/mongoid/clients/sessions.rb +13 -15
  14. data/lib/mongoid/composable.rb +2 -0
  15. data/lib/mongoid/document.rb +2 -0
  16. data/lib/mongoid/errors/unrecognized_model_alias.rb +53 -0
  17. data/lib/mongoid/errors/unrecognized_resolver.rb +27 -0
  18. data/lib/mongoid/errors/unregistered_class.rb +47 -0
  19. data/lib/mongoid/errors.rb +3 -0
  20. data/lib/mongoid/identifiable.rb +28 -0
  21. data/lib/mongoid/matcher.rb +15 -1
  22. data/lib/mongoid/model_resolver.rb +154 -0
  23. data/lib/mongoid/persistence_context.rb +15 -9
  24. data/lib/mongoid/railties/controller_runtime.rb +2 -2
  25. data/lib/mongoid/serializable.rb +7 -7
  26. data/lib/mongoid/threaded.rb +96 -28
  27. data/lib/mongoid/timestamps/timeless.rb +4 -1
  28. data/lib/mongoid/touchable.rb +1 -1
  29. data/lib/mongoid/traversable.rb +11 -2
  30. data/lib/mongoid/validatable/associated.rb +5 -2
  31. data/lib/mongoid/version.rb +1 -1
  32. data/spec/integration/active_job_spec.rb +24 -20
  33. data/spec/integration/app_spec.rb +9 -1
  34. data/spec/integration/associations/belongs_to_spec.rb +129 -0
  35. data/spec/integration/persistence/collection_options_spec.rb +36 -0
  36. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +4 -0
  37. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +5 -0
  38. data/spec/mongoid/association/referenced/belongs_to_spec.rb +58 -21
  39. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +4 -0
  40. data/spec/mongoid/attributes/nested_spec.rb +1 -0
  41. data/spec/mongoid/clients/options_spec.rb +127 -2
  42. data/spec/mongoid/clients/transactions_spec.rb +2 -2
  43. data/spec/mongoid/interceptable_spec.rb +12 -0
  44. data/spec/mongoid/interceptable_spec_models.rb +12 -0
  45. data/spec/mongoid/model_resolver_spec.rb +167 -0
  46. data/spec/mongoid/monkey_patches_spec.rb +1 -1
  47. data/spec/mongoid/persistence_context_spec.rb +48 -4
  48. data/spec/mongoid/railties/bson_object_id_serializer_spec.rb +18 -12
  49. data/spec/mongoid/serializable_spec.rb +16 -9
  50. data/spec/mongoid/threaded_spec.rb +24 -5
  51. data/spec/mongoid/validatable/associated_spec.rb +14 -4
  52. data/spec/rails/controller_extension/controller_runtime_spec.rb +14 -14
  53. metadata +14 -4
@@ -36,6 +36,8 @@ describe 'Mongoid application tests' do
36
36
  context 'demo application' do
37
37
  context 'sinatra' do
38
38
  it 'runs' do
39
+ skip 'https://jira.mongodb.org/browse/MONGOID-5826'
40
+
39
41
  clone_application(
40
42
  'https://github.com/mongoid/mongoid-demo',
41
43
  subdir: 'sinatra-minimal',
@@ -55,6 +57,8 @@ describe 'Mongoid application tests' do
55
57
 
56
58
  context 'rails-api' do
57
59
  it 'runs' do
60
+ skip 'https://jira.mongodb.org/browse/MONGOID-5826'
61
+
58
62
  clone_application(
59
63
  'https://github.com/mongoid/mongoid-demo',
60
64
  subdir: 'rails-api',
@@ -172,7 +176,7 @@ describe 'Mongoid application tests' do
172
176
  if (rails_version = SpecConfig.instance.rails_version) == 'master'
173
177
  else
174
178
  check_call(%w(gem list))
175
- check_call(%w(gem install rails --no-document -v) + ["~> #{rails_version}.0"])
179
+ check_call(%w(gem install rails --no-document --force -v) + ["~> #{rails_version}.0"])
176
180
  end
177
181
  end
178
182
 
@@ -319,6 +323,10 @@ describe 'Mongoid application tests' do
319
323
  end
320
324
 
321
325
  def adjust_rails_defaults(rails_version: SpecConfig.instance.rails_version)
326
+ if !rails_version.match?(/^\d+\.\d+$/)
327
+ # This must be pre-release version, we trim it
328
+ rails_version = rails_version.split('.')[0..1].join('.')
329
+ end
322
330
  if File.exist?('config/application.rb')
323
331
  lines = IO.readlines('config/application.rb')
324
332
  lines.each do |line|
@@ -2,8 +2,40 @@
2
2
  # rubocop:todo all
3
3
 
4
4
  require 'spec_helper'
5
+ require 'support/feature_sandbox'
6
+
5
7
  require_relative '../../mongoid/association/referenced/has_one_models'
6
8
 
9
+ def quarantine(context, polymorphic:, dept_aliases:, team_aliases:)
10
+ state = {}
11
+
12
+ context.before(:context) do
13
+ state[:quarantine] = FeatureSandbox.start_quarantine
14
+
15
+ # Have to eval this, because otherwise we get syntax errors when defining a class
16
+ # inside a method.
17
+ #
18
+ # I know the scissors are sharp! But I want to run with them anwyay!
19
+ Object.class_eval <<-RUBY
20
+ class SandboxManager; include Mongoid::Document; end
21
+ class SandboxDepartment; include Mongoid::Document; end
22
+ class SandboxTeam; include Mongoid::Document; end
23
+ RUBY
24
+
25
+ SandboxManager.belongs_to :unit, polymorphic: polymorphic
26
+
27
+ SandboxDepartment.identify_as *dept_aliases, resolver: polymorphic
28
+ SandboxDepartment.has_many :sandbox_managers, as: :unit
29
+
30
+ SandboxTeam.identify_as *team_aliases, resolver: polymorphic
31
+ SandboxTeam.has_one :sandbox_manager, as: :unit
32
+ end
33
+
34
+ context.after(:context) do
35
+ FeatureSandbox.end_quarantine(state[:quarantine])
36
+ end
37
+ end
38
+
7
39
  describe 'belongs_to associations' do
8
40
  context 'referencing top level classes when source class is namespaced' do
9
41
  let(:college) { HomCollege.create! }
@@ -31,4 +63,101 @@ describe 'belongs_to associations' do
31
63
  expect(instance.movie).to eq movie
32
64
  end
33
65
  end
66
+
67
+ context 'when the association is polymorphic' do
68
+ let(:dept_manager) { SandboxManager.create(unit: department) }
69
+ let(:team_manager) { SandboxManager.create(unit: team) }
70
+ let(:department) { SandboxDepartment.create }
71
+ let(:team) { SandboxTeam.create }
72
+
73
+ shared_context 'it finds the associated records' do
74
+ it 'successfully finds the manager\'s unit' do
75
+ expect(dept_manager.reload.unit).to be == department
76
+ expect(team_manager.reload.unit).to be == team
77
+ end
78
+
79
+ it 'successfully finds the unit\'s manager' do
80
+ dept_manager; team_manager # make sure these are created first...
81
+
82
+ expect(department.reload.sandbox_managers).to be == [ dept_manager ]
83
+ expect(team.reload.sandbox_manager).to be == team_manager
84
+ end
85
+ end
86
+
87
+ shared_context 'it searches for alternative aliases' do
88
+ it 'successfully finds the corresponding unit when unit_type is a different alias' do
89
+ dept_manager.update unit_type: 'sandbox_dept'
90
+ dept_manager.reload
91
+
92
+ team_manager.update unit_type: 'group'
93
+ team_manager.reload
94
+
95
+ expect(dept_manager.reload.unit_type).to be == 'sandbox_dept'
96
+ expect(dept_manager.unit).to be == department
97
+
98
+ expect(team_manager.reload.unit_type).to be == 'group'
99
+ expect(team_manager.unit).to be == team
100
+ end
101
+ end
102
+
103
+ context 'when the association uses the default resolver' do
104
+ context 'when there are no aliases given' do
105
+ quarantine(self, polymorphic: true, dept_aliases: [], team_aliases: [])
106
+
107
+ it 'populates the unit_type with the class name' do
108
+ expect(dept_manager.unit_type).to be == 'SandboxDepartment'
109
+ expect(team_manager.unit_type).to be == 'SandboxTeam'
110
+ end
111
+
112
+ it_behaves_like 'it finds the associated records'
113
+ end
114
+
115
+ context 'when there are multiple aliases given' do
116
+ quarantine(self, polymorphic: true, dept_aliases: %w[ dept sandbox_dept ], team_aliases: %w[ team group ])
117
+
118
+ it 'populates the unit_type with the first alias' do
119
+ expect(dept_manager.unit_type).to be == 'dept'
120
+ expect(team_manager.unit_type).to be == 'team'
121
+ end
122
+
123
+ it_behaves_like 'it finds the associated records'
124
+ it_behaves_like 'it searches for alternative aliases'
125
+ end
126
+ end
127
+
128
+ context 'when the association uses a registered resolver' do
129
+ before(:context) { Mongoid::ModelResolver.register_resolver Mongoid::ModelResolver.new, :sandbox }
130
+ quarantine(self, polymorphic: :sandbox, dept_aliases: %w[ dept sandbox_dept ], team_aliases: %w[ team group ])
131
+
132
+ it 'does not include the aliases in the default resolver' do
133
+ expect(Mongoid::ModelResolver.instance.keys_for(SandboxDepartment.new)).not_to include('dept')
134
+ end
135
+
136
+ it 'populates the unit_type with the first alias' do
137
+ expect(dept_manager.unit_type).to be == 'dept'
138
+ expect(team_manager.unit_type).to be == 'team'
139
+ end
140
+
141
+ it_behaves_like 'it finds the associated records'
142
+ it_behaves_like 'it searches for alternative aliases'
143
+ end
144
+
145
+ context 'when the association uses an unregistered resolver' do
146
+ quarantine(self, polymorphic: Mongoid::ModelResolver.new,
147
+ dept_aliases: %w[ dept sandbox_dept ],
148
+ team_aliases: %w[ team group ])
149
+
150
+ it 'does not include the aliases in the default resolver' do
151
+ expect(Mongoid::ModelResolver.instance.keys_for(SandboxDepartment.new)).not_to include('dept')
152
+ end
153
+
154
+ it 'populates the unit_type with the first alias' do
155
+ expect(dept_manager.unit_type).to be == 'dept'
156
+ expect(team_manager.unit_type).to be == 'team'
157
+ end
158
+
159
+ it_behaves_like 'it finds the associated records'
160
+ it_behaves_like 'it searches for alternative aliases'
161
+ end
162
+ end
34
163
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ # rubocop:disable RSpec/LeakyConstantDeclaration
6
+ # rubocop:disable Lint/ConstantDefinitionInBlock
7
+ describe 'Collection options' do
8
+ before(:all) do
9
+ class CollectionOptionsCapped
10
+ include Mongoid::Document
11
+
12
+ store_in collection_options: {
13
+ capped: true,
14
+ size: 25_600
15
+ }
16
+ end
17
+ end
18
+
19
+ after(:all) do
20
+ CollectionOptionsCapped.collection.drop
21
+ Mongoid.deregister_model(CollectionOptionsCapped)
22
+ Object.send(:remove_const, :CollectionOptionsCapped)
23
+ end
24
+
25
+ before do
26
+ CollectionOptionsCapped.collection.drop
27
+ # We should create the collection explicitly to apply collection options.
28
+ CollectionOptionsCapped.create_collection
29
+ end
30
+
31
+ it 'creates a document' do
32
+ expect { CollectionOptionsCapped.create! }.not_to raise_error
33
+ end
34
+ end
35
+ # rubocop:enable Lint/ConstantDefinitionInBlock
36
+ # rubocop:enable RSpec/LeakyConstantDeclaration
@@ -28,6 +28,10 @@ describe Mongoid::Association::Embedded::EmbedsMany do
28
28
  expect(legislator.attributes.keys).to eq(['_id', 'a'])
29
29
  end
30
30
 
31
+ it 'allows accessing the parent' do
32
+ expect { legislator.congress }.not_to raise_error
33
+ end
34
+
31
35
  context 'when using only with $' do
32
36
  before do
33
37
  Patient.destroy_all
@@ -2,6 +2,7 @@
2
2
  # rubocop:todo all
3
3
 
4
4
  require "spec_helper"
5
+ require 'support/models/canvas'
5
6
  require_relative '../belongs_to_models.rb'
6
7
 
7
8
  describe Mongoid::Association::Referenced::BelongsTo::Proxy do
@@ -750,6 +751,10 @@ describe Mongoid::Association::Referenced::BelongsTo::Proxy do
750
751
  person.save!
751
752
  end
752
753
 
754
+ # NOTE: there as a bad interdependency here, with the auto_save_spec.rb
755
+ # file. If auto_save_spec.rb runs before this, the following specs fail
756
+ # with "undefined method `nullify' for an instance of Person".
757
+
753
758
  context "when parent exists" do
754
759
 
755
760
  context "when child is destroyed" do
@@ -4,6 +4,10 @@
4
4
  require "spec_helper"
5
5
  require_relative './has_one_models'
6
6
 
7
+ BELONGS_TO_RESOLVER_ID__ = :__belongs_to_resolver_id
8
+ BELONGS_TO_RESOLVER = Mongoid::ModelResolver.new
9
+ Mongoid::ModelResolver.register_resolver BELONGS_TO_RESOLVER, BELONGS_TO_RESOLVER_ID__
10
+
7
11
  describe Mongoid::Association::Referenced::BelongsTo do
8
12
 
9
13
  before do
@@ -199,47 +203,76 @@ describe Mongoid::Association::Referenced::BelongsTo do
199
203
 
200
204
  context 'when the polymorphic option is provided' do
201
205
 
202
- context 'when the polymorphic option is true' do
206
+ [ true, :default ].each do |opt|
207
+ context "when the polymorphic option is #{opt.inspect}" do
208
+ let(:options) { { polymorphic: opt } }
209
+ before { association }
203
210
 
204
- let(:options) do
205
- {
206
- polymorphic: true
207
- }
211
+ it 'set the polymorphic attribute on the owner class' do
212
+ expect(belonging_class.polymorphic).to be(true)
213
+ end
214
+
215
+ it 'sets up a field for the inverse type' do
216
+ expect(belonging_class.fields.keys).to include(association.inverse_type)
217
+ end
218
+
219
+ it 'uses the default resolver' do
220
+ expect(association.resolver).to be == Mongoid::ModelResolver.instance
221
+ end
208
222
  end
223
+ end
209
224
 
210
- before do
211
- association
225
+ [ false, nil ].each do |opt|
226
+ context "when the polymorphic option is #{opt.inspect}" do
227
+ let(:options) { { polymorphic: opt } }
228
+
229
+ it 'does not set the polymorphic attribute on the owner class' do
230
+ expect(belonging_class.polymorphic).to be(false)
231
+ end
232
+
233
+ it 'does not set up a field for the inverse type' do
234
+ expect(belonging_class.fields.keys).not_to include(association.inverse_type)
235
+ end
236
+
237
+ it 'does not use a resolver' do
238
+ expect(association.resolver).to be_nil
239
+ end
212
240
  end
241
+ end
213
242
 
214
- it 'set the polymorphic attribute on the owner class' do
215
- expect(belonging_class.polymorphic).to be(true)
243
+ context 'when the polymorphic option is set to an unregistered id' do
244
+ let(:options) { { polymorphic: :bogus } }
245
+
246
+ # This behavior is intentional, so that the resolver can be registered after the classes
247
+ # are loaded.
248
+ it 'does not immediately raise an exception' do
249
+ expect { association }.not_to raise_error
216
250
  end
217
251
 
218
- it 'sets up a field for the inverse type' do
219
- expect(belonging_class.fields.keys).to include(association.inverse_type)
252
+ it 'raises error when resolver is accessed' do
253
+ expect { association.resolver }.to raise_error(Mongoid::Errors::UnrecognizedResolver)
220
254
  end
221
255
  end
222
256
 
223
- context 'when the polymorphic option is false' do
257
+ context 'when the polymorphic option is set to a registered id' do
258
+ let(:options) { { polymorphic: BELONGS_TO_RESOLVER_ID__ } }
259
+ before { association }
224
260
 
225
- let(:options) do
226
- {
227
- polymorphic: false
228
- }
261
+ it 'set the polymorphic attribute on the owner class' do
262
+ expect(belonging_class.polymorphic).to be(true)
229
263
  end
230
264
 
231
- it 'does not set the polymorphic attribute on the owner class' do
232
- expect(belonging_class.polymorphic).to be(false)
265
+ it 'sets up a field for the inverse type' do
266
+ expect(belonging_class.fields.keys).to include(association.inverse_type)
233
267
  end
234
268
 
235
- it 'does not set up a field for the inverse type' do
236
- expect(belonging_class.fields.keys).not_to include(association.inverse_type)
269
+ it 'connects the association to the corresponding resolver' do
270
+ expect(association.resolver).to be == BELONGS_TO_RESOLVER
237
271
  end
238
272
  end
239
273
  end
240
274
 
241
275
  context 'when the polymorphic option is not provided' do
242
-
243
276
  it 'does not set the polymorphic attribute on the owner class' do
244
277
  expect(belonging_class.polymorphic).to be(false)
245
278
  end
@@ -247,6 +280,10 @@ describe Mongoid::Association::Referenced::BelongsTo do
247
280
  it 'does not set up a field for the inverse type' do
248
281
  expect(belonging_class.fields.keys).not_to include(association.inverse_type)
249
282
  end
283
+
284
+ it 'does not use a resolver' do
285
+ expect(association.resolver).to be_nil
286
+ end
250
287
  end
251
288
  end
252
289
 
@@ -100,6 +100,10 @@ describe Mongoid::Association::Referenced::HasMany::Buildable do
100
100
  Post.where(association.foreign_key => object, 'ratable_type' => 'Rating')
101
101
  end
102
102
 
103
+ before do
104
+ Post.belongs_to :ratable, polymorphic: true
105
+ end
106
+
103
107
  it "adds the type to the criteria" do
104
108
  expect(documents).to eq(criteria)
105
109
  end
@@ -2,6 +2,7 @@
2
2
  # rubocop:todo all
3
3
 
4
4
  require "spec_helper"
5
+ require 'support/models/sandwich'
5
6
  require_relative '../association/referenced/has_many_models'
6
7
  require_relative '../association/referenced/has_and_belongs_to_many_models'
7
8
  require_relative './nested_spec_models'
@@ -27,7 +27,7 @@ describe Mongoid::Clients::Options, retry: 3 do
27
27
  let(:options) { { database: 'other' } }
28
28
 
29
29
  it 'sets the options on the client' do
30
- expect(persistence_context.client.options['database']).to eq(options[:database])
30
+ expect(persistence_context.client.options['database'].to_s).to eq(options[:database].to_s)
31
31
  end
32
32
 
33
33
  it 'does not set the options on class level' do
@@ -319,7 +319,7 @@ describe Mongoid::Clients::Options, retry: 3 do
319
319
  end
320
320
 
321
321
  it 'sets the options on the client' do
322
- expect(persistence_context.client.options['database']).to eq(options[:database])
322
+ expect(persistence_context.client.options['database'].to_s).to eq(options[:database].to_s)
323
323
  end
324
324
 
325
325
  it 'does not set the options on instance level' do
@@ -522,4 +522,129 @@ describe Mongoid::Clients::Options, retry: 3 do
522
522
  end
523
523
  end
524
524
  end
525
+
526
+ context 'with global overrides' do
527
+ let(:default_subscriber) do
528
+ Mrss::EventSubscriber.new
529
+ end
530
+
531
+ let(:override_subscriber) do
532
+ Mrss::EventSubscriber.new
533
+ end
534
+
535
+ context 'when global client is overridden' do
536
+ before do
537
+ Mongoid.clients['override_client'] = { hosts: SpecConfig.instance.addresses, database: 'default_override_database' }
538
+ Mongoid.override_client('override_client')
539
+ Mongoid.client(:default).subscribe(Mongo::Monitoring::COMMAND, default_subscriber)
540
+ Mongoid.client('override_client').subscribe(Mongo::Monitoring::COMMAND, override_subscriber)
541
+ end
542
+
543
+ after do
544
+ Mongoid.client(:default).unsubscribe(Mongo::Monitoring::COMMAND, default_subscriber)
545
+ Mongoid.client('override_client').unsubscribe(Mongo::Monitoring::COMMAND, override_subscriber)
546
+ Mongoid.override_client(nil)
547
+ Mongoid.clients['override_client'] = nil
548
+ end
549
+
550
+ it 'uses the overridden client for create' do
551
+ Minim.create!
552
+
553
+ expect(override_subscriber.single_command_started_event('insert').database_name).to eq('default_override_database')
554
+ expect(default_subscriber.command_started_events('insert')).to be_empty
555
+ end
556
+
557
+ it 'uses the overridden client for queries' do
558
+ Minim.where(name: 'Dmitry').to_a
559
+
560
+ expect(override_subscriber.single_command_started_event('find').database_name).to eq('default_override_database')
561
+ expect(default_subscriber.command_started_events('find')).to be_empty
562
+ end
563
+
564
+ context 'when the client is set on the model level' do
565
+ let(:model_level_subscriber) do
566
+ Mrss::EventSubscriber.new
567
+ end
568
+
569
+ around(:example) do |example|
570
+ opts = Minim.storage_options
571
+ Minim.storage_options = Minim.storage_options.merge( { client: 'model_level_client' } )
572
+ Mongoid.clients['model_level_client'] = { hosts: SpecConfig.instance.addresses, database: 'model_level_database' }
573
+ Mongoid.client('model_level_client').subscribe(Mongo::Monitoring::COMMAND, override_subscriber)
574
+ example.run
575
+ Mongoid.client('model_level_client').unsubscribe(Mongo::Monitoring::COMMAND, override_subscriber)
576
+ Mongoid.clients['model_level_client'] = nil
577
+ Minim.storage_options = opts
578
+ end
579
+
580
+ # This behaviour is consistent with 8.x
581
+ it 'uses the overridden client for create' do
582
+ Minim.create!
583
+
584
+ expect(override_subscriber.single_command_started_event('insert').database_name).to eq('default_override_database')
585
+ expect(default_subscriber.command_started_events('insert')).to be_empty
586
+ expect(model_level_subscriber.command_started_events('insert')).to be_empty
587
+ end
588
+
589
+ # This behaviour is consistent with 8.x
590
+ it 'uses the overridden client for queries' do
591
+ Minim.where(name: 'Dmitry').to_a
592
+
593
+ expect(override_subscriber.single_command_started_event('find').database_name).to eq('default_override_database')
594
+ expect(default_subscriber.command_started_events('find')).to be_empty
595
+ expect(model_level_subscriber.command_started_events('find')).to be_empty
596
+ end
597
+ end
598
+ end
599
+
600
+ context 'when global database is overridden' do
601
+ before do
602
+ Mongoid.override_database('override_database')
603
+ Mongoid.client(:default).subscribe(Mongo::Monitoring::COMMAND, default_subscriber)
604
+ end
605
+
606
+ after do
607
+ Mongoid.client(:default).unsubscribe(Mongo::Monitoring::COMMAND, default_subscriber)
608
+ Mongoid.override_database(nil)
609
+ end
610
+
611
+ it 'uses the overridden database for create' do
612
+ Minim.create!
613
+
614
+ expect(default_subscriber.single_command_started_event('insert').database_name).to eq('override_database')
615
+ end
616
+
617
+ it 'uses the overridden database for queries' do
618
+ Minim.where(name: 'Dmitry').to_a
619
+
620
+ expect(default_subscriber.single_command_started_event('find').database_name).to eq('override_database')
621
+ end
622
+
623
+ context 'when the database is set on the model level' do
624
+ around(:example) do |example|
625
+ opts = Minim.storage_options
626
+ Minim.storage_options = Minim.storage_options.merge( { database: 'model_level_database' } )
627
+ Mongoid.clients['model_level_client'] = { hosts: SpecConfig.instance.addresses, database: 'model_level_database' }
628
+ Mongoid.client(:default).subscribe(Mongo::Monitoring::COMMAND, default_subscriber)
629
+ example.run
630
+ Mongoid.client(:default).unsubscribe(Mongo::Monitoring::COMMAND, default_subscriber)
631
+ Mongoid.clients['model_level_client'] = nil
632
+ Minim.storage_options = opts
633
+ end
634
+
635
+ # This behaviour is consistent with 8.x
636
+ it 'uses the overridden database for create' do
637
+ Minim.create!
638
+
639
+ expect(default_subscriber.single_command_started_event('insert').database_name).to eq('override_database')
640
+ end
641
+
642
+ it 'uses the overridden database for queries' do
643
+ Minim.where(name: 'Dmitry').to_a
644
+
645
+ expect(default_subscriber.single_command_started_event('find').database_name).to eq('override_database')
646
+ end
647
+ end
648
+ end
649
+ end
525
650
  end
@@ -282,7 +282,7 @@ describe Mongoid::Clients::Sessions do
282
282
  end
283
283
  end
284
284
 
285
- include_examples 'it aborts the transaction', Mongoid::Errors::InvalidTransactionNesting
285
+ include_examples 'it aborts the transaction', Mongoid::Errors::TransactionError
286
286
  end
287
287
  end
288
288
  end
@@ -591,7 +591,7 @@ describe Mongoid::Clients::Sessions do
591
591
  end
592
592
 
593
593
  it 'raises an error' do
594
- expect(error).to be_a(Mongoid::Errors::InvalidTransactionNesting)
594
+ expect(error).to be_a(Mongoid::Errors::TransactionError)
595
595
  end
596
596
 
597
597
  it 'does not execute any operations' do
@@ -1789,6 +1789,12 @@ describe Mongoid::Interceptable do
1789
1789
  context 'with around callbacks' do
1790
1790
  config_override :around_callbacks_for_embeds, true
1791
1791
 
1792
+ after do
1793
+ Mongoid::Threaded.stack('interceptable').clear
1794
+ end
1795
+
1796
+ let(:stack) { Mongoid::Threaded.stack('interceptable') }
1797
+
1792
1798
  let(:expected) do
1793
1799
  [
1794
1800
  [InterceptableSpec::CbCascadedChild, :before_validation],
@@ -1824,6 +1830,12 @@ describe Mongoid::Interceptable do
1824
1830
  parent.save!
1825
1831
  expect(registry.calls).to eq expected
1826
1832
  end
1833
+
1834
+ it 'shows that cascaded callbacks can access Mongoid state' do
1835
+ expect(stack).to be_empty
1836
+ parent.save!
1837
+ expect(stack).not_to be_empty
1838
+ end
1827
1839
  end
1828
1840
 
1829
1841
  context 'without around callbacks' do
@@ -224,7 +224,19 @@ module InterceptableSpec
224
224
 
225
225
  attr_accessor :callback_registry
226
226
 
227
+ before_save :test_mongoid_state
228
+
227
229
  include CallbackTracking
230
+
231
+ private
232
+
233
+ # Helps test that cascading child callbacks have access to the Mongoid
234
+ # state objects; if the implementation uses fiber-local (instead of truly
235
+ # thread-local) variables, the related tests will fail because the
236
+ # cascading child callbacks use fibers to linearize the recursion.
237
+ def test_mongoid_state
238
+ Mongoid::Threaded.stack('interceptable').push(self)
239
+ end
228
240
  end
229
241
  end
230
242