2

Rails 5 Alpha 版本 / Ruby 2.2.3 / active_model_serializers (0.10.0.rc3)(以下简称 AMS

GIT
  remote: https://github.com/rails/rails.git
  revision: 5217db2a7acb80b18475709018088535bdec6d30

GEM
  remote: https://rubygems.org/
  specs:
    active_model_serializers (0.10.0.rc3)

我已经有一个使用Rabl-Rails生成 JSON 响应的纯 API 应用程序。

我正在努力使其准备好与 Rails 5 一起使用,作为评估 Rails 5 内置 API 功能的一部分,尤其是 ActiveModel::Serializers 可以提供的可重用性和灵活性。

我创建了几个序列化程序

- app
  - serializers 
    - client
      - base_serializer.rb
    - device
      - base_serializer.rb
    - provider
      - base_serializer.rb

应用程序中的 JSON 响应是使用任何现有模板以复合方式创建的,并且base_serializer包含有关可以序列化的资源的基本数据。

下面显示的是我最初创建的 3 个序列化程序:

对于 ActiveRecord 型号设备

  class Device::BaseSerializer < ActiveModel::Serializer
    attributes :id, :name

    belongs_to :client, serializer: Client::BaseSerializer

    def client
      unless exclude_client?
        object.client
      end
    end

    private

    def device_opts?
      options.key?(:device) # options inherited from by ActiveModel::Serializer
    end

    def device_opts
      options[:device]
    end

    def exclude_client?
      device_opts? && device_opts.key?(:exclude_client) # options inherited from by ActiveModel::Serializer
    end
  end

对于 ActiveRecord 模型客户端

  class Client::BaseSerializer < ActiveModel::Serializer
    attributes :id, :name, :time_zone

    belongs_to :provider, serializer: Provider::BaseSerializer

    def provider
      unless exclude_provider?
        object.provider
      end
    end

    private

    def client_opts?
      options.key?(:client) # options inherited from by ActiveModel::Serializer
    end

    def client_opts
      options[:client]
    end

    def exclude_provider?
      client_opts? && client_opts.key?(:exclude_provider)
    end
  end

对于 ActiveRecord 模型提供者

  class Provider::BaseSerializer < ActiveModel::Serializer
    attributes :id, :name
  end

提供者模型

class Provider < ActiveRecord::Base
  has_many :clients, dependent: :destroy
end

客户模型

class Client < ActiveRecord::Base
  belongs_to :provider
  has_many :devices
end

设备型号

class Device < ActiveRecord::Base
  belongs_to :client
end

问题

序列化 Device 对象时,其客户端节点不包含Client::BaseSerializer中定义的客户端提供程序节点belongs_to :provider, serializer: Provider::BaseSerializer

device = Device.find(1)
options = { serializer: Device::BaseSerializer }
serializable_resource = ActiveModel::SerializableResource.new(device, options)
puts JSON.pretty_generate(serializable_resource.as_json)
{
  "id": 1,
  "name": "Test Device",
  "client": {
    "id": 2,
    "name": "Test Client",
    "time_zone": "Eastern Time (US & Canada)"
  }
}

但是,在序列化 Client 对象时,它包含提供程序节点:

client = Client.find(2)
options = { serializer: Client::BaseSerializer }
serializable_resource = ActiveModel::SerializableResource.new(client, options)
puts JSON.pretty_generate(serializable_resource.as_json)
{
  "id": 2,
  "name": "Test Client",
  "time_zone": "Eastern Time (US & Canada)",
  "provider": {
    "id": 1,
    "name": "Test Provider"
  }
}

从上面可以看出,当我们在其“客户端”属性中生成设备的 json 时,并没有生成客户端的提供者关系。但是,当我们生成客户端的 json 时,它包含“提供者”属性。传递任何包含选项,如下所示

options = { serializer: Client::BaseSerializer, include: "client.provider.**" }

如此处所述没有任何效果。

我深入研究了 AMS 源代码,发现include只有当适配器是JSON API. 但是,默认适配器是base.config.adapter = :flatten_json.

从适配器的实现和方法(如下所示)中可以看出,ActiveModel::Serializer#attributes(options = {})在序列化期间仅考虑以下数据:

  1. 对象的属性
  2. 对象的关系及其属性。不考虑关系自身的关系。

ActiveModel::Serializer::Adapter::FlattenJson < ActiveModel::Serializer::Adapter::Json

    def serializable_hash(options = nil)
      options ||= {}
      if serializer.respond_to?(:each)
        result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
      else
        hash = {}

        core = cache_check(serializer) do
          serializer.attributes(options)
        end

        serializer.associations.each do |association|
          serializer = association.serializer
          opts = association.options

          if serializer.respond_to?(:each)
            array_serializer = serializer
            hash[association.key] = array_serializer.map do |item|
              cache_check(item) do
                item.attributes(opts)
              end
            end
          else
            hash[association.key] =
              if serializer && serializer.object
                cache_check(serializer) do
                # As can be seen here ASSOCIATION's serializer's attributes only gets serialize and not its own relations.
                  serializer.attributes(options) 
                end
              elsif opts[:virtual_value]
                opts[:virtual_value]
              end
          end
        end

        result = core.merge hash
      end

ActiveModel::序列化器

def attributes(options = {})
  attributes =
    if options[:fields]
      self.class._attributes & options[:fields]
    else
      self.class._attributes.dup # <<<<<<<<<< here
    end

  attributes.each_with_object({}) do |name, hash|
    unless self.class._fragmented
      hash[name] = send(name)
    else
      hash[name] = self.class._fragmented.public_send(name)
    end
  end
end

由于我的 JSON 结构是自定义和预定义的,因此我无法切换到 JSON API 适配器。这留下了仅使用属性的选项,例如

  class Client::BaseSerializer < ActiveModel::Serializer
    attributes :id, :name, :time_zone

    attributes :provider

    def provider
      unless exclude_provider?
        object.provider
      end
    end
  end

但是这样我就无法灵活地为:provider 属性使用自定义序列化程序。

问题:

  1. 有什么办法可以解决上述问题并达到预期的效果?

  2. 是否有任何规定可以忽略属性被包含在序列化哈希中?例如,在我的 JSON 模板中使用 Rabl-Rails,我可以执行以下操作:

    node(:client, if: ->(device_decorator) {  !device_decorator.exclude_client? } ) do |device_decorator|
      partial('../clients/base', object: device_decorator.client_decorator)
    end
    

如果DeviceDecorator#exclude_client?返回 false,则不会在 JSON 中生成 :client 节点。

谢谢。

4

0 回答 0