diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 13c8bc3676..33c20bb5cc 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -1,24 +1,29 @@ module ActiveRecord class Attribute # :nodoc: class << self - def from_database(value, type) - FromDatabase.new(value, type) + def from_database(name, value, type) + FromDatabase.new(name, value, type) end - def from_user(value, type) - FromUser.new(value, type) + def from_user(name, value, type) + FromUser.new(name, value, type) end - def uninitialized(type) - Uninitialized.new(type) + def null(name) + Null.new(name) + end + + def uninitialized(name, type) + Uninitialized.new(name, type) end end - attr_reader :value_before_type_cast, :type + attr_reader :name, :value_before_type_cast, :type # This method should not be called directly. # Use #from_database or #from_user - def initialize(value_before_type_cast, type) + def initialize(name, value_before_type_cast, type) + @name = name @value_before_type_cast = value_before_type_cast @type = type end @@ -42,11 +47,11 @@ module ActiveRecord end def with_value_from_user(value) - self.class.from_user(value, type) + self.class.from_user(name, value, type) end def with_value_from_database(value) - self.class.from_database(value, type) + self.class.from_database(name, value, type) end def type_cast @@ -77,9 +82,9 @@ module ActiveRecord end end - class NullAttribute < Attribute # :nodoc: - def initialize - super(nil, Type::Value.new) + class Null < Attribute # :nodoc: + def initialize(name) + super(name, nil, Type::Value.new) end def value @@ -88,21 +93,23 @@ module ActiveRecord end class Uninitialized < Attribute # :nodoc: - def initialize(type) - super(nil, type) + def initialize(name, type) + super(name, nil, type) end def value - nil + if block_given? + yield name + end + end + + def value_for_database end - alias value_for_database value def initialized? false end end - private_constant :FromDatabase, :FromUser, :NullAttribute, :Uninitialized - - Null = NullAttribute.new # :nodoc: + private_constant :FromDatabase, :FromUser, :Null, :Uninitialized end end diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 65e15b16dd..5be11e6ab9 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -2,13 +2,16 @@ require 'active_record/attribute_set/builder' module ActiveRecord class AttributeSet # :nodoc: - delegate :[], to: :attributes delegate :keys, to: :initialized_attributes def initialize(attributes) @attributes = attributes end + def [](name) + attributes[name] || Attribute.null(name) + end + def values_before_type_cast attributes.each_with_object({}) { |(k, v), h| h[k] = v.value_before_type_cast } end @@ -22,13 +25,8 @@ module ActiveRecord attributes.include?(name) && self[name].initialized? end - def fetch_value(name) - attribute = self[name] - if attribute.initialized? || !block_given? - attribute.value - else - yield name - end + def fetch_value(name, &block) + self[name].value(&block) end def write_from_database(name, value) diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index f9cf9c1809..1e146a07da 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -16,16 +16,15 @@ module ActiveRecord private def build_attributes_from_values(values, additional_types) - attributes = Hash.new(Attribute::Null) - values.each_with_object(attributes) do |(name, value), hash| + values.each_with_object({}) do |(name, value), hash| type = additional_types.fetch(name, types[name]) - hash[name] = Attribute.from_database(value, type) + hash[name] = Attribute.from_database(name, value, type) end end def add_uninitialized_attributes(attributes) types.except(*attributes.keys).each do |name, type| - attributes[name] = Attribute.uninitialized(type) + attributes[name] = Attribute.uninitialized(name, type) end end end diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb index 35caa0ddab..cdbb11fa32 100644 --- a/activerecord/test/cases/attribute_set_test.rb +++ b/activerecord/test/cases/attribute_set_test.rb @@ -8,6 +8,8 @@ module ActiveRecord assert_equal 1, attributes[:foo].value assert_equal 2.2, attributes[:bar].value + assert_equal :foo, attributes[:foo].name + assert_equal :bar, attributes[:bar].name end test "building with custom types" do @@ -24,6 +26,7 @@ module ActiveRecord assert_equal '3.3', attributes[:foo].value_before_type_cast assert_equal nil, attributes[:bar].value_before_type_cast + assert_equal :bar, attributes[:bar].name end test "duping creates a new hash and dups each attribute" do diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb index 0adf9545b8..24452fdec2 100644 --- a/activerecord/test/cases/attribute_test.rb +++ b/activerecord/test/cases/attribute_test.rb @@ -13,7 +13,7 @@ module ActiveRecord test "from_database + read type casts from database" do @type.expect(:type_cast_from_database, 'type cast from database', ['a value']) - attribute = Attribute.from_database('a value', @type) + attribute = Attribute.from_database(nil, 'a value', @type) type_cast_value = attribute.value @@ -22,7 +22,7 @@ module ActiveRecord test "from_user + read type casts from user" do @type.expect(:type_cast_from_user, 'type cast from user', ['a value']) - attribute = Attribute.from_user('a value', @type) + attribute = Attribute.from_user(nil, 'a value', @type) type_cast_value = attribute.value @@ -31,7 +31,7 @@ module ActiveRecord test "reading memoizes the value" do @type.expect(:type_cast_from_database, 'from the database', ['whatever']) - attribute = Attribute.from_database('whatever', @type) + attribute = Attribute.from_database(nil, 'whatever', @type) type_cast_value = attribute.value second_read = attribute.value @@ -42,14 +42,14 @@ module ActiveRecord test "reading memoizes falsy values" do @type.expect(:type_cast_from_database, false, ['whatever']) - attribute = Attribute.from_database('whatever', @type) + attribute = Attribute.from_database(nil, 'whatever', @type) attribute.value attribute.value end test "read_before_typecast returns the given value" do - attribute = Attribute.from_database('raw value', @type) + attribute = Attribute.from_database(nil, 'raw value', @type) raw_value = attribute.value_before_type_cast @@ -59,7 +59,7 @@ module ActiveRecord test "from_database + read_for_database type casts to and from database" do @type.expect(:type_cast_from_database, 'read from database', ['whatever']) @type.expect(:type_cast_for_database, 'ready for database', ['read from database']) - attribute = Attribute.from_database('whatever', @type) + attribute = Attribute.from_database(nil, 'whatever', @type) type_cast_for_database = attribute.value_for_database @@ -69,7 +69,7 @@ module ActiveRecord test "from_user + read_for_database type casts from the user to the database" do @type.expect(:type_cast_from_user, 'read from user', ['whatever']) @type.expect(:type_cast_for_database, 'ready for database', ['read from user']) - attribute = Attribute.from_user('whatever', @type) + attribute = Attribute.from_user(nil, 'whatever', @type) type_cast_for_database = attribute.value_for_database @@ -78,7 +78,7 @@ module ActiveRecord test "duping dups the value" do @type.expect(:type_cast_from_database, 'type cast', ['a value']) - attribute = Attribute.from_database('a value', @type) + attribute = Attribute.from_database(nil, 'a value', @type) value_from_orig = attribute.value value_from_clone = attribute.dup.value @@ -90,13 +90,13 @@ module ActiveRecord test "duping does not dup the value if it is not dupable" do @type.expect(:type_cast_from_database, false, ['a value']) - attribute = Attribute.from_database('a value', @type) + attribute = Attribute.from_database(nil, 'a value', @type) assert_same attribute.value, attribute.dup.value end test "duping does not eagerly type cast if we have not yet type cast" do - attribute = Attribute.from_database('a value', @type) + attribute = Attribute.from_database(nil, 'a value', @type) attribute.dup end @@ -111,7 +111,7 @@ module ActiveRecord end test "with_value_from_user returns a new attribute with the value from the user" do - old = Attribute.from_database("old", MyType.new) + old = Attribute.from_database(nil, "old", MyType.new) new = old.with_value_from_user("new") assert_equal "old from database", old.value @@ -119,11 +119,24 @@ module ActiveRecord end test "with_value_from_database returns a new attribute with the value from the database" do - old = Attribute.from_user("old", MyType.new) + old = Attribute.from_user(nil, "old", MyType.new) new = old.with_value_from_database("new") assert_equal "old from user", old.value assert_equal "new from database", new.value end + + test "uninitialized attributes yield their name if a block is given to value" do + block = proc { |name| name.to_s + "!" } + foo = Attribute.uninitialized(:foo, nil) + bar = Attribute.uninitialized(:bar, nil) + + assert_equal "foo!", foo.value(&block) + assert_equal "bar!", bar.value(&block) + end + + test "uninitialized attributes have no value" do + assert_nil Attribute.uninitialized(:foo, nil).value + end end end