mongoid 8.0.8 → 8.0.10

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +65 -41
  3. data/lib/mongoid/association/accessors.rb +5 -1
  4. data/lib/mongoid/association/eager_loadable.rb +3 -0
  5. data/lib/mongoid/association/referenced/has_many/enumerable.rb +21 -4
  6. data/lib/mongoid/config.rb +10 -0
  7. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +15 -1
  8. data/lib/mongoid/document.rb +8 -1
  9. data/lib/mongoid/fields.rb +11 -6
  10. data/lib/mongoid/interceptable.rb +10 -8
  11. data/lib/mongoid/timestamps/created.rb +8 -1
  12. data/lib/mongoid/traversable.rb +12 -0
  13. data/lib/mongoid/validatable/associated.rb +6 -3
  14. data/lib/mongoid/version.rb +1 -1
  15. data/spec/integration/app_spec.rb +4 -0
  16. data/spec/integration/callbacks_models.rb +37 -0
  17. data/spec/integration/callbacks_spec.rb +27 -0
  18. data/spec/mongoid/association/eager_spec.rb +24 -2
  19. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +4 -0
  20. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +28 -37
  21. data/spec/mongoid/association_spec.rb +60 -0
  22. data/spec/mongoid/document_spec.rb +27 -0
  23. data/spec/mongoid/interceptable_spec.rb +100 -0
  24. data/spec/mongoid/interceptable_spec_models.rb +51 -111
  25. data/spec/mongoid/serializable_spec.rb +14 -14
  26. data/spec/mongoid/timestamps/created_spec.rb +23 -0
  27. data/spec/mongoid/validatable/associated_spec.rb +14 -4
  28. data/spec/support/models/book.rb +1 -0
  29. data/spec/support/models/cover.rb +10 -0
  30. metadata +6 -80
  31. checksums.yaml.gz.sig +0 -0
  32. data/spec/shared/LICENSE +0 -20
  33. data/spec/shared/bin/get-mongodb-download-url +0 -17
  34. data/spec/shared/bin/s3-copy +0 -45
  35. data/spec/shared/bin/s3-upload +0 -69
  36. data/spec/shared/lib/mrss/child_process_helper.rb +0 -80
  37. data/spec/shared/lib/mrss/cluster_config.rb +0 -231
  38. data/spec/shared/lib/mrss/constraints.rb +0 -378
  39. data/spec/shared/lib/mrss/docker_runner.rb +0 -298
  40. data/spec/shared/lib/mrss/eg_config_utils.rb +0 -51
  41. data/spec/shared/lib/mrss/event_subscriber.rb +0 -210
  42. data/spec/shared/lib/mrss/lite_constraints.rb +0 -238
  43. data/spec/shared/lib/mrss/server_version_registry.rb +0 -113
  44. data/spec/shared/lib/mrss/session_registry.rb +0 -69
  45. data/spec/shared/lib/mrss/session_registry_legacy.rb +0 -60
  46. data/spec/shared/lib/mrss/spec_organizer.rb +0 -179
  47. data/spec/shared/lib/mrss/utils.rb +0 -37
  48. data/spec/shared/share/Dockerfile.erb +0 -321
  49. data/spec/shared/share/haproxy-1.conf +0 -16
  50. data/spec/shared/share/haproxy-2.conf +0 -17
  51. data/spec/shared/shlib/config.sh +0 -27
  52. data/spec/shared/shlib/distro.sh +0 -74
  53. data/spec/shared/shlib/server.sh +0 -416
  54. data/spec/shared/shlib/set_env.sh +0 -169
  55. data.tar.gz.sig +0 -0
  56. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d604924530b809dbef3eef08d7e3c52b14e815d674821789a98c590f704b149
4
- data.tar.gz: bfefc068fa68ed135560cff738dd7641f7df3780d01600a9f936752d5c760b8c
3
+ metadata.gz: 5d5d57d80bf503ada4f229db360fee76b689f1224292a05ce8b9e2e1f2768f17
4
+ data.tar.gz: 409bfdfa5c3f91cfc75429569148cbff7bc19716193ac2af3d101f91647b7ea8
5
5
  SHA512:
