mongoid 8.1.3 → 8.1.11

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +43 -45
  3. data/lib/mongoid/association/accessors.rb +5 -1
  4. data/lib/mongoid/association/eager_loadable.rb +3 -0
  5. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +18 -3
  6. data/lib/mongoid/association/embedded/embeds_one/proxy.rb +1 -1
  7. data/lib/mongoid/association/referenced/has_many/enumerable.rb +21 -4
  8. data/lib/mongoid/association/referenced/has_many/proxy.rb +11 -2
  9. data/lib/mongoid/atomic.rb +9 -7
  10. data/lib/mongoid/attributes/readonly.rb +8 -3
  11. data/lib/mongoid/contextual/mongo.rb +1 -1
  12. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +15 -1
  13. data/lib/mongoid/criteria/queryable/selectable.rb +1 -1
  14. data/lib/mongoid/document.rb +8 -1
  15. data/lib/mongoid/equality.rb +1 -0
  16. data/lib/mongoid/extensions/hash.rb +4 -4
  17. data/lib/mongoid/fields.rb +13 -8
  18. data/lib/mongoid/interceptable.rb +6 -7
  19. data/lib/mongoid/matcher.rb +15 -1
  20. data/lib/mongoid/serializable.rb +7 -7
  21. data/lib/mongoid/timestamps/created.rb +8 -1
  22. data/lib/mongoid/touchable.rb +1 -1
  23. data/lib/mongoid/traversable.rb +36 -1
  24. data/lib/mongoid/validatable/associated.rb +98 -17
  25. data/lib/mongoid/validatable/macros.rb +15 -0
  26. data/lib/mongoid/validatable/numericality.rb +19 -0
  27. data/lib/mongoid/validatable.rb +9 -0
  28. data/lib/mongoid/version.rb +5 -1
  29. data/spec/integration/app_spec.rb +24 -5
  30. data/spec/integration/associations/embeds_one_spec.rb +25 -5
  31. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +40 -0
  32. data/spec/mongoid/association/eager_spec.rb +24 -2
  33. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +35 -0
  34. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +4 -0
  35. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +4 -0
  36. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +28 -37
  37. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +42 -0
  38. data/spec/mongoid/attributes/readonly_spec.rb +19 -0
  39. data/spec/mongoid/attributes_spec.rb +16 -0
  40. data/spec/mongoid/contextual/mongo_spec.rb +78 -3
  41. data/spec/mongoid/criteria/queryable/selectable_spec.rb +29 -0
  42. data/spec/mongoid/document_spec.rb +27 -0
  43. data/spec/mongoid/equality_spec.rb +6 -0
  44. data/spec/mongoid/fields_spec.rb +3 -3
  45. data/spec/mongoid/interceptable_spec.rb +80 -0
  46. data/spec/mongoid/interceptable_spec_models.rb +47 -111
  47. data/spec/mongoid/serializable_spec.rb +16 -9
  48. data/spec/mongoid/timestamps/created_spec.rb +23 -0
  49. data/spec/mongoid/validatable/associated_spec.rb +27 -34
  50. data/spec/mongoid/validatable/numericality_spec.rb +16 -0
  51. data/spec/shared/lib/mrss/docker_runner.rb +1 -2
  52. data/spec/shared/lib/mrss/release/candidate.rb +281 -0
  53. data/spec/shared/lib/mrss/release/product_data.rb +144 -0
  54. data/spec/shared/lib/mrss/server_version_registry.rb +1 -1
  55. data/spec/shared/lib/tasks/candidate.rake +64 -0
  56. data/spec/shared/share/Dockerfile.erb +15 -85
  57. data/spec/shared/shlib/distro.sh +10 -0
  58. data/spec/shared/shlib/server.sh +33 -26
  59. data/spec/shared/shlib/set_env.sh +9 -68
  60. data/spec/support/expectations.rb +20 -17
  61. data/spec/support/models/band.rb +1 -0
  62. data/spec/support/models/lat_lng.rb +6 -0
  63. data/spec/support/models/name.rb +10 -0
  64. metadata +15 -38
  65. checksums.yaml.gz.sig +0 -0
  66. data.tar.gz.sig +0 -0
  67. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 561c6173b8a11bb858b0df7dc72cec9e96767e7780572ecbc1233c11de84fb6a
