跟踪下serialize干了什么

[2] pry(main)> class Student
[2] pry(main)*   binding.trace_tree(htmp: 'rails/serialize'){serialize :alias_names, Array}
[2] pry(main)* end


完整调用栈如下

20170711_221055_529_serialize.html

如果指定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如下

20170711_221127_849_write_serialized_attr.html

save如下

20170711_221144_361_save_serialized_attr.html

从调用栈可知,赋值时会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