mongoid 7.0.0.beta → 7.0.0

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 (86) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/config/locales/en.yml +21 -0
  5. data/lib/mongoid.rb +1 -1
  6. data/lib/mongoid/association/embedded/batchable.rb +39 -13
  7. data/lib/mongoid/association/many.rb +4 -0
  8. data/lib/mongoid/association/referenced/has_many.rb +2 -0
  9. data/lib/mongoid/association/referenced/has_many/enumerable.rb +38 -7
  10. data/lib/mongoid/association/referenced/has_many/proxy.rb +3 -2
  11. data/lib/mongoid/association/referenced/syncable.rb +1 -1
  12. data/lib/mongoid/association/touchable.rb +1 -1
  13. data/lib/mongoid/atomic.rb +3 -3
  14. data/lib/mongoid/atomic/modifiers.rb +12 -8
  15. data/lib/mongoid/attributes.rb +20 -12
  16. data/lib/mongoid/attributes/dynamic.rb +2 -2
  17. data/lib/mongoid/changeable.rb +1 -1
  18. data/lib/mongoid/clients.rb +2 -0
  19. data/lib/mongoid/clients/sessions.rb +113 -0
  20. data/lib/mongoid/clients/storage_options.rb +1 -0
  21. data/lib/mongoid/composable.rb +1 -0
  22. data/lib/mongoid/contextual/aggregable/mongo.rb +1 -1
  23. data/lib/mongoid/contextual/atomic.rb +6 -3
  24. data/lib/mongoid/contextual/geo_near.rb +1 -1
  25. data/lib/mongoid/contextual/map_reduce.rb +6 -2
  26. data/lib/mongoid/contextual/memory.rb +25 -2
  27. data/lib/mongoid/contextual/mongo.rb +33 -14
  28. data/lib/mongoid/copyable.rb +2 -2
  29. data/lib/mongoid/criteria.rb +1 -0
  30. data/lib/mongoid/criteria/queryable/extensions/string.rb +1 -1
  31. data/lib/mongoid/criteria/queryable/mergeable.rb +3 -1
  32. data/lib/mongoid/errors.rb +1 -0
  33. data/lib/mongoid/errors/invalid_session_use.rb +24 -0
  34. data/lib/mongoid/extensions.rb +0 -4
  35. data/lib/mongoid/extensions/hash.rb +5 -2
  36. data/lib/mongoid/factory.rb +14 -5
  37. data/lib/mongoid/fields.rb +2 -2
  38. data/lib/mongoid/indexable.rb +4 -4
  39. data/lib/mongoid/matchable/and.rb +1 -1
  40. data/lib/mongoid/matchable/elem_match.rb +9 -3
  41. data/lib/mongoid/persistable.rb +2 -2
  42. data/lib/mongoid/persistable/creatable.rb +4 -2
  43. data/lib/mongoid/persistable/deletable.rb +4 -2
  44. data/lib/mongoid/persistable/destroyable.rb +1 -5
  45. data/lib/mongoid/persistable/updatable.rb +2 -2
  46. data/lib/mongoid/persistable/upsertable.rb +2 -1
  47. data/lib/mongoid/persistence_context.rb +5 -4
  48. data/lib/mongoid/query_cache.rb +5 -3
  49. data/lib/mongoid/reloadable.rb +1 -1
  50. data/lib/mongoid/serializable.rb +1 -1
  51. data/lib/mongoid/shardable.rb +1 -1
  52. data/lib/mongoid/tasks/database.rb +3 -2
  53. data/lib/mongoid/threaded.rb +38 -0
  54. data/lib/mongoid/traversable.rb +1 -1
  55. data/lib/mongoid/version.rb +1 -1
  56. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +4 -0
  57. data/spec/app/models/band.rb +1 -0
  58. data/spec/app/models/shipment_address.rb +1 -0
  59. data/spec/mongoid/association/macros_spec.rb +20 -0
  60. data/spec/mongoid/association/referenced/has_many/eager_spec.rb +15 -0
  61. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +229 -0
  62. data/spec/mongoid/atomic/modifiers_spec.rb +17 -17
  63. data/spec/mongoid/atomic_spec.rb +17 -17
  64. data/spec/mongoid/attributes_spec.rb +38 -2
  65. data/spec/mongoid/clients/sessions_spec.rb +325 -0
  66. data/spec/mongoid/contextual/memory_spec.rb +19 -0
  67. data/spec/mongoid/contextual/mongo_spec.rb +133 -0
  68. data/spec/mongoid/copyable_spec.rb +34 -0
  69. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +43 -0
  70. data/spec/mongoid/criteria/queryable/selectable_spec.rb +32 -3
  71. data/spec/mongoid/extensions/hash_spec.rb +18 -1
  72. data/spec/mongoid/factory_spec.rb +11 -0
  73. data/spec/mongoid/interceptable_spec.rb +3 -1
  74. data/spec/mongoid/matchable/elem_match_spec.rb +20 -0
  75. data/spec/mongoid/persistable/deletable_spec.rb +19 -0
  76. data/spec/mongoid/persistable/destroyable_spec.rb +19 -0
  77. data/spec/mongoid/persistable/savable_spec.rb +2 -2
  78. data/spec/mongoid/persistable/updatable_spec.rb +2 -2
  79. data/spec/mongoid/persistable_spec.rb +31 -16
  80. data/spec/mongoid/persistence_context_spec.rb +14 -0
  81. data/spec/mongoid/positional_spec.rb +10 -10
  82. data/spec/mongoid/query_cache_spec.rb +2 -16
  83. data/spec/mongoid/shardable_spec.rb +32 -12
  84. data/spec/spec_helper.rb +74 -0
  85. metadata +456 -446
  86. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6f483fd6267d4f9d7cdd88cd53e8fda43b3266d5