4
- data.tar.gz: 1bc15420d04291843d26e52d1d04f2689d3e28b25db115d5a5f2c765b872fd30
3
+ metadata.gz: 21db50efa946dce1a9e43718d0e15d37c0e4373c9d693123d21cfe13b8afb3bf
4
+ data.tar.gz: 7d01ee5d5a8eb3c81a92c3055e94bf8bb90a8a911258401459b5e447c45a220c
5
5
  SHA512:
6
- metadata.gz: 112499f5ac9998aa952c851ac4e6d64014bf35fcfeeed0b29bdb5fac5519866657b8f7fe087a001033cee7adcf1f07a7eb4b4c68dcef2611cbf36a1aef22d31c
7
- data.tar.gz: 43f784a2d2d5782f464f064d35bd384a6711a2355f6a1cd3f9529372184928ecac9d2e74e4cf90092857733945a10c08452ede7e1a4cbba903d3617d5ccbf601
6
+ metadata.gz: f1e10a0cb03863509acb1d150460e4f7c475092b45bb65f7619ecd8f0384d4ed6e92416565a07e6e900af26fbf26b32c4cd2c9418c4dfbe1f858da451a3e9378
7
+ data.tar.gz: e2e75603f8ba8f3debead96161f81350ced4696aac125ba0b17f48a94a42cc48e867651deb1b6a60c3d57f48fa237f26ac61775f3c2be3750f62fc2b66d27661
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
12
 
27
- tasks = Rake.application.instance_variable_get('@tasks')
28
- tasks['release:do'] = tasks.delete('release')
13
+ if File.exist?('./spec/shared/lib/tasks/candidate.rake')
14
+ load 'spec/shared/lib/tasks/candidate.rake'
15
+ end
29
16
 
30
- task :gem => :build
17
+ desc 'Build the gem'
31
18
  task :build do
32
- system "gem build mongoid.gemspec"
19
+ command = %w[ gem build ]
20
+ command << "--output=#{ENV['GEM_FILE_NAME']}" if ENV['GEM_FILE_NAME']
21
+ command << (ENV['GEMSPEC'] || 'mongoid.gemspec')
22
+ system(*command)
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 'bundle', 'exec', '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,10 @@ 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
-
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
113
- end
114
- end
115
-
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
129
- end
130
- 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|
@@ -227,9 +227,24 @@ module Mongoid
227
227
  # @example Are there persisted documents?
228
228
  # person.posts.exists?
229
229
  #
230
- # @return [ true | false ] True is persisted documents exist, false if not.
231
- def exists?
232
- _target.any? { |doc| doc.persisted? }
230
+ # @param [ :none | nil | false | Hash | Object ] id_or_conditions
231
+ # When :none (the default), returns true if any persisted
232
+ # documents exist in the association. When nil or false, this
233
+ # will always return false. When a Hash is given, this queries
234
+ # the documents in the association for those that match the given
235
+ # conditions, and returns true if any match which have been
236
+ # persisted. Any other argument is interpreted as an id, and
237
+ # queries for the existence of persisted documents in the
238
+ # association with a matching _id.
239
+ #
240
+ # @return [ true | false ] True if persisted documents exist, false if not.
241
+ def exists?(id_or_conditions = :none)
242
+ case id_or_conditions
243
+ when :none then _target.any?(&:persisted?)
244
+ when nil, false then false
245
+ when Hash then where(id_or_conditions).any?(&:persisted?)
246
+ else where(_id: id_or_conditions).any?(&:persisted?)
247
+ end
233
248
  end
234
249
 
235
250
  # Finds a document in this association through several different
@@ -32,8 +32,8 @@ module Mongoid
32
32
  bind_one
33
33
  characterize_one(_target)
34
34
  update_attributes_hash(_target)
35
- _base._reset_memoized_descendants!
36
35
  _target.save if persistable?
36
+ _base._reset_memoized_descendants!
37
37
  end
38
38
  end
39
39
 
@@ -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
 
@@ -172,9 +172,18 @@ module Mongoid
172
172
  # @example Are there persisted documents?
173
173
  # person.posts.exists?
174
174
  #
175
+ # @param [ :none | nil | false | Hash | Object ] id_or_conditions
176
+ # When :none (the default), returns true if any persisted
177
+ # documents exist in the association. When nil or false, this
178
+ # will always return false. When a Hash is given, this queries
179
+ # the documents in the association for those that match the given
180
+ # conditions, and returns true if any match. Any other argument is
181
+ # interpreted as an id, and queries for the existence of documents
182
+ # in the association with a matching _id.
183
+ #
175
184
  # @return [ true | false ] True is persisted documents exist, false if not.
