2014-06-07 15:46:22 -04:00
|
|
|
module ActiveRecord
|
|
|
|
class Attribute # :nodoc:
|
|
|
|
class << self
|
2014-06-22 18:57:40 -04:00
|
|
|
def from_database(name, value, type)
|
|
|
|
FromDatabase.new(name, value, type)
|
2014-06-07 15:46:22 -04:00
|
|
|
end
|
|
|
|
|
2014-06-22 18:57:40 -04:00
|
|
|
def from_user(name, value, type)
|
|
|
|
FromUser.new(name, value, type)
|
2014-06-07 15:46:22 -04:00
|
|
|
end
|
2014-06-22 17:38:55 -04:00
|
|
|
|
`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 17:19:47 -05:00
|
|
|
def with_cast_value(name, value, type)
|
|
|
|
WithCastValue.new(name, value, type)
|
|
|
|
end
|
|
|
|
|
2014-06-22 18:57:40 -04:00
|
|
|
def null(name)
|
|
|
|
Null.new(name)
|
|
|
|
end
|
|
|
|
|
|
|
|
def uninitialized(name, type)
|
|
|
|
Uninitialized.new(name, type)
|
2014-06-22 17:38:55 -04:00
|
|
|
end
|
2014-06-07 15:46:22 -04:00
|
|
|
end
|
|
|
|
|
2014-06-22 18:57:40 -04:00
|
|
|
attr_reader :name, :value_before_type_cast, :type
|
2014-06-07 15:46:22 -04:00
|
|
|
|
|
|
|
# This method should not be called directly.
|
|
|
|
# Use #from_database or #from_user
|
2014-06-22 18:57:40 -04:00
|
|
|
def initialize(name, value_before_type_cast, type)
|
|
|
|
@name = name
|
2014-06-07 15:46:22 -04:00
|
|
|
@value_before_type_cast = value_before_type_cast
|
|
|
|
@type = type
|
|
|
|
end
|
|
|
|
|
|
|
|
def value
|
|
|
|
# `defined?` is cheaper than `||=` when we get back falsy values
|
2014-07-12 20:30:49 -04:00
|
|
|
@value = original_value unless defined?(@value)
|
2014-06-07 15:46:22 -04:00
|
|
|
@value
|
|
|
|
end
|
|
|
|
|
2014-07-12 20:30:49 -04:00
|
|
|
def original_value
|
|
|
|
type_cast(value_before_type_cast)
|
|
|
|
end
|
|
|
|
|
2014-06-07 15:46:22 -04:00
|
|
|
def value_for_database
|
|
|
|
type.type_cast_for_database(value)
|
|
|
|
end
|
|
|
|
|
2014-06-16 13:20:55 -04:00
|
|
|
def changed_from?(old_value)
|
|
|
|
type.changed?(old_value, value, value_before_type_cast)
|
|
|
|
end
|
|
|
|
|
|
|
|
def changed_in_place_from?(old_value)
|
2015-01-20 16:09:53 -05:00
|
|
|
has_been_read? && type.changed_in_place?(old_value, value)
|
2014-06-16 13:20:55 -04:00
|
|
|
end
|
|
|
|
|
2014-06-22 18:25:40 -04:00
|
|
|
def with_value_from_user(value)
|
2014-06-22 18:57:40 -04:00
|
|
|
self.class.from_user(name, value, type)
|
2014-06-22 18:25:40 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def with_value_from_database(value)
|
2014-06-22 18:57:40 -04:00
|
|
|
self.class.from_database(name, value, type)
|
2014-06-22 18:25:40 -04:00
|
|
|
end
|
|
|
|
|
`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 17:19:47 -05:00
|
|
|
def with_cast_value(value)
|
|
|
|
self.class.with_cast_value(name, value, type)
|
|
|
|
end
|
|
|
|
|
2014-07-12 20:30:49 -04:00
|
|
|
def type_cast(*)
|
2014-06-07 15:46:22 -04:00
|
|
|
raise NotImplementedError
|
|
|
|
end
|
|
|
|
|
2014-06-22 17:38:55 -04:00
|
|
|
def initialized?
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2015-01-14 19:08:25 -05:00
|
|
|
def came_from_user?
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2015-01-20 16:09:53 -05:00
|
|
|
def has_been_read?
|
|
|
|
defined?(@value)
|
|
|
|
end
|
|
|
|
|
2014-08-15 15:37:53 -04:00
|
|
|
def ==(other)
|
|
|
|
self.class == other.class &&
|
|
|
|
name == other.name &&
|
|
|
|
value_before_type_cast == other.value_before_type_cast &&
|
|
|
|
type == other.type
|
|
|
|
end
|
|
|
|
|
2014-06-07 15:46:22 -04:00
|
|
|
protected
|
|
|
|
|
|
|
|
def initialize_dup(other)
|
|
|
|
if defined?(@value) && @value.duplicable?
|
|
|
|
@value = @value.dup
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-06-24 06:30:21 -04:00
|
|
|
class FromDatabase < Attribute # :nodoc:
|
2014-06-07 15:46:22 -04:00
|
|
|
def type_cast(value)
|
|
|
|
type.type_cast_from_database(value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-06-24 06:30:21 -04:00
|
|
|
class FromUser < Attribute # :nodoc:
|
2014-06-07 15:46:22 -04:00
|
|
|
def type_cast(value)
|
|
|
|
type.type_cast_from_user(value)
|
|
|
|
end
|
2015-01-14 19:08:25 -05:00
|
|
|
|
|
|
|
def came_from_user?
|
|
|
|
true
|
|
|
|
end
|
2014-06-07 15:46:22 -04:00
|
|
|
end
|
2014-06-16 13:20:55 -04:00
|
|
|
|
`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 17:19:47 -05:00
|
|
|
class WithCastValue < Attribute # :nodoc:
|
|
|
|
def type_cast(value)
|
|
|
|
value
|
|
|
|
end
|
|
|
|
|
|
|
|
def changed_in_place_from?(old_value)
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-06-22 18:57:40 -04:00
|
|
|
class Null < Attribute # :nodoc:
|
|
|
|
def initialize(name)
|
|
|
|
super(name, nil, Type::Value.new)
|
2014-06-22 18:25:40 -04:00
|
|
|
end
|
2014-06-22 17:38:55 -04:00
|
|
|
|
2014-06-22 18:25:40 -04:00
|
|
|
def value
|
|
|
|
nil
|
2014-06-22 17:38:55 -04:00
|
|
|
end
|
2014-06-26 11:36:40 -04:00
|
|
|
|
|
|
|
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
|
2014-06-22 17:38:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
class Uninitialized < Attribute # :nodoc:
|
2014-06-22 18:57:40 -04:00
|
|
|
def initialize(name, type)
|
|
|
|
super(name, nil, type)
|
2014-06-22 17:38:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def value
|
2014-06-22 18:57:40 -04:00
|
|
|
if block_given?
|
|
|
|
yield name
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def value_for_database
|
2014-06-22 17:38:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def initialized?
|
|
|
|
false
|
2014-06-16 13:20:55 -04:00
|
|
|
end
|
|
|
|
end
|
2014-06-22 18:57:40 -04:00
|
|
|
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
|
2014-06-07 15:46:22 -04:00
|
|
|
end
|
|
|
|
end
|