4
- data.tar.gz: a9dc77a3e44b2e719b5bc5e74c918c0d20dc964d
2
+ SHA256:
3
+ metadata.gz: 8d7943ffc77e9a6990cb063c1301476cd1d5b10cd8543f0e9861e96228ff8027
4
+ data.tar.gz: bc71866e339c29688dae94bdd8b4590b198ab25b503bc321802d6acea4af5c25
5
5
  SHA512:
6
- metadata.gz: 5404ab3c5ee259a29c80848cdf90f5709cb4665945c41edbaab5c329a9bd28b5350dedc115e3e41d537043f730f1b90db8646a5d8b075c26f324c0a94f4e6b35
7
- data.tar.gz: f14bad6291f3628cf3c767c55b7707d32f9fe0bb001e34bd466c66d483378752f0550666359a206afbd1f0bf3409b6fb4eb12f4b2fea0cc4cc0e83f737a50da6
6
+ metadata.gz: 51e00c706c33a552c148b768bf851042a2f5c46aecacd3aa6bed5105c8c9b002bf84f317b1d5d65ee209e153537af5fb7360560758aef34ca4cbc39ffa11b489
7
+ data.tar.gz: 81a21a9ee3e6aa381bb495eb098b7faba65f2fa32c304460ecb481356ab505bb1189c4850cf218680d93c269cb656360727d3ed3fd967de5525ff92bf510425d
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -216,6 +216,20 @@ en:
216
216
  \_\_\_\_include Mongoid::Document\n
217
217
  \_\_\_\_scope :inactive, ->{ where(active: false) }\n
218
218
  \_\_end\n\n"
219
+ invalid_session_use:
220
+ message: "A session was attempted to be used with a model whose client cannot use
221
+ that session."
222
+ summary: "Sessions are started via driver clients (Model#mongo_client) and, in most cases, driver
223
+ clients are shared across models. When different models have their own clients, a session cannot
224
+ be obtained via one model and used for operations on another model."
225
+ resolution: "Only execute operations on the model class or instances of the model through which
226
+ the session was created. Otherwise, ensure that all models on which operations are executed
227
+ in the session block share the same driver client. For example, a model may have a different
228
+ client specified in its 'store_in' options.\n\n"
229
+ invalid_session_nesting:
230
+ message: "A session was started while another session was being used."
231
+ summary: "Sessions cannot be nested. Only one session can be used in a thread at once."
232
+ resolution: "Only use one session at a time; sessions cannot be nested."
219
233
  invalid_storage_options:
220
234
  message: "Invalid options passed to %{klass}.store_in: %{options}."
221
235
  summary: "The :store_in macro takes only a hash of parameters with
@@ -462,6 +476,13 @@ en:
462
476
  with the already defined method %{model_name}, or set the
463
477
  configuration option Mongoid.scope_overwrite_exception to false,
464
478
  which is its default. In this case a warning will be logged."
