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 = {})
在序列化期间仅考虑以下数据:
- 对象的属性
- 对象的关系及其属性。不考虑关系自身的关系。
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 属性使用自定义序列化程序。
问题:
有什么办法可以解决上述问题并达到预期的效果?
是否有任何规定可以忽略属性被包含在序列化哈希中?例如,在我的 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 节点。
谢谢。