6
- metadata.gz: 50280304426a7dc55cb2e86d2ce69d295f5b51df6f714dee96e4a5c0b8da00c1ca09ad516d300da6755cf5160699a982868bb46c1afd2da812f952ca8c18e240
7
- data.tar.gz: 0fccee8bb9add55b0dfaa6454a33c89fe40553ce1a3fe9bef8be36fe4c92a884970d2e2b2a44c32761a198e750351276ef38f27a5e95f7fe11be02686425568b
6
+ metadata.gz: 2aa57a75a92f7a03d872f28a0be8cfdd800d2917a9893b2cb4101cedcc1196c02c2e9591e3de67580b00b06626ea16bdf8aa742cc5752a806581e88a186410de
7
+ data.tar.gz: 725b89395a8d8126c196bd3ed42f02dc44c9e019247364f4fcc2d5587cf742a2ba3e9c27cafface3ea13712e2f17ea0fab081a8177b52299ed36b74b35a49347
data/Rakefile CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler"
4
- require "bundler/gem_tasks"
5
4
  Bundler.setup
6
5
 
7
6
  ROOT = File.expand_path(File.join(File.dirname(__FILE__)))
@@ -10,34 +9,53 @@ $: << File.join(ROOT, 'spec/shared/lib')
10
9
 
11
10
  require "rake"
12
11
  require "rspec/core/rake_task"
13
- require 'mrss/spec_organizer'
14
- require 'rubygems/package'
15
- require 'rubygems/security/policies'
16
-
17
- def signed_gem?(path_to_gem)
18
- Gem::Package.new(path_to_gem, Gem::Security::HighSecurity).verify
19
- true
20
- rescue Gem::Security::Exception => e
21
- false
22
- end
23
-
24
- $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
25
- require "mongoid/version"
26
-
27
- tasks = Rake.application.instance_variable_get('@tasks')
28
- tasks['release:do'] = tasks.delete('release')
29
12
 
30
- task :gem => :build
13
+ # stands in for the Bundler-provided `build` task, which builds the
14
+ # gem for this project. Our release process builds the gems in a
15
+ # particular way, in a GitHub action. This task is just to help remind
16
+ # developers of that fact.
31
17
  task :build do
32
- system "gem build mongoid.gemspec"
18
+ abort <<~WARNING
19
+ `rake build` does nothing in this project. The gem must be built via
20
+ the `Mongoid Release` action on GitHub, which is triggered manually when
21
+ a new release is ready.
22
+ WARNING
33
23
  end
34
24
 
35
- task :install => :build do
36
- system "sudo gem install mongoid-#{Mongoid::VERSION}.gem"
25
+ # `rake version` is used by the deployment system so get the release version
26
+ # of the product beng deployed. It must do nothing more than just print the
27
+ # product version number.
28
+ #
29
+ # See the mongodb-labs/driver-github-tools/ruby/publish Github action.
30
+ desc "Print the current value of Mongoid::VERSION"
31
+ task :version do
32
+ require 'mongoid/version'
33
+
34
+ puts Mongoid::VERSION
37
35
  end
38
36
 
37
+ # overrides the default Bundler-provided `release` task, which also
38
+ # builds the gem. Our release process assumes the gem has already
39
+ # been built (and signed via GPG), so we just need `rake release` to
40
+ # push the gem to rubygems.
39
41
  task :release do
40
- raise "Please use ./release.sh to release"
42
+ require 'mongoid/version'
43
+
44
+ if ENV['GITHUB_ACTION'].nil?
45
+ abort <<~WARNING
46
+ `rake release` must be invoked from the `Mongoid Release` GitHub action,
47
+ and must not be invoked locally. This ensures the gem is properly signed
48
+ and distributed by the appropriate user.
49
+
50
+ Note that it is the `rubygems/release-gem@v1` step in the `Mongoid Release`
51
+ action that invokes this task. Do not rename or remove this task, or the
52
+ release-gem step will fail. Reimplement this task with caution.
53
+
54
+ mongoid-#{Mongoid::VERSION}.gem was NOT pushed to RubyGems.
55
+ WARNING
56
+ end
57
+
58
+ system 'gem', 'push', "mongoid-#{Mongoid::VERSION}.gem"
41
59
  end
42
60
 
43
61
  RSpec::Core::RakeTask.new("spec") do |spec|
@@ -64,6 +82,8 @@ RUN_PRIORITY = %i(
64
82
  )
65
83
 
66
84
  def spec_organizer
