mongoid 9.0.7 → 9.0.9

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mongoid/association/embedded/batchable.rb +11 -10
  3. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +64 -29
  4. data/lib/mongoid/association/nested/many.rb +2 -0
  5. data/lib/mongoid/association/nested/one.rb +1 -1
  6. data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +0 -6
  7. data/lib/mongoid/association/referenced/has_many/enumerable.rb +40 -0
  8. data/lib/mongoid/association/referenced/has_many/proxy.rb +17 -5
  9. data/lib/mongoid/attributes.rb +19 -1
  10. data/lib/mongoid/changeable.rb +10 -1
  11. data/lib/mongoid/clients/sessions.rb +3 -4
  12. data/lib/mongoid/config.rb +1 -1
  13. data/lib/mongoid/contextual/aggregable/mongo.rb +6 -1
  14. data/lib/mongoid/contextual/mongo.rb +8 -89
  15. data/lib/mongoid/pluckable.rb +132 -0
  16. data/lib/mongoid/railties/bson_object_id_serializer.rb +7 -0
  17. data/lib/mongoid/reloadable.rb +6 -0
  18. data/lib/mongoid/traversable.rb +0 -2
  19. data/lib/mongoid/version.rb +1 -1
  20. data/spec/integration/associations/embeds_many_spec.rb +110 -0
  21. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +81 -0
  22. data/spec/integration/associations/has_many_spec.rb +56 -0
  23. data/spec/integration/associations/has_one_spec.rb +55 -3
  24. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +394 -0
  25. data/spec/mongoid/association/referenced/has_many_models.rb +24 -0
  26. data/spec/mongoid/association/referenced/has_one_models.rb +10 -2
  27. data/spec/mongoid/attributes_spec.rb +13 -0
  28. data/spec/mongoid/clients/transactions_spec.rb +162 -1
  29. data/spec/mongoid/clients/transactions_spec_models.rb +93 -0
  30. data/spec/mongoid/contextual/aggregable/mongo_spec.rb +33 -0
  31. data/spec/mongoid/reloadable_spec.rb +24 -0
  32. data/spec/shared/CANDIDATE.md +28 -0
  33. data/spec/shared/lib/mrss/spec_organizer.rb +32 -3
  34. data/spec/shared/shlib/server.sh +1 -1
  35. data/spec/support/models/company.rb +2 -0
  36. data/spec/support/models/passport.rb +1 -0
  37. data/spec/support/models/product.rb +2 -0
  38. data/spec/support/models/seo.rb +2 -0
  39. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72ad16d0232a1f1c1144801896ec7322b6c452100e02e9bc5c1ba6d21e015e62
4
- data.tar.gz: 5ca24809ea6498805807e80e78d347a8f2d517862d2ace0c39a73fb81918e178
3
+ metadata.gz: c5dca0df68ed7086b6c641b1021e556812e23872d5bd52a9777093e2d8cf1e64
4
+ data.tar.gz: 24630d19104369a922b3a5a152012ae8eba3719576ef3c9e5f97304b6896a1c4
5
5
  SHA512:
6
- metadata.gz: 82a45afa6174672d301145d1910eb6ef4d86433f078fd6514c22d644c101637404f5c2562e497dd3c9576935ce0f267fb3993d3493c520645a85e42799d0601f
7
- data.tar.gz: d01a7d34d5b01aa5b7629b4dad7735f6c86756e86e4cbe6695301f1fe1864043041ccf4beb785bb28e48b2886d85a2c7dc4d54dddbf644a24f07b9852e15ba17
6
+ metadata.gz: 03013a0c28cda90cfc3b99c26d22e7672e8ce1c6449e599d7ae39a42d19ba742f37dea7510411ca004c7d5d5f25af80e9230bac63fef8ce43515711d7f669343
7
+ data.tar.gz: 8bd5b0c4faa4f5f16fcc56ff612bae5a14fd40fa01513fda545469d225c6cd4909022c066719950147dc116dc1a7bdb79b69d48ad40454aabdb45481994dd69b
@@ -313,18 +313,19 @@ module Mongoid
313
313
  #
314
314
  # @return [ Array<Hash> ] The documents as an array of hashes.