479
+ sessions_not_supported:
480
+ message: "Sessions are not supported by the connected server(s)."
481
+ summary: "A session was attempted to be used with a MongoDB server version
482
+ that doesn't support sessions. Sessions are supported in MongoDB
483
+ server versions 3.6 and higher."
484
+ resolution: "Verify that all servers in your deployment are at least
485
+ version 3.6 or don't attempt to use sessions with older server versions."
465
486
  taken:
466
487
  "is already taken"
467
488
  too_many_nested_attribute_records:
@@ -42,7 +42,7 @@ module Mongoid
42
42
  PLATFORM_DETAILS = "mongoid-#{VERSION}".freeze
43
43
 
44
44
  # The minimum MongoDB version supported.
45
- MONGODB_VERSION = "2.4.0"
45
+ MONGODB_VERSION = "2.6.0"
46
46
 
47
47
  # Sets the Mongoid configuration options. Best used by passing a block.
48
48
  #
@@ -8,7 +8,7 @@ module Mongoid
8
8
  module Batchable
9
9
  include Positional
10
10
 
11
- # Insert new documents as a batch push ($pushAll). This ensures that
11
+ # Insert new documents as a batch push ($push with $each). This ensures that
12
12
  # all callbacks are run at the appropriate time and only 1 request is
13
13
  # made to the database.
14
14
  #
@@ -21,7 +21,7 @@ module Mongoid
21
21
  #
22
22
  # @since 3.0.0
23
23
  def batch_insert(docs)
24
- execute_batch_insert(docs, "$pushAll")
24
+ execute_batch_push(docs)
25
25
  end
26
26
 
27
27
  # Clear all of the docs out of the relation in a single swipe.
@@ -38,7 +38,8 @@ module Mongoid
38
38
  pre_process_batch_remove(docs, :delete)
39
39
  unless docs.empty?
40
40
  collection.find(selector).update_one(
41
- positionally(selector, "$unset" => { path => true })
41
+ positionally(selector, "$unset" => { path => true }),
42
+ session: session
42
43
  )
43
44
  post_process_batch_remove(docs, :delete)
44
45
  end
@@ -58,7 +59,8 @@ module Mongoid
58
59
  removals = pre_process_batch_remove(docs, method)
59
60
  if !docs.empty?
60
61
  collection.find(selector).update_one(
61
- positionally(selector, "$pullAll" => { path => removals })
62
+ positionally(selector, "$pullAll" => { path => removals }),
63
+ session: session
62
64
  )
63
65
  post_process_batch_remove(docs, method)
64
66
  end
@@ -89,7 +91,7 @@ module Mongoid
89
91
  _base.delayed_atomic_sets.clear unless _assigning?
90
92
  docs = normalize_docs(docs).compact
91
93
  _target.clear and _unscoped.clear
92
- inserts = execute_batch_insert(docs, "$set")
94
+ inserts = execute_batch_set(docs)
93
95
  add_atomic_sets(inserts)
94
96
  end
95
97
  end
@@ -116,32 +118,56 @@ module Mongoid
116
118
  end
117
119
  end
118
120
 
119
- # Perform a batch persist of the provided documents with the supplied
120
- # operation.
121
+ # Perform a batch persist of the provided documents with a $set.
121
122
  #
122
123
  # @api private
123
124
  #
124
- # @example Perform a batch operation.
125
- # batchable.execute_batch(docs, "$set")
125
+ # @example Perform a batch $set.
126
+ # batchable.execute_batch_set(docs)
126
127
  #
127
128
  # @param [ Array<Document> ] docs The docs to persist.
128
- # @param [ String ] operation The atomic operation.
129
129
  #
130
130
  # @return [ Array<Hash> ] The inserts.
131
131
  #
132
- # @since 3.0.0
133
- def execute_batch_insert(docs, operation)
132
+ # @since 7.0.0
133
+ def execute_batch_set(docs)
134
134
  self.inserts_valid = true
135
135
  inserts = pre_process_batch_insert(docs)
136
136
  if insertable?
137
137
  collection.find(selector).update_one(
138
- positionally(selector, operation => { path => inserts })
138
+ positionally(selector, '$set' => { path => inserts }),
139
+ session: session
139
140
  )
140
141
  post_process_batch_insert(docs)
141
142
  end
142
143
  inserts
143
144
  end
144
145
 