85
+ require 'mrss/spec_organizer'
86
+
67
87
  Mrss::SpecOrganizer.new(
68
88
  root: ROOT,
69
89
  classifiers: CLASSIFIERS,
@@ -99,32 +119,36 @@ task :docs => 'docs:yard'
99
119
  namespace :docs do
100
120
  desc "Generate yard documention"
101
121
  task :yard do
122
+ require "mongoid/version"
123
+
102
124
  out = File.join('yard-docs', Mongoid::VERSION)
103
125
  FileUtils.rm_rf(out)
104
126
  system "yardoc -o #{out} --title mongoid-#{Mongoid::VERSION}"
105
127
  end
106
128
  end
107
129
 
108
- namespace :release do
109
- task :check_private_key do
110
- unless File.exist?('gem-private_key.pem')
111
- raise "No private key present, cannot release"
112
- end
130
+ desc 'Build and validate the evergreen config'
131
+ task eg: %w[ eg:build eg:validate ]
132
+
133
+ # 'eg' == 'evergreen', but evergreen is too many letters for convenience
134
+ namespace :eg do
135
+ desc 'Builds the .evergreen/config.yml file from the templates'
136
+ task :build do
137
+ ruby '.evergreen/update-evergreen-configs'
113
138
  end
114
- end
115
139
 
116
- desc 'Verifies that all built gems in pkg/ are valid'
117
- task :verify do
118
- gems = Dir['pkg/*.gem']
119
- if gems.empty?
120
- puts 'There are no gems in pkg/ to verify'
121
- else
122
- gems.each do |gem|
123
- if signed_gem?(gem)
124
- puts "#{gem} is signed"
125
- else
126
- abort "#{gem} is not signed"
127
- end
128
- end
140
+ desc 'Validates the .evergreen/config.yml file'
141
+ task :validate do
142
+ system 'evergreen validate --project mongoid .evergreen/config.yml'
143
+ end
144
+
145
+ desc 'Updates the evergreen executable to the latest available version'
146
+ task :update do
147
+ system 'evergreen get-update --install'
148
+ end
149
+
150
+ desc 'Runs the current branch as an evergreen patch'
151
+ task :patch do
152
+ system 'evergreen patch --uncommitted --project mongoid --browse --auto-description --yes'
129
153
  end
130
154
  end
@@ -115,7 +115,11 @@ module Mongoid
115
115
  # during binding or when cascading callbacks. Whenever we retrieve
116
116
  # associations within the codebase, we use without_autobuild.
117
117
  if !without_autobuild? && association.embedded? && attribute_missing?(field_name)
118
- raise ActiveModel::MissingAttributeError, "Missing attribute: '#{field_name}'"
118
+ # We always allow accessing the parent document of an embedded one.
119
+ try_get_parent = association.is_a?(
120
+ Mongoid::Association::Embedded::EmbeddedIn
121
+ ) && field_name == association.key
122
+ raise ActiveModel::MissingAttributeError, "Missing attribute: '#{field_name}'" unless try_get_parent
119
123
  end
120
124
 
121
125
  if !reload && (value = ivar(name)) != false
@@ -31,6 +31,9 @@ module Mongoid
31
31
  docs_map = {}
32
32
  queue = [ klass.to_s ]
33
33
 
34
+ # account for single-collection inheritance
35
+ queue.push(klass.root_class.to_s) if klass != klass.root_class
36
+
34
37
  while klass = queue.shift
35
38
  if as = assoc_map.delete(klass)
36
39
  as.each do |assoc|
@@ -422,11 +422,28 @@ module Mongoid
422
422
  #
423
423
  # @return [ Integer ] The size of the enumerable.
424
424
  def size
425
- count = (_unloaded ? _unloaded.count : _loaded.count)
426
- if count.zero?
427
- count + _added.count
425
+ # If _unloaded is present, then it will match the set of documents
426
+ # that belong to this association, which have already been persisted
427
+ # to the database. This set of documents must be considered when
428
+ # computing the size of the association, along with anything that has
429
+ # since been added.
430
+ if _unloaded
431
+ if _added.any?
432
+ # Note that _added may include records that _unloaded already
433
+ # matches. This is the case if the association is assigned an array
434
+ # of items and some of them were already elements of the association.
435
+ #
436
+ # we need to thus make sure _unloaded.count excludes any elements
437
+ # that already exist in _added.
438
+
439
+ count = _unloaded.not(:_id.in => _added.values.map(&:id)).count
440
+ count + _added.values.count
441
+ else
442
+ _unloaded.count
443
+ end
444
+
428
445
  else
429
- count + _added.values.count { |d| d.new_record? }
446
+ _loaded.count + _added.count
430
447
  end
431
448
  end
432
449
 
@@ -142,6 +142,16 @@ module Mongoid
142
142
  end
143
143
  end
144
144
 
145
+ # When this flag is true, callbacks for every embedded document will be
146
+ # called only once, even if the embedded document is embedded in multiple
147
+ # documents in the root document's dependencies graph.
148
+ # This will be the default in 9.0. Setting this flag to false restores the
149
+ # pre-9.0 behavior, where callbacks are called for every occurrence of an
150
+ # embedded document. The pre-9.0 behavior leads to a problem that for multi
151
+ # level nested documents callbacks are called multiple times.
152
+ # See https://jira.mongodb.org/browse/MONGOID-5542
153
+ option :prevent_multiple_calls_of_embedded_callbacks, default: false
154
+
145
155
  # When this flag is true, callbacks for embedded documents will not be
146
156
  # called. This is the default in 8.x, but will be changed to false in 9.0.
147
157
  #
@@ -43,7 +43,21 @@ module Mongoid
43
43
  #
44
44
  # @return [ Object ] The converted number.
45
45
  def __numeric__(object)
46
- object.to_s.match?(/\A[-+]?[0-9]*[0-9.]0*\z/) ? object.to_i : Float(object)
46
+ str = object.to_s
47
+ raise ArgumentError if str.empty?
48
+
49
+ # These requirements seem a bit odd, but they're explicitly specified in the tests,
50
+ # so we're obligated to keep them, for now. (This code was rewritten from a one-line
51
+ # regex, due to security concerns with a polynomial regex being used on uncontrolled
52
+ # data).
53
+
54
+ str = str.chop if str.end_with?('.')
55
+ return 0 if str.empty?
56
+
57
+ result = Integer(str) rescue Float(object)
58
+
59
+ integer = result.to_i
60
+ integer == result ? integer : result
47
61
  end
48
62
 
49
63
  # Evolve the object to an integer.
@@ -133,7 +133,14 @@ module Mongoid
133
133
  #
134
134
  # @return [ Hash ] A hash of all attributes in the hierarchy.
135
135
  def as_document
136
- BSON::Document.new(as_attributes)
136
+ attrs = as_attributes
137
+
138
+ # legacy attributes have a tendency to leak internal state via
139
+ # `as_document`; we have to deep_dup the attributes here to prevent
140
+ # that.
141
+ attrs = attrs.deep_dup if Mongoid.legacy_attributes
142
+
143
+ BSON::Document.new(attrs)
137
144
  end
138
145
 
139
146
  # Calls #as_json on the document with additional, Mongoid-specific options.
@@ -47,6 +47,11 @@ module Mongoid
47
47
  # @api private
48
48
  INVALID_BSON_CLASSES = [ BSON::Decimal128, BSON::Int32, BSON::Int64 ].freeze
49
49
 
50
+ # The suffix for generated translated fields.
51
+ #
52
+ # @api private
53
+ TRANSLATIONS_SFX = '_translations'
54
+
50
55
  module ClassMethods
51
56
  # Returns the list of id fields for this model class, as both strings
52
57
  # and symbols.
@@ -99,8 +104,8 @@ module Mongoid
99
104
  ar.each_with_index do |fn, i|
100
105
  key = fn
101
106
  unless klass.fields.key?(fn) || klass.relations.key?(fn)
102
- if tr = fn.match(/(.*)_translations\z/)&.captures&.first
103
- key = tr
107
+ if fn.end_with?(TRANSLATIONS_SFX)
108
+ key = fn.delete_suffix(TRANSLATIONS_SFX)
104
109
  else
105
110
  key = fn
106
111
  end
@@ -708,11 +713,11 @@ module Mongoid
708
713
  # @param [ String ] meth The name of the method.
709
714
  def create_translations_getter(name, meth)
710
715
  generated_methods.module_eval do
711
- re_define_method("#{meth}_translations") do
716
+ re_define_method("#{meth}#{TRANSLATIONS_SFX}") do
712
717
  attributes[name] ||= {}
713
718
  attributes[name].with_indifferent_access
714
719
  end
715
- alias_method :"#{meth}_t", :"#{meth}_translations"
720
+ alias_method :"#{meth}_t", :"#{meth}#{TRANSLATIONS_SFX}"
716
721
  end
717
722
  end
718
723
 
@@ -726,14 +731,14 @@ module Mongoid
726
731
  # @param [ Field ] field The field.
727
732
  def create_translations_setter(name, meth, field)
728
733
  generated_methods.module_eval do
729
- re_define_method("#{meth}_translations=") do |value|
734
+ re_define_method("#{meth}#{TRANSLATIONS_SFX}=") do |value|
730
735
  attribute_will_change!(name)
731
736
  value&.transform_values! do |_value|
732
737
  field.type.mongoize(_value)
733
738
  end
734
739
  attributes[name] = value
735
740
  end
736
- alias_method :"#{meth}_t=", :"#{meth}_translations="
741
+ alias_method :"#{meth}_t=", :"#{meth}#{TRANSLATIONS_SFX}="
737
742
  end
738
743
  end
739
744
 
@@ -141,9 +141,13 @@ module Mongoid
141
141
  # @api private
142
142
  def _mongoid_run_child_callbacks(kind, children: nil, &block)
143
143
  if Mongoid::Config.around_callbacks_for_embeds
144
- _mongoid_run_child_callbacks_with_around(kind, children: children, &block)
144
+ _mongoid_run_child_callbacks_with_around(kind,
145
+ children: children,
146
+ &block)
145
147
  else
146
- _mongoid_run_child_callbacks_without_around(kind, children: children, &block)
148
+ _mongoid_run_child_callbacks_without_around(kind,
149
+ children: children,
150
+ &block)
147
151
  end
148
152
  end
149
153
 
@@ -163,13 +167,14 @@ module Mongoid
163
167
  # @api private
164
168
  def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block)