176
- def exists?
177
- criteria.exists?
185
+ def exists?(id_or_conditions = :none)
186
+ criteria.exists?(id_or_conditions)
178
187
  end
179
188
 
180
189
  # Find the matching document on the association, either based on id or
@@ -178,13 +178,15 @@ module Mongoid
178
178
  #
179
179
  # @return [ Object ] The associated path.
180
180
  def atomic_paths
181
- @atomic_paths ||= begin
182
- if _association
183
- _association.path(self)
184
- else
185
- Atomic::Paths::Root.new(self)
186
- end
187
- end
181
+ return @atomic_paths if @atomic_paths
182
+
183
+ paths = if _association
184
+ _association.path(self)
185
+ else
186
+ Atomic::Paths::Root.new(self)
187
+ end
188
+
189
+ paths.tap { @atomic_paths = paths unless new_record? }
188
190
  end
189
191
 
190
192
  # Get all the attributes that need to be pulled.
@@ -22,7 +22,7 @@ module Mongoid
22
22
  # @return [ true | false ] If the document is new, or if the field is not
23
23
  # readonly.
24
24
  def attribute_writable?(name)
25
- new_record? || (!readonly_attributes.include?(name) && _loaded?(name))
25
+ new_record? || (!self.class.readonly_attributes.include?(name) && _loaded?(name))
26
26
  end
27
27
 
28
28
  private
@@ -62,12 +62,17 @@ module Mongoid
62
62
  # end
63
63
  #
64
64
  # @param [ Symbol... ] *names The names of the fields.
65
+ # @note When a parent class contains readonly attributes and is then
66
+ # inherited by a child class, the child class will inherit the
67
+ # parent's readonly attributes at the time of its creation.
68
+ # Updating the parent does not propagate down to child classes after wards.
65
69
  def attr_readonly(*names)
70
+ self.readonly_attributes = self.readonly_attributes.dup
66
71
  names.each do |name|
67
- readonly_attributes << database_field_name(name)
72
+ self.readonly_attributes << database_field_name(name)
68
73
  end
69
74
  end
70
75
  end
71
76
  end
72
77
  end
73
- end
78
+ end
@@ -1091,7 +1091,7 @@ module Mongoid
1091
1091
  end
1092
1092
 
1093
1093
  def retrieve_nth_to_last_with_limit(n, limit)
1094
- v = view.sort(inverse_sorting).skip(n).limit(limit || 1)
1094
+ v = view.sort(inverse_sorting).limit(limit || 1)
1095
1095
  v = v.skip(n) if n > 0
1096
1096
  raw_docs = v.to_a.reverse
1097
1097
  process_raw_docs(raw_docs, limit)
@@ -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.
@@ -552,7 +552,7 @@ module Mongoid
552
552
  # @return [ Selectable ] The new selectable.
553
553
  def not(*criteria)
554
554
  if criteria.empty?
555
- dup.tap { |query| query.negating = true }
555
+ dup.tap { |query| query.negating = !query.negating }
556
556
  else
557
557
  criteria.compact.inject(self.clone) do |c, new_s|
558
558
  if new_s.is_a?(Selectable)
@@ -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.
@@ -17,6 +17,7 @@ module Mongoid
17
17
  #
18
18
  # @return [ Integer ] -1, 0, 1.
19
19
  def <=>(other)
20
+ return super unless other.is_a?(Mongoid::Equality)
20
21
  attributes["_id"].to_s <=> other.attributes["_id"].to_s
21
22
  end
22
23
 
@@ -49,7 +49,7 @@ module Mongoid
49
49
  consolidated[key].update(value)
50
50
  else
51
51
  consolidated["$set"] ||= {}
52
- consolidated["$set"].update(key => mongoize_for(key, klass, key, value))
52
+ consolidated["$set"].update(key => mongoize_for("$set", klass, key, value))
53
53
  end
54
54
  end
55
55
  consolidated
@@ -187,7 +187,7 @@ module Mongoid
187
187
 
188
188
  # Get the value for the provided operator, klass, key and value.
189
189
  #
190
- # This is necessary for special cases like $rename, $addToSet and $push.
190
+ # This is necessary for special cases like $rename, $addToSet, $push, $pull and $pop.
191
191
  #
192
192
  # @param [ String ] operator The operator.
193
193
  # @param [ Class ] klass The model class.
