diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 815902a129d7d7cac43ee6be836a07ec3bba8e55..f630be6f70b06ee5108efe43a980e22391e94980 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,28 @@
## Rails 4.0.0 (unreleased) ##
+* `ActiveRecord::AttributeMethods#[]` raises `ActiveModel::MissingAttributeError`
+ error if the given attribute is missing. Fixes #5433.
+
+ class Person < ActiveRecord::Base
+ belongs_to :company
+ end
+
+ # Before:
+ person = Person.select('id').first
+ person[:name] # => nil
+ person.name # => ActiveModel::MissingAttributeError: missing_attribute: name
+ person[:company_id] # => nil
+ person.company # => nil
+
+ # After:
+ person = Person.select('id').first
+ person[:name] # => ActiveModel::MissingAttributeError: missing_attribute: name
+ person.name # => ActiveModel::MissingAttributeError: missing_attribute: name
+ person[:company_id] # => ActiveModel::MissingAttributeError: missing_attribute: company_id
+ person.company # => ActiveModel::MissingAttributeError: missing_attribute: company_id
+
+ *Francesco Rodriguez*
+
* Small binary fields use the `VARBINARY` MySQL type, instead of `TINYBLOB`.
*Victor Costan*
@@ -51,7 +74,7 @@
*Scott Willson*
-* Fix bug where sum(expression) returns string '0' for no matching records
+* Fix bug where sum(expression) returns string '0' for no matching records.
Fixes #7439
*Tim Macfarlane*
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 9a4fcdfda3eb387dc38cdbb4e7e7a1f7e70dec71..101c641877895346646273b20f67d379b3a96d30 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -266,16 +266,22 @@ def column_for_attribute(name)
# Returns the value of the attribute identified by attr_name after it has been typecast (for example,
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
- # (Alias for the protected read_attribute method).
+ # (Alias for the protected read_attribute method). It raises an ActiveModel::MissingAttributeError
+ # error if the identified attribute is missing.
#
# class Person < ActiveRecord::Base
+ # belongs_to :organization
# end
#
# person = Person.new(name: 'Francesco', age: '22'
# person[:name] # => "Francesco"
# person[:age] # => 22
+ #
+ # person = Person.select('id').first
+ # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name
+ # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id
def [](attr_name)
- read_attribute(attr_name)
+ read_attribute(attr_name) { |n| missing_attribute(n, caller) }
end
# Updates the attribute identified by attr_name with the specified +value+.
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index c2b58fd7d11239f86b074168efa5e58e301d60e7..8b82b7921918eaf0422fe1b0583ed3acdc5be24f 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -287,6 +287,12 @@ def test_read_attribute
assert_equal "Don't change the topic", topic[:title]
end
+ def test_read_attribute_raises_missing_attribute_error_when_not_exists
+ computer = Computer.select('id').first
+ assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] }
+ assert_raises(ActiveModel::MissingAttributeError) { computer[:extendedWarranty] }
+ end
+
def test_read_attribute_when_false
topic = topics(:first)
topic.approved = false