165
169
  child, *tail = (children || cascadable_children(kind))
170
+ with_children = !Mongoid::Config.prevent_multiple_calls_of_embedded_callbacks
166
171
  if child.nil?
167
172
  block&.call
168
173
  elsif tail.empty?
169
- child.run_callbacks(child_callback_type(kind, child), &block)
174
+ child.run_callbacks(child_callback_type(kind, child), with_children: with_children, &block)
170
175
  else
171
- child.run_callbacks(child_callback_type(kind, child)) do
172
- _mongoid_run_child_callbacks_with_around(kind, children: tail, &block)
176
+ child.run_callbacks(child_callback_type(kind, child), with_children: with_children) do
177
+ _mongoid_run_child_callbacks(kind, children: tail, &block)
173
178
  end
174
179
  end
175
180
  end
@@ -218,9 +223,6 @@ module Mongoid
218
223
  return false if env.halted
219
224
  env.value = !env.halted
220
225
  callback_list << [next_sequence, env]
221
- if (grandchildren = child.send(:cascadable_children, kind))
222
- _mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
223
- end
224
226
  end
225
227
  callback_list
226
228
  end
@@ -22,13 +22,20 @@ module Mongoid
22
22
  # @example Set the created at time.
23
23
  # person.set_created_at
24
24
  def set_created_at
