mongoid 7.0.0.beta → 7.0.0

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