Skip to content

Commit

Permalink
Merge pull request #47569 from p8/activemodel/add-model-name-to-missi…
Browse files Browse the repository at this point in the history
…ng-attribute-error

Add class name to ActiveModel::MissingAttributeError error message
  • Loading branch information
rafaelfranca committed Mar 3, 2023
2 parents 37bd59a + 661c995 commit 35d574d
Show file tree
Hide file tree
Showing 10 changed files with 25 additions and 11 deletions.
12 changes: 12 additions & 0 deletions activemodel/CHANGELOG.md
@@ -1,3 +1,15 @@
* Add class to ActiveModel::MissingAttributeError error message.

Show which class is missing the attribute in the error message:

```ruby
user = User.first
user.pets.select(:id).first.user_id
# => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
```

*Petrik de Heus*

* Raise `NoMethodError` in `ActiveModel::Type::Value#as_json` to avoid unpredictable
results.

Expand Down
4 changes: 2 additions & 2 deletions activemodel/lib/active_model/attribute_methods.rb
Expand Up @@ -11,7 +11,7 @@ module ActiveModel
#
# user = User.first
# user.pets.select(:id).first.user_id
# # => ActiveModel::MissingAttributeError: missing attribute: user_id
# # => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
class MissingAttributeError < NoMethodError
end

Expand Down Expand Up @@ -501,7 +501,7 @@ def matched_attribute_method(method_name)
end

def missing_attribute(attr_name, stack)
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
raise ActiveModel::MissingAttributeError, "missing attribute '#{attr_name}' for #{self.class}", stack
end

def _read_attribute(attr)
Expand Down
Expand Up @@ -94,7 +94,7 @@ def loaded?
# receive:
#
# person.pets.select(:name).first.person_id
# # => ActiveModel::MissingAttributeError: missing attribute: person_id
# # => ActiveModel::MissingAttributeError: missing attribute 'person_id' for Pet
#
# *Second:* You can pass a block so it can be used just like Array#select.
# This builds an array of objects from the database for the scope,
Expand Down
4 changes: 2 additions & 2 deletions activerecord/lib/active_record/attribute_methods.rb
Expand Up @@ -332,8 +332,8 @@ def attribute_present?(attr_name)
#
# person = Person.select(:name).first
# person[:name] # => "Francesco"
# person[:date_of_birth] # => ActiveModel::MissingAttributeError: missing attribute: date_of_birth
# person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
# person[:date_of_birth] # => ActiveModel::MissingAttributeError: missing attribute 'date_of_birth' for Person
# person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute 'organization_id' for Person
# person[:id] # => nil
def [](attr_name)
read_attribute(attr_name) { |n| missing_attribute(n, caller) }
Expand Down
2 changes: 1 addition & 1 deletion activerecord/lib/active_record/integration.rb
Expand Up @@ -106,7 +106,7 @@ def cache_version
timestamp.utc.to_fs(cache_timestamp_format)
end
elsif self.class.has_attribute?("updated_at")
raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
raise ActiveModel::MissingAttributeError, "missing attribute 'updated_at' for #{self.class}"
end
end

Expand Down
2 changes: 1 addition & 1 deletion activerecord/lib/active_record/relation/query_methods.rb
Expand Up @@ -370,7 +370,7 @@ def references!(*table_names) # :nodoc:
# except +id+ will throw ActiveModel::MissingAttributeError:
#
# Model.select(:field).first.other_field
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
# # => ActiveModel::MissingAttributeError: missing attribute 'other_field' for Model
def select(*fields)
if block_given?
if fields.any?
Expand Down
4 changes: 3 additions & 1 deletion activerecord/test/cases/attribute_methods_test.rb
Expand Up @@ -377,7 +377,9 @@ def setup

test "read_attribute raises ActiveModel::MissingAttributeError when the attribute isn't selected" do
computer = Computer.select(:id, :extendedWarranty).first
assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] }
assert_raises(ActiveModel::MissingAttributeError, match: /attribute 'developer' for Computer/) do
computer[:developer]
end
assert_nothing_raised { computer[:extendedWarranty] }
assert_nothing_raised { computer[:no_column_exists] }
end
Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/cases/cache_key_test.rb
Expand Up @@ -123,7 +123,7 @@ class CacheMeWithVersion < ActiveRecord::Base
test "updated_at on class but not on instance raises an error" do
record = CacheMeWithVersion.create
record_from_db = CacheMeWithVersion.where(id: record.id).select(:id).first
assert_raises(ActiveModel::MissingAttributeError) do
assert_raises(ActiveModel::MissingAttributeError, match: /'updated_at' for .*CacheMeWithVersion/) do
record_from_db.cache_version
end
end
Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/cases/relation/select_test.rb
Expand Up @@ -102,7 +102,7 @@ def test_non_select_columns_wont_be_loaded

def assert_non_select_columns_wont_be_loaded(post)
assert_equal "WELCOME TO THE WEBLOG", post.title
assert_raise(ActiveModel::MissingAttributeError) do
assert_raise(ActiveModel::MissingAttributeError, match: /attribute 'body' for Post/) do
post.body
end
end
Expand Down
2 changes: 1 addition & 1 deletion guides/source/active_record_querying.md
Expand Up @@ -824,7 +824,7 @@ SELECT isbn, out_of_print FROM books
Be careful because this also means you're initializing a model object with only the fields that you've selected. If you attempt to access a field that is not in the initialized record you'll receive:

```
ActiveModel::MissingAttributeError: missing attribute: <attribute>
ActiveModel::MissingAttributeError: missing attribute '<attribute>' for Book
```

Where `<attribute>` is the attribute you asked for. The `id` method will not raise the `ActiveRecord::MissingAttributeError`, so just be careful when working with associations because they need the `id` method to function properly.
Expand Down

0 comments on commit 35d574d

Please sign in to comment.