25
- if !timeless? && !created_at
25
+ if able_to_set_created_at?
26
26
  time = Time.configured.now
27
27
  self.updated_at = time if is_a?(Updated) && !updated_at_changed?
28
28
  self.created_at = time
29
29
  end
30
30
  clear_timeless_option
31
31
  end
32
+
33
+ # Is the created timestamp able to be set?
34
+ #
35
+ # @return [ true, false ] If the timestamp can be set.
36
+ def able_to_set_created_at?
37
+ !frozen? && !timeless? && !created_at
38
+ end
32
39
  end
33
40
  end
34
41
  end
@@ -300,6 +300,18 @@ module Mongoid
300
300
  !!(Mongoid::Document > superclass)
301
301
  end
302
302
 
303
+ # Returns the root class of the STI tree that the current
304
+ # class participates in. If the class is not an STI subclass, this
305
+ # returns the class itself.
306
+ #
307
+ # @return [ Mongoid::Document ] the root of the STI tree
308
+ def root_class
309
+ root = self
310
+ root = root.superclass while root.hereditary?
311
+
312
+ root
313
+ end
314
+
303
315
  # When inheriting, we want to copy the fields from the parent class and
304
316
  # set the on the child to start, mimicking the behavior of the old
305
317
  # class_inheritable_accessor that was deprecated in Rails edge.
