mongoid 9.0.1 → 9.0.3

Sign up to get free protection for your applications and to get access to all the features.
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