aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/attribute.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/attribute.rb')
-rw-r--r--activerecord/lib/active_record/attribute.rb182
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