mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Remove most code related to serialized properties
Nearly completely implemented in terms of custom properties. `_before_type_cast` now stores the raw serialized string consistently, which removes the need to keep track of "state". The following is now consistently true: - `model.serialized == model.reload.serialized` - A model can be dumped and loaded infinitely without changing - A model can be saved and reloaded infinitely without changing
This commit is contained in:
parent
260c384bdb
commit
90c8be76a7
12 changed files with 30 additions and 103 deletions
|
@ -287,11 +287,6 @@ module ActiveRecord
|
|||
}
|
||||
end
|
||||
|
||||
# Placeholder so it can be overriden when needed by serialization
|
||||
def attributes_for_coder # :nodoc:
|
||||
attributes_before_type_cast
|
||||
end
|
||||
|
||||
# Returns an <tt>#inspect</tt>-like string for the value of the
|
||||
# attribute +attr_name+. String attributes are truncated upto 50
|
||||
# characters, Date and Time attributes are returned in the
|
||||
|
|
|
@ -94,7 +94,7 @@ module ActiveRecord
|
|||
|
||||
def cacheable_column?(column)
|
||||
if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
|
||||
! serialized_attributes.include? column.name
|
||||
true
|
||||
else
|
||||
attribute_types_cached_by_default.include?(column.type)
|
||||
end
|
||||
|
|
|
@ -76,21 +76,6 @@ module ActiveRecord
|
|||
module Behavior # :nodoc:
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods # :nodoc:
|
||||
def initialize_attributes(attributes, options = {})
|
||||
serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
|
||||
super(attributes, options)
|
||||
|
||||
serialized_attributes.each do |key, coder|
|
||||
if attributes.key?(key)
|
||||
attributes[key] = Type::Serialized::Attribute.new(coder, attributes[key], serialized)
|
||||
end
|
||||
end
|
||||
|
||||
attributes
|
||||
end
|
||||
end
|
||||
|
||||
def should_record_timestamps?
|
||||
super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?)
|
||||
end
|
||||
|
@ -106,42 +91,6 @@ module ActiveRecord
|
|||
super
|
||||
end
|
||||
end
|
||||
|
||||
def read_attribute_before_type_cast(attr_name)
|
||||
if self.class.serialized_attributes.include?(attr_name)
|
||||
super.unserialized_value
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def attributes_before_type_cast
|
||||
super.dup.tap do |attributes|
|
||||
self.class.serialized_attributes.each_key do |key|
|
||||
if attributes.key?(key)
|
||||
attributes[key] = attributes[key].unserialized_value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def typecasted_attribute_value(name)
|
||||
if self.class.serialized_attributes.include?(name)
|
||||
@raw_attributes[name].serialized_value
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def attributes_for_coder
|
||||
attribute_names.each_with_object({}) do |name, attrs|
|
||||
attrs[name] = if self.class.serialized_attributes.include?(name)
|
||||
@raw_attributes[name].serialized_value
|
||||
else
|
||||
read_attribute_before_type_cast(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -53,11 +53,11 @@ module ActiveRecord
|
|||
# specified +value+. Empty strings for fixnum and float columns are
|
||||
# turned into +nil+.
|
||||
def write_attribute(attr_name, value)
|
||||
write_attribute_with_type_cast(attr_name, value, :type_cast_for_write)
|
||||
write_attribute_with_type_cast(attr_name, value, true)
|
||||
end
|
||||
|
||||
def raw_write_attribute(attr_name, value)
|
||||
write_attribute_with_type_cast(attr_name, value, :raw_type_cast_for_write)
|
||||
write_attribute_with_type_cast(attr_name, value, false)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -66,7 +66,7 @@ module ActiveRecord
|
|||
write_attribute(attribute_name, value)
|
||||
end
|
||||
|
||||
def write_attribute_with_type_cast(attr_name, value, type_cast_method)
|
||||
def write_attribute_with_type_cast(attr_name, value, should_type_cast)
|
||||
attr_name = attr_name.to_s
|
||||
attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
|
||||
@attributes.delete(attr_name)
|
||||
|
@ -78,9 +78,9 @@ module ActiveRecord
|
|||
@attributes[attr_name] = value
|
||||
end
|
||||
|
||||
if column
|
||||
@raw_attributes[attr_name] = column.public_send(type_cast_method, value)
|
||||
elsif @raw_attributes.has_key?(attr_name)
|
||||
if column && should_type_cast
|
||||
@raw_attributes[attr_name] = column.type_cast_for_write(value)
|
||||
elsif !should_type_cast || @raw_attributes.has_key?(attr_name)
|
||||
@raw_attributes[attr_name] = value
|
||||
else
|
||||
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
|
||||
|
|
|
@ -17,7 +17,7 @@ module ActiveRecord
|
|||
|
||||
delegate :type, :precision, :scale, :limit, :klass, :accessor,
|
||||
:text?, :number?, :binary?, :serialized?,
|
||||
:type_cast, :type_cast_for_write, :raw_type_cast_for_write, :type_cast_for_database,
|
||||
:type_cast, :type_cast_for_write, :type_cast_for_database,
|
||||
:type_cast_for_schema,
|
||||
to: :cast_type
|
||||
|
||||
|
@ -52,7 +52,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def extract_default(default)
|
||||
type_cast(default)
|
||||
type_cast_for_write(type_cast(default))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -353,7 +353,7 @@ module ActiveRecord
|
|||
# Post.new.encode_with(coder)
|
||||
# coder # => {"attributes" => {"id" => nil, ... }}
|
||||
def encode_with(coder)
|
||||
coder['attributes'] = attributes_for_coder
|
||||
coder['attributes'] = @raw_attributes
|
||||
end
|
||||
|
||||
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
|
||||
|
|
|
@ -10,20 +10,21 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def type_cast(value)
|
||||
if value.respond_to?(:unserialized_value)
|
||||
value.unserialized_value(super(value.value))
|
||||
if is_default_value?(value)
|
||||
value
|
||||
else
|
||||
super
|
||||
coder.load(super)
|
||||
end
|
||||
end
|
||||
|
||||
def type_cast_for_write(value)
|
||||
Attribute.new(coder, value, :unserialized)
|
||||
return if value.nil?
|
||||
unless is_default_value?(value)
|
||||
coder.dump(value)
|
||||
end
|
||||
end
|
||||
|
||||
def raw_type_cast_for_write(value)
|
||||
Attribute.new(coder, value, :serialized)
|
||||
end
|
||||
alias type_cast_for_database type_cast_for_write
|
||||
|
||||
def serialized?
|
||||
true
|
||||
|
@ -33,24 +34,10 @@ module ActiveRecord
|
|||
ActiveRecord::Store::IndifferentHashAccessor
|
||||
end
|
||||
|
||||
class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
|
||||
def unserialized_value(v = value)
|
||||
state == :serialized ? unserialize(v) : value
|
||||
end
|
||||
private
|
||||
|
||||
def serialized_value
|
||||
state == :unserialized ? serialize : value
|
||||
end
|
||||
|
||||
def unserialize(v)
|
||||
self.state = :unserialized
|
||||
self.value = coder.load(v)
|
||||
end
|
||||
|
||||
def serialize
|
||||
self.state = :serialized
|
||||
self.value = coder.dump(value)
|
||||
end
|
||||
def is_default_value?(value)
|
||||
value == coder.load(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,7 +54,6 @@ module ActiveRecord
|
|||
def type_cast_for_write(value) # :nodoc:
|
||||
value
|
||||
end
|
||||
alias_method :raw_type_cast_for_write, :type_cast_for_write # :internal:
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ module ActiveRecord
|
|||
finder_class = find_finder_class_for(record)
|
||||
table = finder_class.arel_table
|
||||
value = map_enum_attribute(finder_class, attribute, value)
|
||||
value = deserialize_attribute(record, attribute, value)
|
||||
|
||||
relation = build_relation(finder_class, table, attribute, value)
|
||||
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
|
||||
|
@ -86,12 +85,6 @@ module ActiveRecord
|
|||
relation
|
||||
end
|
||||
|
||||
def deserialize_attribute(record, attribute, value)
|
||||
coder = record.class.serialized_attributes[attribute.to_s]
|
||||
value = coder.dump value if value && coder
|
||||
value
|
||||
end
|
||||
|
||||
def map_enum_attribute(klass, attribute, value)
|
||||
mapping = klass.defined_enums[attribute.to_s]
|
||||
value = mapping[value] if value && mapping
|
||||
|
|
|
@ -93,6 +93,7 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def type_cast_for_write(value)
|
||||
return if value.nil?
|
||||
"(#{value.city},#{value.street})"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -870,7 +870,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def cached_columns
|
||||
Topic.columns.map(&:name) - Topic.serialized_attributes.keys
|
||||
Topic.columns.map(&:name)
|
||||
end
|
||||
|
||||
def time_related_columns_on_topic
|
||||
|
|
|
@ -59,8 +59,9 @@ class SerializedAttributeTest < ActiveRecord::TestCase
|
|||
def test_serialized_attribute_calling_dup_method
|
||||
Topic.serialize :content, JSON
|
||||
|
||||
t = Topic.new(:content => { :foo => :bar }).dup
|
||||
assert_equal({ :foo => :bar }, t.content_before_type_cast)
|
||||
orig = Topic.new(content: { foo: :bar })
|
||||
clone = orig.dup
|
||||
assert_equal(orig.content, clone.content)
|
||||
end
|
||||
|
||||
def test_serialized_attribute_declared_in_subclass
|
||||
|
@ -103,8 +104,10 @@ class SerializedAttributeTest < ActiveRecord::TestCase
|
|||
|
||||
def test_serialized_attribute_should_raise_exception_on_save_with_wrong_type
|
||||
Topic.serialize(:content, Hash)
|
||||
topic = Topic.new(:content => "string")
|
||||
assert_raise(ActiveRecord::SerializationTypeMismatch) { topic.save }
|
||||
assert_raise(ActiveRecord::SerializationTypeMismatch) do
|
||||
topic = Topic.new(content: 'string')
|
||||
topic.save
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
|
||||
|
|
Loading…
Reference in a new issue