Skip to content

Commit bf0c010

Browse files
MONGOID-5467 MONGOID-5320 Change usage of ActiveModel::MissingAttributeError to Mongoid::Errors:AttributeNotLoaded (#5437)
* Change usage of ActiveModel::MissingAttributeError to Mongoid::Errors::AttributeNotLoaded. * Fix specs * Improve release notes Co-authored-by: shields <[email protected]>
1 parent 210a79e commit bf0c010

23 files changed

+170
-52
lines changed

docs/reference/crud.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ Mongoid provides several ways of accessing field values.
657657
.. note::
658658

659659
All of the access methods described below raise
660-
``ActiveModel::MissingAttributeError`` when the field being accessed is
660+
``Mongoid::Errors::AttributeNotLoaded`` when the field being accessed is
661661
:ref:`projected out <projection>`, either by virtue of not being included in
662662
:ref:`only <only>` or by virtue of being included in
663663
:ref:`without <without>`. This applies to both reads and writes.

docs/reference/queries.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -775,12 +775,12 @@ operation is sometimes called "projection".
775775
band = Band.only(:name).first
776776

777777
Attempting to reference attributes which have not been loaded results in
778-
``ActiveModel::MissingAttributeError``.
778+
``Mongoid::Errors::AttributeNotLoaded``.
779779

780780
.. code-block:: ruby
781781

782782
band.label
783-
# ActiveModel::MissingAttributeError (Missing attribute: 'label'.)
783+
#=> raises Mongoid::Errors::AttributeNotLoaded
784784

785785
Even though Mongoid currently allows writing to attributes that have not
786786
been loaded, such writes will not be persisted

docs/release-notes/mongoid-9.0.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
***********
2+
Mongoid 9.0
3+
***********
4+
5+
.. default-domain:: mongodb
6+
7+
.. contents:: On this page
8+
:local:
9+
:backlinks: none
10+
:depth: 2
11+
:class: singlecol
12+
13+
This page describes significant changes and improvements in Mongoid 9.0.
14+
The complete list of releases is available `on GitHub
15+
<https://github.com/mongodb/mongoid/releases>`_ and `in JIRA
16+
<https://jira.mongodb.org/projects/MONGOID?selectedItem=com.atlassian.jira.jira-projects-plugin:release-page>`_;
17+
please consult GitHub releases for detailed release notes and JIRA for
18+
the complete list of issues fixed in each release, including bug fixes.
19+
20+
21+
Raise AttributeNotLoaded error when accessing fields omitted from query projection
22+
----------------------------------------------------------------------------------
23+
24+
When attempting to access a field on a model instance which was
25+
excluded with the ``.only`` or ``.without`` query projections methods
26+
when the instance was loaded, Mongoid will now raise a
27+
``Mongoid::Errors::AttributeNotLoaded`` error.
28+
29+
.. code-block:: ruby
30+
31+
Band.only(:name).first.label
32+
#=> raises Mongoid::Errors::AttributeNotLoaded
33+
34+
Band.without(:label).first.label = 'Sub Pop Records'
35+
#=> raises Mongoid::Errors::AttributeNotLoaded
36+
37+
In earlier Mongoid versions, the same conditions would raise an
38+
``ActiveModel::MissingAttributeError``. Please check your code for
39+
any Mongoid-specific usages of this class, and change them to
40+
``Mongoid::Errors::AttributeNotLoaded``. Note additionally that
41+
``AttributeNotLoaded`` inherits from ``Mongoid::Errors::MongoidError``,
42+
while ``ActiveModel::MissingAttributeError`` does not.

lib/config/locales/en.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ en:
2323
resolution: "On the %{name} association on %{inverse} you must add an
2424
:inverse_of option to specify the exact association on %{klass}
2525
that is the opposite of %{name}."
26+
attribute_not_loaded:
27+
message: "Attempted to access attribute '%{name}' on %{klass} which
28+
was not loaded."
29+
summary:
30+
"You loaded an instance of %{klass} using a query projection method
31+
such as `.only` or `.without`, then attempted to read or write an
32+
attribute '%{name}' which was not included in the projection.
33+
Mongoid intentionally prevents this to avoid overwriting data."
34+
resolution: "Ensure your query projection methods such as `.only` and
35+
`.without` include field '%{name}' when loading instances of %{klass},
36+
or remove such projections methods entirely."
2637
callbacks:
2738
message: "Calling %{method} on %{klass} resulted in a false return
2839
from a callback."

lib/mongoid/association/accessors.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def get_relation(name, association, object, reload = false)
115115
# during binding or when cascading callbacks. Whenever we retrieve
116116
# associations within the codebase, we use without_autobuild.
117117
if !without_autobuild? && association.embedded? && attribute_missing?(field_name)
118-
raise ActiveModel::MissingAttributeError, "Missing attribute: '#{field_name}'"
118+
raise Mongoid::Errors::AttributeNotLoaded.new(self.class, field_name)
119119
end
120120

121121
if !reload && (value = ivar(name)) != false

lib/mongoid/attributes.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ module Attributes
3131
def attribute_present?(name)
3232
attribute = read_raw_attribute(name)
3333
!attribute.blank? || attribute == false
34-
rescue ActiveModel::MissingAttributeError
34+
rescue Mongoid::Errors::AttributeNotLoaded
3535
false
3636
end
3737

@@ -165,7 +165,7 @@ def write_attribute(name, value)
165165
field_name = database_field_name(name)
166166

167167
if attribute_missing?(field_name)
168-
raise ActiveModel::MissingAttributeError, "Missing attribute: '#{name}'"
168+
raise Mongoid::Errors::AttributeNotLoaded.new(self.class, field_name)
169169
end
170170

171171
if attribute_writable?(field_name)
@@ -291,7 +291,7 @@ def read_raw_attribute(name)
291291
normalized = database_field_name(name.to_s)
292292

293293
if attribute_missing?(normalized)
294-
raise ActiveModel::MissingAttributeError, "Missing attribute: '#{name}'"
294+
raise Mongoid::Errors::AttributeNotLoaded.new(self.class, name)
295295
end
296296

297297
if hash_dot_syntax?(normalized)

lib/mongoid/errors.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "mongoid/errors/mongoid_error"
44
require "mongoid/errors/ambiguous_relationship"
5+
require "mongoid/errors/attribute_not_loaded"
56
require "mongoid/errors/callback"
67
require "mongoid/errors/criteria_argument_required"
78
require "mongoid/errors/document_not_destroyed"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
module Mongoid
4+
module Errors
5+
6+
# Raised when attempting to read or write an attribute which has
7+
# not been loaded. This can occur when using `.only` or `.without`
8+
# query projection methods.
9+
#
10+
# @example Getting a field which has not been loaded.
11+
# Band.only(:name).first.label
12+
# #=> raises Mongoid::Errors::AttributeNotLoaded
13+
#
14+
# @example Setting a field which has not been loaded.
15+
# Band.without(:label).first.label = 'Sub Pop Records'
16+
# #=> raises Mongoid::Errors::AttributeNotLoaded
17+
class AttributeNotLoaded < MongoidError
18+
19+
# Create the new error.
20+
#
21+
# @example Instantiate the error.
22+
# AttributeNotLoaded.new(Person, "title")
23+
#
24+
# @param [ Class ] klass The model class.
25+
# @param [ String | Symbol ] name The name of the attribute.
26+
def initialize(klass, name)
27+
super(
28+
compose_message("attribute_not_loaded", { klass: klass.name, name: name })
29+
)
30+
end
31+
end
32+
end
33+
end

spec/mongoid/association/accessors_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@
625625
it 'does not create an accessor for another field on the embedded document' do
626626
expect do
627627
persisted_person.passport.country
628-
end.to raise_error(ActiveModel::MissingAttributeError)
628+
end.to raise_error(Mongoid::Errors::AttributeNotLoaded, /Attempted to access attribute 'country' on Passport which was not loaded/)
629629
end
630630
end
631631

@@ -655,7 +655,7 @@
655655
it 'does not create an accessor for another field on the embedded document' do
656656
expect do
657657
persisted_person.passport.country
658-
end.to raise_error(ActiveModel::MissingAttributeError)
658+
end.to raise_error(Mongoid::Errors::AttributeNotLoaded, /Attempted to access attribute 'country' on Passport which was not loaded/)
659659
end
660660
end
661661

@@ -717,7 +717,7 @@
717717
it 'does not create an accessor for another field on the embedded document' do
718718
expect do
719719
persisted_person.phone_numbers.first.landline
720-
end.to raise_error(ActiveModel::MissingAttributeError)
720+
end.to raise_error(Mongoid::Errors::AttributeNotLoaded, /Attempted to access attribute 'landline' on Phone which was not loaded/)
721721
end
722722
end
723723

@@ -746,7 +746,7 @@
746746
it 'does not create an accessor for another field on the embedded document' do
747747
expect do
748748
persisted_person.phone_numbers.first.landline
749-
end.to raise_error(ActiveModel::MissingAttributeError)
749+
end.to raise_error(Mongoid::Errors::AttributeNotLoaded, /Attempted to access attribute 'landline' on Phone which was not loaded/)
750750
end
751751

752752
end

spec/mongoid/association/embedded/embeds_many_query_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
# has a default value specified in the model
2424
expect do
2525
legislator.b
26-
end.to raise_error(ActiveModel::MissingAttributeError)
26+
end.to raise_error(Mongoid::Errors::AttributeNotLoaded, /Attempted to access attribute 'b' on EmmLegislator which was not loaded/)
2727
expect(legislator.attributes.keys).to eq(['_id', 'a'])
2828
end
2929

@@ -53,10 +53,10 @@
5353
EmmCongress.where(name: 'foo').only(:_id).first
5454
end
5555

56-
it 'raises a MissingAttributeError' do
56+
it 'raises AttributeNotLoaded' do
5757
expect do
5858
congress.legislators
59-
end.to raise_error(ActiveModel::MissingAttributeError)
59+
end.to raise_error(Mongoid::Errors::AttributeNotLoaded, /Attempted to access attribute 'legislators' on EmmCongress which was not loaded/)
6060
end
6161
end
6262
end

0 commit comments

Comments
 (0)