跳至内容 跳至搜索
方法
S

实例公共方法

serialize(attr_name, coder: nil, type: Object, yaml: {}, **options)

如果您有一个需要以序列化对象的格式保存到数据库,并通过反序列化为同一个对象检索的属性,那么可以使用此方法指定该属性的名称,序列化将自动处理。

序列化格式可以是 YAML、JSON 或使用自定义编码器类的任何自定义格式。

请记住,数据库适配器为您处理某些序列化任务。例如:PostgreSQL 中的 jsonjsonb 类型将在 JSON 对象/数组语法和 Ruby HashArray 对象之间透明地转换。在这种情况下,无需使用 serialize

对于更复杂的情况,例如与应用程序域对象之间的转换,请考虑使用 ActiveRecord::Attributes API。

参数

  • attr_name - 要序列化的属性的名称。

  • coder 要使用的序列化程序实现,例如 JSON

    • 属性值将使用编码器 dump(value) 方法序列化,并将使用编码器 load(string) 方法反序列化。dump 方法可能会返回 nil 以将值序列化为 NULL

  • type - 可选。序列化对象的类型应该是什么。

  • yaml - 可选。Yaml 特定选项。允许的配置是

    • :permitted_classes - 包含允许类别的 Array

    • :unsafe_load - 不安全地加载 YAML 块,允许 YAML 加载任何类。

选项

  • :default - 当没有提供值时使用的默认值。如果未传递此选项,将使用先前的默认值(如果有)。否则,默认值为 nil

选择序列化程序

虽然可以使用任何序列化格式,但在使用序列化程序之前,建议仔细评估序列化程序的属性,因为稍后迁移到另一种格式可能会很困难。

避免接受任意类型

在对列中的数据进行序列化时,强烈建议确保只序列化预期类型。例如,一些序列化程序(如 MarshalYAML)能够序列化几乎所有 Ruby 对象。

这会导致序列化意外类型,并且重要的是类型序列化保持向后和向前兼容,只要一些数据库记录仍然包含这些序列化类型。

class Address
  def initialize(line, city, country)
    @line, @city, @country = line, city, country
  end
end

在上例中,如果任何 Address 属性被重命名,在更改之前持久化的实例将使用旧属性加载。当序列化类型来自不期望以这种方式序列化并且可能会在不通知的情况下更改其内部表示形式的依赖项时,这个问题更严重。

因此,强烈建议将这些对象转换为序列化格式的基本类型,例如

class Address
  attr_reader :line, :city, :country

  def self.load(payload)
    data = YAML.safe_load(payload)
    new(data["line"], data["city"], data["country"])
  end

  def self.dump(address)
    YAML.safe_dump(
      "line" => address.line,
      "city" => address.city,
      "country" => address.country,
    )
  end

  def initialize(line, city, country)
    @line, @city, @country = line, city, country
  end
end

class User < ActiveRecord::Base
  serialize :address, coder: Address
end

这种模式允许对要序列化的内容更加谨慎,并以向后兼容的方式演化格式。

确保序列化稳定性

一些序列化方法可能接受它们不支持的某些类型,通过将它们静默地转换为其他类型。当数据被反序列化时,这会导致错误。

例如,标准库中提供的 JSON 序列化程序会将不支持的类型静默地转换为 String

>> JSON.parse(JSON.dump(Struct.new(:foo)))
=> "#<Class:0x000000013090b4c0>"

例子

使用 YAML 序列化 preferences 属性
class User < ActiveRecord::Base
  serialize :preferences, coder: YAML
end
使用 JSON 序列化 preferences 属性
class User < ActiveRecord::Base
  serialize :preferences, coder: JSON
end
使用 YAML 序列化 preferences Hash
class User < ActiveRecord::Base
  serialize :preferences, type: Hash, coder: YAML
end
preferences 序列化为 YAML,允许选择类
class User < ActiveRecord::Base
  serialize :preferences, coder: YAML, yaml: { permitted_classes: [Symbol, Time] }
end
使用自定义编码器序列化 preferences 属性
class Rot13JSON
  def self.rot13(string)
    string.tr("a-zA-Z", "n-za-mN-ZA-M")
  end

  # Serializes an attribute value to a string that will be stored in the database.
  def self.dump(value)
    rot13(ActiveSupport::JSON.dump(value))
  end

  # Deserializes a string from the database to an attribute value.
  def self.load(string)
    ActiveSupport::JSON.load(rot13(string))
  end
end

class User < ActiveRecord::Base
  serialize :preferences, coder: Rot13JSON
end
# File activerecord/lib/active_record/attribute_methods/serialization.rb, line 183
        def serialize(attr_name, coder: nil, type: Object, yaml: {}, **options)
          coder ||= default_column_serializer
          unless coder
            raise ArgumentError, <<~MSG.squish
              missing keyword: :coder

              If no default coder is configured, a coder must be provided to `serialize`.
            MSG
          end

          column_serializer = build_column_serializer(attr_name, coder, type, yaml)

          attribute(attr_name, **options)

          decorate_attributes([attr_name]) do |attr_name, cast_type|
            if type_incompatible_with_serialize?(cast_type, coder, type)
              raise ColumnNotSerializableError.new(attr_name, cast_type)
            end

            cast_type = cast_type.subtype if Type::Serialized === cast_type
            Type::Serialized.new(cast_type, column_serializer)
          end
        end