Merge pull request #15438 from sgrif/sg-null-column

Return a null column when no column exists for an attribute

Conflicts:
	activerecord/CHANGELOG.md
This commit is contained in:
Rafael Mendonça França 2014-06-03 19:30:34 -03:00
commit 1c6bb0efe0
5 changed files with 40 additions and 14 deletions

View File

@ -1,3 +1,7 @@
* Return a null column from `column_for_attribute` when no column exists.
*Sean Griffin*
* Implemented ActiveRecord::Base#pretty_print to work with PP.
*Ethan*

View File

@ -350,10 +350,12 @@ module ActiveRecord
# # => #<ActiveRecord::ConnectionAdapters::SQLite3Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
#
# person.column_for_attribute(:nothing)
# # => nil
# # => #<ActiveRecord::ConnectionAdapters::Column:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
def column_for_attribute(name)
# FIXME: should this return a null object for columns that don't exist?
self.class.columns_hash[name.to_s]
name = name.to_s
self.class.columns_hash.fetch(name) do
ConnectionAdapters::Column.new(name, nil, Type::Value.new)
end
end
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
@ -438,16 +440,16 @@ module ActiveRecord
# Filters the primary keys and readonly attributes from the attribute names.
def attributes_for_update(attribute_names)
attribute_names.select do |name|
column_for_attribute(name) && !readonly_attribute?(name)
attribute_names.reject do |name|
readonly_attribute?(name)
end
end
# Filters out the primary keys, from the attribute names, when the primary
# key is to be generated (e.g. the id attribute has no value).
def attributes_for_create(attribute_names)
attribute_names.select do |name|
column_for_attribute(name) && !(pk_attribute?(name) && id.nil?)
attribute_names.reject do |name|
pk_attribute?(name) && id.nil?
end
end

View File

@ -94,8 +94,7 @@ module ActiveRecord
end
def _field_changed?(attr, old, value)
column = column_for_attribute(attr) || Type::Value.new
column.changed?(old, value)
column_for_attribute(attr).changed?(old, value)
end
end
end

View File

@ -72,18 +72,20 @@ module ActiveRecord
@attributes.delete(attr_name)
column = column_for_attribute(attr_name)
unless has_attribute?(attr_name) || self.class.columns_hash.key?(attr_name)
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
end
# If we're dealing with a binary column, write the data to the cache
# so we don't attempt to typecast multiple times.
if column && column.binary?
if column.binary?
@attributes[attr_name] = value
end
if column && should_type_cast
if should_type_cast
@raw_attributes[attr_name] = column.type_cast_for_write(value)
elsif !should_type_cast || @raw_attributes.has_key?(attr_name)
@raw_attributes[attr_name] = value
else
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
@raw_attributes[attr_name] = value
end
end
end

View File

@ -80,6 +80,25 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal :integer, @first.column_for_attribute("id").type
end
def test_non_existent_columns_return_null_object
column = @first.column_for_attribute("attribute_that_doesnt_exist")
assert_equal "attribute_that_doesnt_exist", column.name
assert_equal nil, column.sql_type
assert_equal nil, column.type
assert_not column.number?
assert_not column.text?
assert_not column.binary?
end
def test_non_existent_columns_are_identity_types
column = @first.column_for_attribute("attribute_that_doesnt_exist")
object = Object.new
assert_equal object, column.type_cast(object)
assert_equal object, column.type_cast_for_write(object)
assert_equal object, column.type_cast_for_database(object)
end
def test_reflection_klass_for_nested_class_name
reflection = MacroReflection.new(:company, nil, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base)
assert_nothing_raised do