1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/lib/active_record/attribute.rb
Sean Griffin dd8b5fb9d3 update_column take ruby-land input, not database-land input
In the case of serialized columns, we would expect the unserialized
value as input, not the serialized value. The original issue which made
this distinction, #14163, introduced a bug. If you passed serialized
input to the method, it would double serialize when it was sent to the
database. You would see the wrong input upon reloading, or get an error
if you had a specific type on the serialized column.

To put it another way, `update_column` is a special case of
`update_all`, which would take `['a']` and not `['a'].to_yaml`, but you
would not pass data from `params` to it.

Fixes #18037
2014-12-16 15:23:05 -07:00

149 lines
3.1 KiB
Ruby

module ActiveRecord
class Attribute # :nodoc:
class << self
def from_database(name, value, type)
FromDatabase.new(name, value, type)
end
def from_user(name, value, type)
FromUser.new(name, value, type)
end
def with_cast_value(name, value, type)
WithCastValue.new(name, value, type)
end
def null(name)
Null.new(name)
end
def uninitialized(name, type)
Uninitialized.new(name, type)
end
end
attr_reader :name, :value_before_type_cast, :type
# This method should not be called directly.
# Use #from_database or #from_user
def initialize(name, value_before_type_cast, type)
@name = name
@value_before_type_cast = value_before_type_cast
@type = type
end
def value
# `defined?` is cheaper than `||=` when we get back falsy values
@value = original_value unless defined?(@value)
@value
end
def original_value
type_cast(value_before_type_cast)
end
def value_for_database
type.type_cast_for_database(value)
end
def changed_from?(old_value)
type.changed?(old_value, value, value_before_type_cast)
end
def changed_in_place_from?(old_value)
type.changed_in_place?(old_value, value)
end
def with_value_from_user(value)
self.class.from_user(name, value, type)
end
def with_value_from_database(value)
self.class.from_database(name, value, type)
end
def with_cast_value(value)
self.class.with_cast_value(name, value, type)
end
def type_cast(*)
raise NotImplementedError
end
def initialized?
true
end
def ==(other)
self.class == other.class &&
name == other.name &&
value_before_type_cast == other.value_before_type_cast &&
type == other.type
end
protected
def initialize_dup(other)
if defined?(@value) && @value.duplicable?
@value = @value.dup
end
end
class FromDatabase < Attribute # :nodoc:
def type_cast(value)
type.type_cast_from_database(value)
end
end
class FromUser < Attribute # :nodoc:
def type_cast(value)
type.type_cast_from_user(value)
end
end
class WithCastValue < Attribute # :nodoc:
def type_cast(value)
value
end
def changed_in_place_from?(old_value)
false
end
end
class Null < Attribute # :nodoc:
def initialize(name)
super(name, nil, Type::Value.new)
end
def value
nil
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
class Uninitialized < Attribute # :nodoc:
def initialize(name, type)
super(name, nil, type)
end
def value
if block_given?
yield name
end
end
def value_for_database
end
def initialized?
false
end
end
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
end
end