315
315
  def pre_process_batch_insert(docs)
316
- docs.map do |doc|
317
- next unless doc
318
- append(doc)
319
- if persistable? && !_assigning?
320
- self.path = doc.atomic_path unless path
321
- if doc.valid?(:create)
322
- doc.run_before_callbacks(:save, :create)
323
- else
324
- self.inserts_valid = false
316
+ [].tap do |results|
317
+ append_many(docs) do |doc|
318
+ if persistable? && !_assigning?
319
+ self.path = doc.atomic_path unless path
320
+ if doc.valid?(:create)
321
+ doc.run_before_callbacks(:save, :create)
322
+ else
323
+ self.inserts_valid = false
324
+ end
325
325
  end
326
+
327
+ results << doc.send(:as_attributes)
326
328
  end
327
- doc.send(:as_attributes)
328
329
  end
329
330
  end
330
331
 
@@ -14,6 +14,7 @@ module Mongoid
14
14
  # the array of child documents.
15
15
  class Proxy < Association::Many
16
16
  include Batchable
17
+ extend Forwardable
17
18
 
18
19
  # Class-level methods for the Proxy class.
19
20
  module ClassMethods
@@ -54,6 +55,8 @@ module Mongoid
54
55
 
55
56
  extend ClassMethods
56
57
 
58
+ def_delegators :criteria, :find, :pluck
59
+
57
60
  # Instantiate a new embeds_many association.
58
61
  #
59
62
  # @example Create the new association.
@@ -312,35 +315,6 @@ module Mongoid
312
315
  end
313
316
  end
314
317
 
315
- # Finds a document in this association through several different
316
- # methods.
317
- #
318
- # This method delegates to +Mongoid::Criteria#find+. If this method is
319
- # not given a block, it returns one or many documents for the provided
320
- # _id values.
321
- #
322
- # If this method is given a block, it returns the first document
323
- # of those found by the current Criteria object for which the block
324
- # returns a truthy value.
325
- #
326
- # @example Find a document by its id.
327
- # person.addresses.find(BSON::ObjectId.new)
328
- #
329
- # @example Find documents for multiple ids.
330
- # person.addresses.find([ BSON::ObjectId.new, BSON::ObjectId.new ])
331
- #
332
- # @example Finds the first matching document using a block.
333
- # person.addresses.find { |addr| addr.state == 'CA' }
334
- #
335
- # @param [ Object... ] *args Various arguments.
336
- # @param &block Optional block to pass.
337
- # @yield [ Object ] Yields each enumerable element to the block.
338
- #
339
- # @return [ Document | Array<Document> | nil ] A document or matching documents.
340
- def find(...)
341
- criteria.find(...)
342
- end
343
-
344
318
  # Get all the documents in the association that are loaded into memory.
345
319
  #
346
320
  # @example Get the in memory documents.
@@ -443,6 +417,67 @@ module Mongoid
443
417
  execute_callback :after_add, document
444
418
  end
445
419
 
420
+ # Returns a unique id for the document, which is either
421
+ # its _id or its object_id.
422
+ def id_of(doc)
423
+ doc._id || doc.object_id
424
+ end
425
+
426
+ # Optimized version of #append that handles multiple documents
427
+ # in a more efficient way.
428
+ #
429
+ # @param [ Array<Document> ] documents The documents to append.
430
+ #
431
+ # @return [ EmbedsMany::Proxy ] This proxy instance.
432
+ def append_many(documents, &block)
433
+ unique_set = process_incoming_docs(documents, &block)
434
+
435
+ _unscoped.concat(unique_set)
436
+ _target.push(*scope(unique_set))
437
+ update_attributes_hash
438
+
439
+ unique_set.each { |doc| execute_callback :after_add, doc }
440
+
441
+ self
442
+ end
443
+
444
+ # Processes the list of documents, building a list of those
445
+ # that are not already in the association, and preparing
446
+ # each unique document to be integrated into the association.
447
+ #
448
+ # The :before_add callback is executed for each unique document
449
+ # as part of this step.
450
+ #
451
+ # @param [ Array<Document> ] documents The incoming documents to
452
+ # process.
453
+ #
454
+ # @yield [ Document ] Optional block to call for each unique
455
+ # document.
456
+ #
457
+ # @return [ Array<Document> ] The list of unique documents that
458
+ # do not yet exist in the association.
459
+ def process_incoming_docs(documents, &block)
460
+ visited_docs = Set.new(_target.map { |doc| id_of(doc) })
461
+ next_index = _unscoped.size
462
+
463
+ documents.select do |doc|
464
+ next unless doc
465
+
466
+ id = id_of(doc)
467
+ next if visited_docs.include?(id)
468
+
469
+ execute_callback :before_add, doc
470
+
471
+ visited_docs.add(id)
472
+ integrate(doc)
473
+
474
+ doc._index = next_index
475
+ next_index += 1
476
+
477
+ block&.call(doc) || true
478
+ end
479
+ end
480
+
446
481
  # Instantiate the binding associated with this association.