@@ -69,13 +69,16 @@ module Mongoid
69
69
  # Now, treating the target as an array, look at each element
70
70
  # and see if it is valid, but only if it has already been
71
71
  # persisted, or changed, and hasn't been flagged for destroy.
72
- list.all? do |value|
73
- if value && !value.flagged_for_destroy? && (!value.persisted? || value.changed?)
72
+ #
73
+ # use map.all? instead of just all?, because all? will do short-circuit
74
+ # evaluation and terminate on the first failed validation.
75
+ list.map do |value|
76
+ if value && !value.flagged_for_destroy?
74
77
  value.validated? ? true : value.valid?
75
78
  else
76
79
  true
77
80
  end
78
- end
81
+ end.all?
79
82
  end
80
83
 
81
84
  document.errors.add(attribute, :invalid) unless valid
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongoid
4
- VERSION = "8.0.8"
4
+ VERSION = "8.0.10"
5
5
  end
@@ -16,6 +16,10 @@ describe 'Mongoid application tests' do
16
16
  skip 'Set APP_TESTS=1 in environment to run application tests'
17
17
  end
18
18
 
19
+ if SpecConfig.instance.rails_version < '7.1'
20
+ skip 'App tests require Rails > 7.0 (see https://stackoverflow.com/questions/79360526)'
21
+ end
22
+
19
23
  require 'fileutils'
20
24
  require 'mrss/child_process_helper'
21
25
  require 'open-uri'
@@ -153,3 +153,40 @@ class Building
153
153
 
154
154
  has_and_belongs_to_many :architects, dependent: :nullify
155
155
  end
156
+
157
+ class Root
158
+ include Mongoid::Document
159
+ embeds_many :embedded_once, cascade_callbacks: true
160
+ after_save :trace
161
+
162
+ attr_accessor :logger
163
+
164
+ def trace
165
+ logger << :root
166
+ end
167
+ end
168
+
169
+ class EmbeddedOnce
170
+ include Mongoid::Document
171
+ embeds_many :embedded_twice, cascade_callbacks: true
172
+ embedded_in :root
173
+ after_save :trace
174
+
175
+ attr_accessor :logger
176
+
177
+ def trace
178
+ logger << :embedded_once
179
+ end
180
+ end
181
+
182
+ class EmbeddedTwice
183
+ include Mongoid::Document
184
+ embedded_in :embedded_once
185
+ after_save :trace
186
+
187
+ attr_accessor :logger
188
+
189
+ def trace
190
+ logger << :embedded_twice
191
+ end
192
+ end
@@ -467,4 +467,31 @@ describe 'callbacks integration tests' do
467
467
  expect { book.save! }.not_to raise_error(SystemStackError)
468
468
  end
469
469
  end
470
+
471
+ context 'nested embedded documents' do
472
+ config_override :prevent_multiple_calls_of_embedded_callbacks, true
473
+
474
+ let(:logger) { Array.new }
475
+
476
+ let(:root) do
477
+ Root.new(
478
+ embedded_once: [
479
+ EmbeddedOnce.new(
480
+ embedded_twice: [EmbeddedTwice.new]
481
+ )
482
+ ]
483
+ )
484
+ end
485
+
486
+ before(:each) do
487
+ root.logger = logger
488
+ root.embedded_once.first.logger = logger
489
+ root.embedded_once.first.embedded_twice.first.logger = logger
490
+ end
491
+
492
+ it 'runs callbacks in the correct order' do
493
+ root.save!
494
+ expect(logger).to eq(%i[embedded_twice embedded_once root])
495
+ end
496
+ end
470
497
  end
@@ -14,14 +14,36 @@ describe Mongoid::Association::EagerLoadable do
14
14
  Mongoid::Contextual::Mongo.new(criteria)
15
15
  end
16
16
 
17
+ let(:association_host) { Account }
18
+
17
19
  let(:inclusions) do
18
20
  includes.map do |key|
19
- Account.reflect_on_association(key)
21
+ association_host.reflect_on_association(key)
20
22
  end
21
23
  end
22
24
 
23
25
  let(:doc) { criteria.first }
24
26
 
