active_record的serialize类方法
跟踪下serialize干了什么
[2] pry(main)> class Student
[2] pry(main)* binding.trace_tree(htmp: 'rails/serialize'){serialize :alias_names, Array}
[2] pry(main)* end
完整调用栈如下
如果指定Array、Hash,那么就会使用Coders::YAMLColumn来进行serialize/deserialize
# activerecord-5.0.4/lib/active_record/attribute_methods/serialization.rb
def serialize(attr_name, class_name_or_coder = Object)
# When ::JSON is used, force it to go through the Active Support JSON encoder
# to ensure special objects (e.g. Active Record models) are dumped correctly
# using the #as_json hook.
coder = if class_name_or_coder == ::JSON
Coders::JSON
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
class_name_or_coder
else
Coders::YAMLColumn.new(class_name_or_coder)
end
decorate_attribute_type(attr_name, :serialize) do |type|
Type::Serialized.new(type, coder)
end
end
而从decorate_attribute_type下还可以看到,一个model的哪些属性使用了那些包装,可以用attribute_type_decorations来检查
[11] pry(main)> Student.attribute_type_decorations
=> #<activerecord::attributedecorators::typedecorator:0x007f8f78b94a38 @decorations=" " {"_time_zone_conversion"="">
[#<proc:0x007f8f77cd7ba8@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activerecord-5.0.4="" lib="" active_record="" attribute_methods="" time_zone_conversion.rb:78="" (lambda)="">,
#<proc:0x007f8f77cd7b08@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activerecord-5.0.4="" lib="" active_record="" attribute_methods="" time_zone_conversion.rb:79="">],
"_optimistic_locking"=>
[#<proc:0x007f8f77cd6028@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activerecord-5.0.4="" lib="" active_record="" locking="" optimistic.rb:177="" (lambda)="">,
#<proc:0x007f8f77cd5fd8@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activerecord-5.0.4="" lib="" active_record="" locking="" optimistic.rb:178="">],
"_gender_enum"=>
[#<proc:0x007f8f77ccb128@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activerecord-5.0.4="" lib="" active_record="" attribute_decorators.rb:12="" (lambda)="">,
#<proc:0x007f8f77ccb1f0@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activerecord-5.0.4="" lib="" active_record="" enum.rb:165="">],
"_alias_names_serialize"=>
[#<proc:0x007f8f78ce83a8@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activerecord-5.0.4="" lib="" active_record="" attribute_decorators.rb:12="" (lambda)="">,
#<proc:0x007f8f78cf8190@ home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" activerecord-5.0.4="" lib="" active_record="" attribute_methods="" serialization.rb:60="">]}></proc:0x007f8f78cf8190@></proc:0x007f8f78ce83a8@></proc:0x007f8f77ccb1f0@></proc:0x007f8f77ccb128@></proc:0x007f8f77cd5fd8@></proc:0x007f8f77cd6028@></proc:0x007f8f77cd7b08@></proc:0x007f8f77cd7ba8@></activerecord::attributedecorators::typedecorator:0x007f8f78b94a38>
由此,可推断它跟enum一样也会经过同样的一套type cast流程。这个可以跟踪write和save来检查
[4] pry(main)> binding.trace_tree(htmp: 'rails/write_serialized_attr'){s.alias_names = ['jay', 'jj']}
=> ["jay", "jj"]
[7] pry(main)> binding.trace_tree(htmp: 'rails/save_serialized_attr'){s.save}
(13.4ms) begin transaction
SQL (16.2ms) UPDATE "students" SET "updated_at" = ?, "alias_names" = ? WHERE "students"."no" = ? [["updated_at", "2017-07-11 14:11:44.588915"], ["alias_names", "---\n- jay\n- jj\n"], ["no", "1"]]
(20.7ms) commit transaction
=> true
write如下
save如下
从调用栈可知,赋值时会assert_valid_value,持久化时会dump(检查dirty?时似乎会load出来在ruby中对比)
# activerecord-5.0.4/lib/active_record/coders/yaml_column.rb
module ActiveRecord
module Coders
class YAMLColumn
def dump(obj)
return if obj.nil?
assert_valid_value(obj)
YAML.dump obj
end
def load(yaml)
return object_class.new if object_class != Object && yaml.nil?
return yaml unless yaml.is_a?(String) && yaml =~ /^---/
obj = YAML.load(yaml)
assert_valid_value(obj)
obj ||= object_class.new if object_class != Object
obj
end
def assert_valid_value(obj)
unless obj.nil? || obj.is_a?(object_class)
raise SerializationTypeMismatch,
"Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
end
end