diff options
Diffstat (limited to 'activerecord/lib/active_record/attribute.rb')
-rw-r--r-- | activerecord/lib/active_record/attribute.rb | 182 |
1 files changed, 124 insertions, 58 deletions
diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 73dd3fa041..38281158d8 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -5,8 +5,8 @@ module ActiveRecord FromDatabase.new(name, value, type) end - def from_user(name, value, type) - FromUser.new(name, value, type) + def from_user(name, value, type, original_attribute = nil) + FromUser.new(name, value, type, original_attribute) end def with_cast_value(name, value, type) @@ -26,36 +26,46 @@ module ActiveRecord # This method should not be called directly. # Use #from_database or #from_user - def initialize(name, value_before_type_cast, type) + def initialize(name, value_before_type_cast, type, original_attribute = nil) @name = name @value_before_type_cast = value_before_type_cast @type = type + @original_attribute = original_attribute end def value # `defined?` is cheaper than `||=` when we get back falsy values - @value = original_value unless defined?(@value) + @value = type_cast(value_before_type_cast) unless defined?(@value) @value end def original_value - type_cast(value_before_type_cast) + if assigned? + original_attribute.original_value + else + type_cast(value_before_type_cast) + end end def value_for_database type.serialize(value) end - def changed_from?(old_value) - type.changed?(old_value, value, value_before_type_cast) + def changed? + changed_from_assignment? || changed_in_place? + end + + def changed_in_place? + has_been_read? && type.changed_in_place?(original_value_for_database, value) end - def changed_in_place_from?(old_value) - has_been_read? && type.changed_in_place?(old_value, value) + def forgetting_assignment + with_value_from_database(value_for_database) end def with_value_from_user(value) - self.class.from_user(name, value, type) + type.assert_valid_value(value) + self.class.from_user(name, value, type, original_attribute || self) end def with_value_from_database(value) @@ -67,7 +77,11 @@ module ActiveRecord end def with_type(type) - self.class.new(name, value_before_type_cast, type) + if changed_in_place? + with_value_from_user(value).with_type(type) + else + self.class.new(name, value_before_type_cast, type, original_attribute) + end end def type_cast(*) @@ -98,77 +112,129 @@ module ActiveRecord [self.class, name, value_before_type_cast, type].hash end - protected - - def initialize_dup(other) - if defined?(@value) && @value.duplicable? - @value = @value.dup - end + def init_with(coder) + @name = coder["name"] + @value_before_type_cast = coder["value_before_type_cast"] + @type = coder["type"] + @original_attribute = coder["original_attribute"] + @value = coder["value"] if coder.map.key?("value") end - class FromDatabase < Attribute # :nodoc: - def type_cast(value) - type.deserialize(value) - end + def encode_with(coder) + coder["name"] = name + coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast + coder["type"] = type if type + coder["original_attribute"] = original_attribute if original_attribute + coder["value"] = value if defined?(@value) end - class FromUser < Attribute # :nodoc: - def type_cast(value) - type.cast(value) - end + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected - def came_from_user? - true - end - end + attr_reader :original_attribute + alias_method :assigned?, :original_attribute - class WithCastValue < Attribute # :nodoc: - def type_cast(value) - value + def original_value_for_database + if assigned? + original_attribute.original_value_for_database + else + _original_value_for_database + end end - def changed_in_place_from?(old_value) - false + private + def initialize_dup(other) + if defined?(@value) && @value.duplicable? + @value = @value.dup + end end - end - class Null < Attribute # :nodoc: - def initialize(name) - super(name, nil, Type::Value.new) + def changed_from_assignment? + assigned? && type.changed?(original_value, value, value_before_type_cast) end - def value - nil + def _original_value_for_database + type.serialize(original_value) end - def with_type(type) - self.class.with_cast_value(name, nil, type) - end + class FromDatabase < Attribute # :nodoc: + def type_cast(value) + type.deserialize(value) + end - def with_value_from_database(value) - raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" + def _original_value_for_database + value_before_type_cast + end end - alias_method :with_value_from_user, :with_value_from_database - end - class Uninitialized < Attribute # :nodoc: - def initialize(name, type) - super(name, nil, type) + class FromUser < Attribute # :nodoc: + def type_cast(value) + type.cast(value) + end + + def came_from_user? + true + end end - def value - if block_given? - yield name + class WithCastValue < Attribute # :nodoc: + def type_cast(value) + value + end + + def changed_in_place? + false end end - def value_for_database + class Null < Attribute # :nodoc: + def initialize(name) + super(name, nil, Type.default_value) + end + + def type_cast(*) + nil + end + + def with_type(type) + self.class.with_cast_value(name, nil, type) + end + + def with_value_from_database(value) + raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" + end + alias_method :with_value_from_user, :with_value_from_database end - def initialized? - false + class Uninitialized < Attribute # :nodoc: + UNINITIALIZED_ORIGINAL_VALUE = Object.new + + def initialize(name, type) + super(name, nil, type) + end + + def value + if block_given? + yield name + end + end + + def original_value + UNINITIALIZED_ORIGINAL_VALUE + end + + def value_for_database + end + + def initialized? + false + end + + def with_type(type) + self.class.new(name, type) + end end - end - private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue + private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue end end |