447
482
  #
448
483
  # @example Create the binding.
@@ -190,6 +190,8 @@ module Mongoid
190
190
  update_document(doc, attrs)
191
191
  existing.push(doc) unless destroyable?(attrs)
192
192
  end
193
+
194
+ parent.children_may_have_changed!
193
195
  end
194
196
  end
195
197
  end
@@ -37,7 +37,7 @@ module Mongoid
37
37
  parent.send(association.setter, nil)
38
38
  else
39
39
  check_for_id_violation!
40
- end
40
+ end.tap { parent.children_may_have_changed! }
41
41
  end
42
42
 
43
43
  # Create the new builder for nested attributes on one-to-one
@@ -53,8 +53,6 @@ module Mongoid
53
53
  # @param [ Document... ] *args Any number of documents.
54
54
  #
55
55
  # @return [ Array<Document> ] The loaded docs.
56
- #
57
- # rubocop:disable Metrics/AbcSize
58
56
  def <<(*args)
59
57
  docs = args.flatten
60
58
  return concat(docs) if docs.size > 1
@@ -89,7 +87,6 @@ module Mongoid
89
87
  end
90
88
  unsynced(_base, foreign_key) and self
91
89
  end
92
- # rubocop:enable Metrics/AbcSize
93
90
 
94
91
  alias push <<
95
92
 
@@ -360,8 +357,6 @@ module Mongoid
360
357
  # in bulk
361
358
  # @param [ Array ] inserts the list of Hashes of attributes that will
362
359
  # be inserted (corresponding to the ``docs`` list)
363
- #
364
- # rubocop:disable Metrics/AbcSize
365
360
  def append_document(doc, ids, docs, inserts)
366
361
  return unless doc
367
362
 
@@ -379,7 +374,6 @@ module Mongoid
379
374
  unsynced(_base, foreign_key)
380
375
  end
381
376
  end
382
- # rubocop:enable Metrics/AbcSize
383
377
  end
384
378
  end
385
379
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # rubocop:todo all
3
3
 
4
+ require 'mongoid/pluckable'
5
+
4
6
  module Mongoid
5
7
  module Association
6
8
  module Referenced
@@ -12,6 +14,7 @@ module Mongoid
12
14
  class Enumerable
13
15
  extend Forwardable
14
16
  include ::Enumerable
17
+ include Pluckable
15
18
 
16
19
  # The three main instance variables are collections of documents.
17
20
  #
@@ -374,6 +377,43 @@ module Mongoid
374
377
  @_added, @_loaded, @_unloaded, @executed = data
375
378
  end
376
379
 