146
+ # Perform a batch persist of the provided documents with $push and $each.
147
+ #
148
+ # @api private
149
+ #
150
+ # @example Perform a batch push.
151
+ # batchable.execute_batch_push(docs)
152
+ #
153
+ # @param [ Array<Document> ] docs The docs to persist.
154
+ #
155
+ # @return [ Array<Hash> ] The inserts.
156
+ #
157
+ # @since 7.0.0
158
+ def execute_batch_push(docs)
159
+ self.inserts_valid = true
160
+ pushes = pre_process_batch_insert(docs)
161
+ if insertable?
162
+ collection.find(selector).update_one(
163
+ positionally(selector, '$push' => { path => { '$each' => pushes } }),
164
+ session: session
165
+ )
166
+ post_process_batch_insert(docs)
167
+ end
168
+ pushes
169
+ end
170
+
145
171
  # Are we in a state to be able to batch insert?
146
172
  #
147
173
  # @api private
@@ -189,6 +189,10 @@ module Mongoid
189
189
 
190
190
  private
191
191
 
192
+ def session
193
+ _base.send(:session)
194
+ end
195
+
192
196
  # Find the first object given the supplied attributes or create/initialize it.
193
197
  #
194
198
  # @example Find or create|initialize.
@@ -240,6 +240,8 @@ module Mongoid
240
240
  def query_criteria(object, base)
241
241
  crit = klass.where(foreign_key => object)
242
242
  crit = with_polymorphic_criterion(crit, base)
243
+ crit.association = self
244
+ crit.parent_document = base
243
245
  with_ordering(crit)
244
246
  end
245
247
 
@@ -179,12 +179,14 @@ module Mongoid
179
179
  if _loaded?
180
180
  _loaded.each_pair do |id, doc|
181
181
  document = _added.delete(doc._id) || doc
182
+ set_base(document)
182
183
  yield(document)
183
184
  end
184
185
  else
185
186
  unloaded_documents.each do |doc|
186
187
  document = _added.delete(doc._id) || _loaded.delete(doc._id) || doc
187
188
  _loaded[document._id] = document
189
+ set_base(document)
188
190
  yield(document)
189
191
  end
190
192
  end
@@ -217,12 +219,22 @@ module Mongoid
217
219
  # @example Get the first document.
218
220
  # enumerable.first
219
221
  #
222
+ # @note Automatically adding a sort on _id when no other sort is
223
+ # defined on the criteria has the potential to cause bad performance issues.
224
+ # If you experience unexpected poor performance when using #first or #last,
225
+ # use the option { id_sort: :none }.
226
+ # Be aware that #first/#last won't guarantee order in this case.
227
+ #
228
+ # @param [ Hash ] opts The options for the query returning the first document.
229
+ #
230
+ # @option opts [ :none ] :id_sort Don't apply a sort on _id.
231
+ #
220
232
  # @return [ Document ] The first document found.
221
233
  #
222
234
  # @since 2.1.0
223
- def first
235
+ def first(opts = {})
224
236
  _loaded.try(:values).try(:first) ||
225
- _added[(ul = _unloaded.try(:first)).try(:id)] ||
237
+ _added[(ul = _unloaded.try(:first, opts)).try(:id)] ||
226
238
  ul ||
227
239
  _added.values.try(:first)
228
240
  end
@@ -238,7 +250,9 @@ module Mongoid
238
250
  # @param [ Criteria, Array<Document> ] target The wrapped object.
239
251
  #
240
252
  # @since 2.1.0
241
- def initialize(target)
253
+ def initialize(target, base = nil, association = nil)
254
+ @_base = base
255
+ @_association = association
242
256
  if target.is_a?(Criteria)
243
257
  @_added, @executed, @_loaded, @_unloaded = {}, false, {}, target
244
258
  else
@@ -291,8 +305,9 @@ module Mongoid
291
305
  # @since 2.1.0
292
306
  def in_memory
293
307
  docs = (_loaded.values + _added.values)
294
- docs.each { |doc| yield(doc) } if block_given?
295
- docs
308
+ docs.each do |doc|
309
+ yield(doc) if block_given?
310
+ end
296
311
  end
297
312
 
298
313
  # Get the last document in the enumerable. Will check the new
@@ -301,13 +316,23 @@ module Mongoid
301
316
  # @example Get the last document.
302
317
  # enumerable.last
303
318
  #