27
+ context 'when root is an STI subclass' do
28
+ # Driver has_one Vehicle
29
+ # Vehicle belongs_to Driver
30
+ # Truck is a Vehicle
31
+
32
+ before do
33
+ Driver.create!(vehicle: Truck.new)
34
+ end
35
+
36
+ let(:criteria) { Truck.all }
37
+ let(:includes) { %i[ driver ] }
38
+ let(:association_host) { Truck }
39
+
40
+ it 'preloads the driver' do
41
+ expect(doc.ivar(:driver)).to be false
42
+ context.preload(inclusions, [ doc ])
43
+ expect(doc.ivar(:driver)).to be == Driver.first
44
+ end
45
+ end
46
+
25
47
  context "when belongs_to" do
26
48
 
27
49
  let!(:account) do
@@ -42,7 +64,7 @@ describe Mongoid::Association::EagerLoadable do
42
64
  it "preloads the parent" do
43
65
  expect(doc.ivar(:person)).to be false
44
66
  context.preload(inclusions, [doc])
45
- expect(doc.ivar(:person)).to eq(doc.person)
67
+ expect(doc.ivar(:person)).to be == person
46
68
  end
47
69
  end
48
70
 
@@ -27,6 +27,10 @@ describe Mongoid::Association::Embedded::EmbedsMany do
27
27
  expect(legislator.attributes.keys).to eq(['_id', 'a'])
28
28
  end
29
29
 
30
+ it 'allows accessing the parent' do
31
+ expect { legislator.congress }.not_to raise_error
32
+ end
33
+
30
34
  context 'when using only with $' do
31
35
  before do
32
36
  Patient.destroy_all
@@ -1757,43 +1757,6 @@ describe Mongoid::Association::Referenced::HasAndBelongsToMany::Proxy do
1757
1757
  end
1758
1758
  end
1759
1759
 
1760
- describe "#any?" do
1761
-
1762
- let(:person) do
1763
- Person.create!
1764
- end
1765
-
1766
- context "when nothing exists on the relation" do
1767
-
1768
- context "when no document is added" do
1769
-
1770
- let!(:sandwich) do
1771
- Sandwich.create!
1772
- end
1773
-
1774
- it "returns false" do
1775
- expect(sandwich.meats.any?).to be false
1776
- end
1777
- end
1778
-
1779
- context "when the document is destroyed" do
1780
-
1781
- before do
1782
- Meat.create!
1783
- end
1784
-
1785
- let!(:sandwich) do
1786
- Sandwich.create!
1787
- end
1788
-
1789
- it "returns false" do
1790
- sandwich.destroy
1791
- expect(sandwich.meats.any?).to be false
1792
- end
1793
- end
1794
- end
1795
- end
1796
-
1797
1760
  context "when documents have been persisted" do
1798
1761
 
1799
1762
  let!(:preference) do
@@ -3063,6 +3026,34 @@ describe Mongoid::Association::Referenced::HasAndBelongsToMany::Proxy do
3063
3026
  end
3064
3027
  end
3065
3028
 
3029
+ # MONGOID-5844
3030
+ #
3031
+ # Specifically, this tests the case where the association is
3032
+ # initialized with a single element (so that Proxy#push does not take
3033
+ # the `concat` route), which causes `reset_unloaded` to be called, which
3034
+ # sets the `_unloaded` Criteria object to match only the specific element
3035
+ # that was given.
3036
+ #
3037
+ # The issue now is that when the events list is updated to be both events,
3038
+ # _unloaded matches one of them already, and the other has previously been
3039
+ # persisted so `new_record?` won't match it. We need to make sure the
3040
+ # `#size` logic properly accounts for this case.
3041
+ context 'when documents have been previously persisted' do
3042
+ let(:person1) { Person.create! }
3043
+ let(:person2) { Person.create! }
3044
+ let(:event1) { Event.create!(administrators: [ person1 ]) }
3045
+ let(:event2) { Event.create!(administrators: [ person2 ]) }
3046
+
3047
+ before do
3048
+ person1.administrated_events = [ event1, event2 ]
3049
+ end
3050
+
3051
+ it 'returns the number of associated documents [MONGOID-5844]' do
3052
+ expect(person1.administrated_events.to_a.size).to eq(2)
3053
+ expect(person1.administrated_events.size).to eq(2)
3054
+ end
3055
+ end
3056
+
3066
3057
  context "when documents have not been persisted" do
3067
3058
 
3068
3059
  before do