380
+ # Plucks the given field names from the documents in the target.
381
+ # If the collection has been loaded, it plucks from the loaded
382
+ # documents; otherwise, it plucks from the unloaded criteria.
383
+ # Regardless, it also plucks from any added documents.
384
+ #
385
+ # @param [ Symbol... ] *fields The field names to pluck.
386
+ #
387
+ # @return [ Array | Array<Array> ] The array of field values. If
388
+ # multiple fields are given, an array of arrays is returned.
389
+ def pluck(*keys)
390
+ [].tap do |results|
391
+ if _loaded? || _added.any?
392
+ klass = @_association.klass
393
+ prepared = prepare_pluck(keys, document_class: klass)
394
+ end
395
+
396
+ if _loaded?
397
+ docs = _loaded.values.map { |v| BSON::Document.new(v.attributes) }
398
+ results.concat pluck_from_documents(docs, prepared[:field_names], document_class: klass)
399
+ elsif _unloaded
400
+ criteria = if _added.any?
401
+ ids_to_exclude = _added.keys
402
+ _unloaded.not(:_id.in => ids_to_exclude)
403
+ else
404
+ _unloaded
405
+ end
406
+
407
+ results.concat criteria.pluck(*keys)
408
+ end
409
+
410
+ if _added.any?
411
+ docs = _added.values.map { |v| BSON::Document.new(v.attributes) }
412
+ results.concat pluck_from_documents(docs, prepared[:field_names], document_class: klass)
413
+ end
414
+ end
415
+ end
416
+
377
417
  # Reset the enumerable back to its persisted state.
378
418
  #
379
419
  # @example Reset the enumerable.
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # TODO: consider refactoring this Proxy class, to satisfy the following
4
- # cops...
5
- # rubocop:disable Metrics/ClassLength
6
3
  module Mongoid
7
4
  module Association
8
5
  module Referenced
@@ -36,7 +33,7 @@ module Mongoid
36
33
 
37
34
  extend ClassMethods
38
35
 
39
- def_delegator :criteria, :count
36
+ def_delegators :criteria, :count
40
37
  def_delegators :_target, :first, :in_memory, :last, :reset, :uniq
41
38
 
42
39
  # Instantiate a new references_many association. Will set the foreign key
@@ -284,6 +281,22 @@ module Mongoid
284
281
 
285
282
  alias nullify_all nullify
286
283
 
284
+ # Plucks the given field names from the documents in the
285
+ # association. It is safe to use whether the association is
286
+ # loaded or not, and whether there are unsaved documents in the
287
+ # association or not.
288
+ #
289
+ # @example Pluck the titles of all posts.
290
+ # person.posts.pluck(:title)
291
+ #
292
+ # @param [ Symbol... ] *fields The field names to pluck.
293
+ #
294
+ # @return [ Array | Array<Array> ] The array of field values. If
295
+ # multiple fields are given, an array of arrays is returned.
296
+ def pluck(*fields)
297
+ _target.pluck(*fields)
298
+ end
299
+
287
300
  # Clear the association. Will delete the documents from the db if they are
288
301
  # already persisted.
289
302
  #
@@ -588,4 +601,3 @@ module Mongoid
588
601
  end
589
602
  end
590
603
  end
591
- # rubocop:enable Metrics/ClassLength
@@ -175,7 +175,7 @@ module Mongoid
175
175
  localized = fields[field_name].try(:localized?)
176
176
  attributes_before_type_cast[name.to_s] = value
177
177
  typed_value = typed_value_for(field_name, value)
178
- unless attributes[field_name] == typed_value || attribute_changed?(field_name)
178
+ unless attribute_will_not_change?(field_name, typed_value) || attribute_changed?(field_name)
179
179
  attribute_will_change!(field_name)
180
180
  end
181
181
  if localized
@@ -366,5 +366,23 @@ module Mongoid
366
366
  end
367
367
  value.present?
368
368
  end
369
+
370
+ # If `value` is a `BSON::Decimal128`, convert it to a `BigDecimal` for
371
+ # comparison purposes. This is necessary because `BSON::Decimal128` does
372
+ # not implement `#==` in a way that is compatible with `BigDecimal`.
373
+ def normalize_value(value)
374
+ value.is_a?(BSON::Decimal128) ? BigDecimal(value.to_s) : value
375
+ end
376
+
377
+ # Determine if the attribute will not change, by comparing the current
378
+ # value with the new value. The values are normalized to account for
379
+ # types that do not implement `#==` in a way that is compatible with
380
+ # each other, such as `BSON::Decimal128` and `BigDecimal`.
381
+ def attribute_will_not_change?(field_name, typed_value)
382
+ normalized_attribute = normalize_value(attributes[field_name])
383
+ normalized_typed_value = normalize_value(typed_value)
384
+
385
+ normalized_attribute == normalized_typed_value
386
+ end
369
387
  end
370
388
  end