@@ -198,8 +198,8 @@ module Mongoid
198
198
  def value_for(operator, klass, key, value)
199
199
  case operator
200
200
  when "$rename" then value.to_s
201
- when "$addToSet", "$push" then value.mongoize
202
- else mongoize_for(operator, klass, operator, value)
201
+ when "$addToSet", "$push", '$pull', '$pop' then value.mongoize
202
+ else mongoize_for(operator, klass, key, value)
203
203
  end
204
204
  end
205
205
 
@@ -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
@@ -405,12 +410,12 @@ module Mongoid
405
410
  #
406
411
  # @api private
407
412
  def database_field_name(name, relations, aliased_fields, aliased_associations)
413
+ return '' unless name.present?
414
+
408
415
  if Mongoid.broken_alias_handling
409
- return nil unless name
410
416
  normalized = name.to_s
411
417
  aliased_fields[normalized] || normalized
412
418
  else
413
- return nil unless name.present?
414
419
  key = name.to_s
415
420
  segment, remaining = key.split('.', 2)
416
421
 
@@ -726,11 +731,11 @@ module Mongoid
726
731
  # @api private
727
732
  def create_translations_getter(name, meth)
728
733
  generated_methods.module_eval do
729
- re_define_method("#{meth}_translations") do
734
+ re_define_method("#{meth}#{TRANSLATIONS_SFX}") do
730
735
  attributes[name] ||= {}
731
736
  attributes[name].with_indifferent_access
732
737
  end
733
- alias_method :"#{meth}_t", :"#{meth}_translations"
738
+ alias_method :"#{meth}_t", :"#{meth}#{TRANSLATIONS_SFX}"
734
739
  end
735
740
  end
736
741
 
@@ -746,14 +751,14 @@ module Mongoid
746
751
  # @api private
747
752
  def create_translations_setter(name, meth, field)
748
753
  generated_methods.module_eval do
749
- re_define_method("#{meth}_translations=") do |value|
754
+ re_define_method("#{meth}#{TRANSLATIONS_SFX}=") do |value|
750
755
  attribute_will_change!(name)
751
756
  value&.transform_values! do |_value|
752
757
  field.type.mongoize(_value)
753
758
  end
754
759
  attributes[name] = value
755
760
  end
756
- alias_method :"#{meth}_t=", :"#{meth}_translations="
761
+ alias_method :"#{meth}_t=", :"#{meth}#{TRANSLATIONS_SFX}="
757
762
  end
758
763
  end
759
764
 
@@ -43,8 +43,6 @@ module Mongoid
43
43
  # @api private
44
44
  define_model_callbacks :persist_parent
45
45
 
46
- define_model_callbacks :commit, :rollback, only: :after
47
-
48
46
  attr_accessor :before_callback_halted
49
47
  end
50
48
 
@@ -143,9 +141,13 @@ module Mongoid
143
141
  # @api private
144
142
  def _mongoid_run_child_callbacks(kind, children: nil, &block)
145
143
  if Mongoid::Config.around_callbacks_for_embeds
146
- _mongoid_run_child_callbacks_with_around(kind, children: children, &block)
144
+ _mongoid_run_child_callbacks_with_around(kind,
145
+ children: children,
146
+ &block)
147
147
  else
148
- _mongoid_run_child_callbacks_without_around(kind, children: children, &block)
148
+ _mongoid_run_child_callbacks_without_around(kind,
149
+ children: children,
150
+ &block)
149
151
  end
150
152
  end
151
153
 
@@ -221,9 +223,6 @@ module Mongoid
221
223
  return false if env.halted
222
224
  env.value = !env.halted
223
225
  callback_list << [next_sequence, env]
224
- if (grandchildren = child.send(:cascadable_children, kind))
225
- _mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
226
- end
227
226
  end
228
227
  callback_list
229
228
  end
@@ -35,11 +35,25 @@ module Mongoid
35
35
  # from and behaves identically to association traversal for the purposes
36
36
  # of, for example, subsequent array element retrieval.
37
37
  #
38
- # @param [ Document | Hash ] document The document to extract from.
38
+ # @param [ Document | Hash | String ] document The document to extract from.
39
39
  # @param [ String ] key The key path to extract.
40
40
  #
41
41
  # @return [ Object | Array ] Field value or values.
42
42
  module_function def extract_attribute(document, key)