319
+ # @note Automatically adding a sort on _id when no other sort is
320
+ # defined on the criteria has the potential to cause bad performance issues.
321
+ # If you experience unexpected poor performance when using #first or #last,
322
+ # use the option { id_sort: :none }.
323
+ # Be aware that #first/#last won't guarantee order in this case.
324
+ #
325
+ # @param [ Hash ] opts The options for the query returning the first document.
326
+ #
327
+ # @option opts [ :none ] :id_sort Don't apply a sort on _id.
328
+ #
304
329
  # @return [ Document ] The last document found.
305
330
  #
306
331
  # @since 2.1.0
307
- def last
332
+ def last(opts = {})
308
333
  _added.values.try(:last) ||
309
334
  _loaded.try(:values).try(:last) ||
310
- _added[(ul = _unloaded.try(:last)).try(:id)] ||
335
+ _added[(ul = _unloaded.try(:last, opts)).try(:id)] ||
311
336
  ul
312
337
  end
313
338
 
@@ -464,6 +489,12 @@ module Mongoid
464
489
 
465
490
  private
466
491
 
492
+ def set_base(document)
493
+ if @_association.is_a?(Referenced::HasMany)
494
+ document.set_relation(@_association.inverse, @_base) if @_association
495
+ end
496
+ end
497
+
467
498
  def method_missing(name, *args, &block)
468
499
  entries.send(name, *args, &block)
469
500
  end
@@ -210,7 +210,8 @@ module Mongoid
210
210
  #
211
211
  # @since 2.0.0.beta.1
212
212
  def initialize(base, target, association)
213
- init(base, HasMany::Targets::Enumerable.new(target), association) do
213
+ enum = HasMany::Targets::Enumerable.new(target, base, association)
214
+ init(base, enum, association) do
214
215
  raise_mixed if klass.embedded? && !klass.cyclic?
215
216
  end
216
217
  end
@@ -455,7 +456,7 @@ module Mongoid
455
456
  # @since 3.0.0
456
457
  def persist_delayed(docs, inserts)
457
458
  unless docs.empty?
458
- collection.insert_many(inserts)
459
+ collection.insert_many(inserts, session: session)
459
460
  docs.each do |doc|
460
461
  doc.new_record = false
461
462
  doc.run_after_callbacks(:create, :save)
@@ -81,7 +81,7 @@ module Mongoid
81
81
  adds, subs = new - (old || []), (old || []) - new
82
82
 
83
83
  # If we are autosaving we don't want a duplicate to get added - the
84
- # $addToSet would run previously and then the $pushAll from the
84
+ # $addToSet would run previously and then the $push and $each from the
85
85
  # inverse on the autosave would cause this. We delete each id from
86
86
  # what's in memory in case a mix of id addition and object addition
87
87
  # had occurred.
@@ -32,7 +32,7 @@ module Mongoid
32
32
  touches = touch_atomic_updates(field)
33
33
  unless touches["$set"].blank?
34
34
  selector = atomic_selector
35
- _root.collection.find(selector).update_one(positionally(selector, touches))
35
+ _root.collection.find(selector).update_one(positionally(selector, touches), session: session)
36
36
  end
37
37
  run_callbacks(:touch)
38
38
  true
@@ -110,10 +110,10 @@ module Mongoid
110
110
  # performed in a single operation. Conflicting modifications are
111
111
  # detected by the 'haveConflictingMod' function in MongoDB.
112
112
  # Examination of the code suggests that two modifications (a $set
113
- # and a $pushAll, for example) conflict if:
113
+ # and a $push with $each, for example) conflict if:
114
114
  # (1) the key paths being modified are equal.
115
115
  # (2) one key path is a prefix of the other.
116
- # So a $set of 'addresses.0.street' will conflict with a $pushAll
116
+ # So a $set of 'addresses.0.street' will conflict with a $push and $each
117
117
  # to 'addresses', and we will need to split our update into two
118
118
  # pieces. We do not, however, attempt to match MongoDB's logic
119
119
  # exactly. Instead, we assume that two updates conflict if the
@@ -218,7 +218,7 @@ module Mongoid
218
218
  # @example Get the pushes.
219
219
  # person.atomic_pushes
220
220
  #
221
- # @return [ Hash ] The $pushAll operations.
221
+ # @return [ Hash ] The $push and $each operations.
222
222
  #
223
223
  # @since 2.1.0
224
224
  def atomic_pushes
@@ -68,7 +68,7 @@ module Mongoid
68
68
  modifications.each_pair do |field, value|
69
69
  push_fields[field] = field