@@ -15,6 +15,14 @@ module Mongoid
15
15
  changed_attributes.keys.select { |attr| attribute_change(attr) }
16
16
  end
17
17
 
18
+ # Indicates that the children of this document may have changed, and
19
+ # ought to be checked when the document is validated.
20
+ #
21
+ # @api private
22
+ def children_may_have_changed!
23
+ @children_may_have_changed = true
24
+ end
25
+
18
26
  # Has the document changed?
19
27
  #
20
28
  # @example Has the document changed?
@@ -31,7 +39,7 @@ module Mongoid
31
39
  #
32
40
  # @return [ true | false ] If any children have changed.
33
41
  def children_changed?
34
- _children.any?(&:changed?)
42
+ @children_may_have_changed || _children.any?(&:changed?)
35
43
  end
36
44
 
37
45
  # Get the attribute changes.
@@ -69,6 +77,7 @@ module Mongoid
69
77
  @previous_changes = changes
70
78
  @attributes_before_last_save = @previous_attributes
71
79
  @previous_attributes = attributes.dup
80
+ @children_may_have_changed = false
72
81
  reset_atomic_updates!
73
82
  changed_attributes.clear
74
83
  end
@@ -92,8 +92,7 @@ module Mongoid
92
92
  begin
93
93
  session.with_transaction(options) do
94
94
  yield
95
- end
96
- run_commit_callbacks(session)
95
+ end.tap { run_commit_callbacks(session) }
97
96
  rescue *transactions_not_supported_exceptions
98
97
  raise Mongoid::Errors::TransactionsNotSupported
99
98
  rescue Mongoid::Errors::Rollback
@@ -213,8 +212,8 @@ module Mongoid
213
212
 
214
213
  # Transforms custom options for after_commit and after_rollback callbacks
215
214
  # into options for +set_callback+.
216
- def set_options_for_callbacks!(args)
217
- options = args.extract_options!
215
+ def set_options_for_callbacks!(args, enforced_options = {})
216
+ options = args.extract_options!.merge(enforced_options)
218
217
  args << options
219
218
 
220
219
  if options[:on]
@@ -102,7 +102,7 @@ module Mongoid
102
102
  #
103
103
  # - :immediate - Initializes a single +Concurrent::ImmediateExecutor+
104
104
  # - :global_thread_pool - Initializes a single +Concurrent::ThreadPoolExecutor+
105
- # that uses the +async_query_concurrency+ for the +max_threads+ value.
105
+ # that uses the +global_executor_concurrency+ for the +max_threads+ value.
106
106
  option :async_query_executor, default: :immediate
107
107
 
108
108
  # Defines how many asynchronous queries can be executed concurrently.
@@ -27,7 +27,12 @@ module Mongoid
27
27
  # If no documents are found, then returned Hash will have
28
28
  # count, sum of 0 and max, min, avg of nil.
29
29
  def aggregates(field)
30
- result = collection.aggregate(pipeline(field), session: _session).to_a
30
+ result = collection.aggregate(
31
+ pipeline(field),
32
+ session: _session,
33
+ hint: view.hint
34
+ ).to_a
35
+
31
36
  if result.empty?
32
37
  Aggregable::EMPTY_RESULT.dup
33
38
  else
@@ -2,6 +2,7 @@
2
2
  # rubocop:todo all
3
3
 
4
4
  require 'mongoid/atomic_update_preparer'
5
+ require 'mongoid/pluckable'
5
6
  require "mongoid/contextual/mongo/documents_loader"
6
7
  require "mongoid/contextual/atomic"
7
8
  require "mongoid/contextual/aggregable/mongo"
@@ -22,6 +23,7 @@ module Mongoid
22
23
  include Atomic
23
24
  include Association::EagerLoadable
24
25
  include Queryable
26
+ include Pluckable
25
27
 
26
28
  # Options constant.