43
+ # The matcher system will wind up sending atomic values to this as well,
44
+ # when attepting to match more complex types. If anything other than a
45
+ # Document or a Hash is given, we'll short-circuit the logic and just
46
+ # return an empty array.
47
+ return [] unless document.is_a?(Hash) || document.is_a?(Document)
48
+
49
+ # Performance optimization; if the key does not include a '.' character,
50
+ # it must reference an immediate attribute of the document.
51
+ unless key.include?('.')
52
+ hash = document.respond_to?(:attributes) ? document.attributes : document
53
+ key = find_exact_key(hash, key)
54
+ return key ? [ hash[key] ] : []
55
+ end
56
+
43
57
  if document.respond_to?(:as_attributes, true)
44
58
  # If a document has hash fields, as_attributes would keep those fields
45
59
  # as Hash instances which do not offer indifferent access.
@@ -12,13 +12,13 @@ module Mongoid
12
12
  included do
13
13
 
14
14
  class << self
15
- # Note that this intentionally only delegates :include_root_in_json
16
- # and not :include_root_in_json? - delegating the latter produces
17
- # wrong behavior.
18
- # Also note that this intentionally uses the ActiveSupport delegation
19
- # functionality and not the Ruby standard library one.
20
- # See https://jira.mongodb.org/browse/MONGOID-4849.
21
- delegate :include_root_in_json, to: ::Mongoid
15
+ def include_root_in_json
16
+ @include_root_in_json.nil? ? ::Mongoid.include_root_in_json : @include_root_in_json
17
+ end
18
+
19
+ def include_root_in_json=(new_value)
20
+ @include_root_in_json = new_value
21
+ end
22
22
  end
23
23
  end
24
24
 
@@ -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
@@ -25,7 +25,7 @@ module Mongoid
25
25
  current = Time.configured.now
26
26
  field = database_field_name(field)
27
27
  write_attribute(:updated_at, current) if respond_to?("updated_at=")
28
- write_attribute(field, current) if field
28
+ write_attribute(field, current) if field.present?
29
29
 
30
30
  # If the document being touched is embedded, touch its parents
31
31
  # all the way through the composition hierarchy to the root object,
@@ -7,6 +7,29 @@ module Mongoid
7
7
  # Provides behavior around traversing the document graph.
8
8
  module Traversable
9
9
  extend ActiveSupport::Concern
10
+ # This code is extracted from ActiveSupport so that we do not depend on
11
+ # their private API that may change at any time.
12
+ # This code should be reviewed and maybe removed when implementing
13
+ # https://jira.mongodb.org/browse/MONGOID-5832
14
+ class << self
15
+ # @api private
16
+ def __redefine(owner, name, value)
17
+ if owner.singleton_class?
18
+ owner.redefine_method(name) { value }
19
+ owner.send(:public, name)
20
+ end
21
+ owner.redefine_singleton_method(name) { value }
22
+ owner.singleton_class.send(:public, name)
23
+ owner.redefine_singleton_method("#{name}=") do |new_value|
24
+ if owner.equal?(self)
25
+ value = new_value
26
+ else
27
+ ::Mongoid::Traversable.redefine(self, name, new_value)
28
+ end
29
+ end
30
+ owner.singleton_class.send(:public, "#{name}=")
31
+ end
32
+ end
10
33
 
11
34
  def _parent
12
35
  @__parent ||= nil
@@ -30,7 +53,7 @@ module Mongoid
30
53
  if value
31
54
  Mongoid::Fields::Validators::Macro.validate_field_name(self, value)
32
55
  value = value.to_s
33
- super
56
+ ::Mongoid::Traversable.__redefine(self, 'discriminator_key', value)
34
57
  else
35
58
  # When discriminator key is set to nil, replace the class's definition
36
59
  # of the discriminator key reader (provided by class_attribute earlier)
@@ -300,6 +323,18 @@ module Mongoid
300
323
  !!(Mongoid::Document > superclass)
301
324
  end
302
325
 
326
+ # Returns the root class of the STI tree that the current
327
+ # class participates in. If the class is not an STI subclass, this
328
+ # returns the class itself.
329
+ #
330
+ # @return [ Mongoid::Document ] the root of the STI tree
331
+ def root_class
332
+ root = self
333
+ root = root.superclass while root.hereditary?
334
+
335
+ root
336
+ end
337
+
303
338
  # When inheriting, we want to copy the fields from the parent class and
304
339
  # set the on the child to start, mimicking the behavior of the old
305
340
  # class_inheritable_accessor that was deprecated in Rails edge.