70
70
  mods = push_conflict?(field) ? conflicting_pushes : pushes
71
- add_operation(mods, field, Array.wrap(value))
71
+ add_operation(mods, field, { '$each' => Array.wrap(value) })
72
72
  end
73
73
  end
74
74
 
@@ -118,8 +118,12 @@ module Mongoid
118
118
  # @since 2.2.0
119
119
  def add_operation(mods, field, value)
120
120
  if mods.has_key?(field)
121
- value.each do |val|
122
- mods[field].push(val)
121
+ if mods[field].is_a?(Array)
122
+ value.each do |val|
123
+ mods[field].push(val)
124
+ end
125
+ elsif mods[field]['$each']
126
+ mods[field]['$each'].concat(value['$each'])
123
127
  end
124
128
  else
125
129
  mods[field] = value
@@ -190,7 +194,7 @@ module Mongoid
190
194
  #
191
195
  # @since 2.2.0
192
196
  def conflicting_pushes
193
- conflicts["$pushAll"] ||= {}
197
+ conflicts["$push"] ||= {}
194
198
  end
195
199
 
196
200
  # Get the conflicting set modifications.
@@ -277,16 +281,16 @@ module Mongoid
277
281
  self["$pull"] ||= {}
278
282
  end
279
283
 
280
- # Get the $pushAll operations or intialize a new one.
284
+ # Get the $push/$each operations or initialize a new one.
281
285
  #
282
- # @example Get the $pushAll operations.
286
+ # @example Get the $push/$each operations.
283
287
  # modifiers.pushes
284
288
  #
285
- # @return [ Hash ] The $pushAll operations.
289
+ # @return [ Hash ] The $push/$each operations.
286
290
  #
287
291
  # @since 2.1.0
288
292
  def pushes
289
- self["$pushAll"] ||= {}
293
+ self["$push"] ||= {}
290
294
  end
291
295
 
292
296
  # Get the $set operations or intialize a new one.
@@ -29,7 +29,7 @@ module Mongoid
29
29
  #
30
30
  # @since 1.0.0
31
31
  def attribute_present?(name)
32
- attribute = read_attribute(name)
32
+ attribute = read_raw_attribute(name)
33
33
  !attribute.blank? || attribute == false
34
34
  rescue ActiveModel::MissingAttributeError
35
35
  false
@@ -92,21 +92,15 @@ module Mongoid
92
92
  #
93
93
  # @since 1.0.0
94
94
  def read_attribute(name)
95
- normalized = database_field_name(name.to_s)
96
- if attribute_missing?(normalized)
97
- raise ActiveModel::MissingAttributeError, "Missing attribute: '#{name}'."
98
- end
99
- if hash_dot_syntax?(normalized)
100
- attributes.__nested__(normalized)
101
- else
102
- attributes[normalized]
103
- end
95
+ field = fields[name.to_s]
96
+ raw = read_raw_attribute(name)
97
+ field ? field.demongoize(raw) : raw
104
98
  end
105
99
  alias :[] :read_attribute
106
100
 
107
101
  # Read a value from the attributes before type cast. If the value has not
108
102
  # yet been assigned then this will return the attribute's existing value
109
- # using read_attribute.
103
+ # using read_raw_attribute.
110
104
  #
111
105
  # @example Read an attribute before type cast.
112
106
  # person.read_attribute_before_type_cast(:price)
@@ -122,7 +116,7 @@ module Mongoid
122
116
  if attributes_before_type_cast.key?(attr)
123
117
  attributes_before_type_cast[attr]
124
118
  else
125
- read_attribute(attr)
119
+ read_raw_attribute(attr)
126
120
  end
127
121
  end
128
122
 
@@ -293,6 +287,20 @@ module Mongoid
293
287
  fields.key?(key) ? fields[key].mongoize(value) : value.mongoize
294
288
  end
295
289
 
290
+ private
291
+
292
+ def read_raw_attribute(name)
293
+ normalized = database_field_name(name.to_s)
294
+ if attribute_missing?(normalized)
295
+ raise ActiveModel::MissingAttributeError, "Missing attribute: '#{name}'."
296
+ end
297
+ if hash_dot_syntax?(normalized)
298
+ attributes.__nested__(normalized)
299
+ else
300
+ attributes[normalized]
301
+ end
302
+ end
303
+
296
304
  module ClassMethods
297
305
 
298
306
  # Alias the provided name to the original field. This will provide an