27
29
  OPTIONS = [ :hint,
@@ -331,23 +333,12 @@ module Mongoid
331
333
  # in the array will be a single value. Otherwise, each
332
334
  # result in the array will be an array of values.
333
335
  def pluck(*fields)
334
- # Multiple fields can map to the same field name. For example, plucking
335
- # a field and its _translations field map to the same field in the database.
336
- # because of this, we need to keep track of the fields requested.
337
- normalized_field_names = []
338
- normalized_select = fields.inject({}) do |hash, f|
339
- db_fn = klass.database_field_name(f)
340
- normalized_field_names.push(db_fn)
341
- hash[klass.cleanse_localized_field_names(f)] = true
342
- hash
343
- end
344
-
345
- view.projection(normalized_select).reduce([]) do |plucked, doc|
346
- values = normalized_field_names.map do |n|
347
- extract_value(doc, n)
348
- end
349
- plucked << (values.size == 1 ? values.first : values)
350
- end
336
+ # Multiple fields can map to the same field name. For example,
337
+ # plucking a field and its _translations field map to the same
338
+ # field in the database. because of this, we need to prepare the
339
+ # projection specifically.
340
+ prep = prepare_pluck(fields, prepare_projection: true)
341
+ pluck_from_documents(view.projection(prep[:projection]), prep[:field_names])
351
342
  end
352
343
 
353
344
  # Pick the single field values from the database.
@@ -893,78 +884,6 @@ module Mongoid
893
884
  collection.write_concern.nil? || collection.write_concern.acknowledged?
894
885
  end
895
886
 
896
- # Fetch the element from the given hash and demongoize it using the
897
- # given field. If the obj is an array, map over it and call this method
898
- # on all of its elements.
899
- #
900
- # @param [ Hash | Array<Hash> ] obj The hash or array of hashes to fetch from.
901
- # @param [ String ] meth The key to fetch from the hash.
902
- # @param [ Field ] field The field to use for demongoization.
903
- #
904
- # @return [ Object ] The demongoized value.
905
- #
906
- # @api private
907
- def fetch_and_demongoize(obj, meth, field)
908
- if obj.is_a?(Array)
909
- obj.map { |doc| fetch_and_demongoize(doc, meth, field) }
910
- else
911
- res = obj.try(:fetch, meth, nil)
912
- field ? field.demongoize(res) : res.class.demongoize(res)
913
- end
914
- end
915
-
916
- # Extracts the value for the given field name from the given attribute
917
- # hash.
918
- #
919
- # @param [ Hash ] attrs The attributes hash.
920
- # @param [ String ] field_name The name of the field to extract.
921
- #
922
- # @param [ Object ] The value for the given field name
923
- def extract_value(attrs, field_name)
924
- i = 1
925
- num_meths = field_name.count('.') + 1
926
- curr = attrs.dup
927
-
928
- klass.traverse_association_tree(field_name) do |meth, obj, is_field|
929
- field = obj if is_field
930
- is_translation = false
931
- # If no association or field was found, check if the meth is an
932
- # _translations field.
933
- if obj.nil? & tr = meth.match(/(.*)_translations\z/)&.captures&.first
934
- is_translation = true
935
- meth = tr
936
- end
937
-
938
- # 1. If curr is an array fetch from all elements in the array.
939
- # 2. If the field is localized, and is not an _translations field
940
- # (_translations fields don't show up in the fields hash).
941
- # - If this is the end of the methods, return the translation for
942
- # the current locale.
943
- # - Otherwise, return the whole translations hash so the next method
944
- # can select the language it wants.
945
- # 3. If the meth is an _translations field, do not demongoize the
946
- # value so the full hash is returned.
947
- # 4. Otherwise, fetch and demongoize the value for the key meth.
948
- curr = if curr.is_a? Array
949
- res = fetch_and_demongoize(curr, meth, field)
950
- res.empty? ? nil : res
951
- elsif !is_translation && field&.localized?
952
- if i < num_meths
953
- curr.try(:fetch, meth, nil)
954
- else
955
- fetch_and_demongoize(curr, meth, field)
956
- end
957
- elsif is_translation
958
- curr.try(:fetch, meth, nil)
959
- else
960
- fetch_and_demongoize(curr, meth, field)
961
- end
962
-
963
- i += 1
964
- end
965
- curr
966
- end
967
-
968
887
  # Recursively demongoize the given value. This method recursively traverses
969
888
  # the class tree to find the correct field to use to demongoize the value.
970
889
  #