如何将具有关联的主动记录转换为嵌套哈希



so ...

我正在编写一些代码,这些代码会查询与关联的活动记录,并以类似于...

的格式返回数据
[{
    foo: {
        id: ...,
        foo_property: ... 
    },
    bar: {
        id: ...,
        bar_property: ...
    }
}, ...]

...最高级别的活动记录及其关联都有我想排除的时间戳和其他字段。我知道attributesas_json,但这两个都不遵循关系(而不是用foo_id和bar_id代替foo和bar)。我也知道AssociationReflection及其动态查找可以与一定复杂性执行我想要的事物的关联的能力,但理想情况下...

我想要一种简单,优雅的方式来查询我的数据,以这种转换和属性过滤,即

Whatever.joins(:foos, :bars)
        .eager_load(:foos, :bars)
        .? # convert results to an array of hashes with association data (under appropriately named hash key)
        .? # filter top level entries and association data to not have timestamps, etc. (select?)

...谢谢!

我建议使用ActiveModel ::支持关联的序列化器:

class WhateverSerializer < ApplicationSerializer
  attributes :id, :other_attribute, :third_attribute
  has_many :foos
  has_many :bars
end

这使您可以在最终的JSON输出(通过attributes)中指定所需的属性,并让您整合关联(它们将分别使用FooSerializerBarSerializer)。

在相应的控制器中您可以使用

render json: whatever

,该对象将由序列化器自动包装。或者,如果您有Whatever对象的数组,则可以执行以下操作:

render json: Whatever.all, each_serializer: WhateverSerializer

您可以使用以下语法实现相同的行为:

假设一个嵌套的关联,例如:Baz有很多:Bars和:Bar有很多:Foos

render json: Foo.includes(bar: :baz),
include: {bar: {include: {baz: {only: [:attr_a]}},
only: [:attr_b]}}, only: [:attr_1, :attr_2, :attr_3]

但是,我也建议使用ActiveModel :: serializers。当您有很多关联时,此语法尚不清楚。

您可以尝试deep_pluck

Whatever.deep_pluck(
  foo: [:id, :foo_property, ...],
  bar: [:id, :bar_property, ...],
)

虽然此问题的另一个答案提供了简洁的解决方案,以序列化已知关联,但下面是一个综合工具,可以序列任何对象的基于关联的依赖图,而无需具体专门列举每个所需的关联:

[这是我给"与关联检查对象"给出的答案的重新发布]

# app/models/application_record.rb
#
#   placing the helper in the ApplicationRecord superclass
#   allows all application models to inherit the helper

class ApplicationRecord < ActiveRecord::Base
  def self.marshal
    # collect the names of the objects associations
    single_associations = self.class.reflect_on_all_associations(:has_one ).map {|x| x.name}
    plural_associations = self.class.reflect_on_all_associations(:has_many).map {|x| x.name}
    # serialize the object as a JSON-compatible hash
    self.as_json.merge(
      # merge in a hash containing each `has_one` association via recursive marshalling
      #   the resulting set of associated objects are merged into
      #   the original object's serialized hash, each keyed by the name of the association
        single_associations.reduce({}) { |memo, assoc| memo.merge({ assoc => self.send(assoc).marshal }) }.as_json
    ).merge(
      # merge in the `has_many` associations
      #   the resulting set of associated collections must then be processed
      #   via mapping each collection into an array of singular serialized objects
        plural_associations.reduce({}) { |memo, assoc| memo.merge({ assoc => self.send(assoc).map {|item| item.marshal } }) }.as_json
    )
  end
end

然后,您可以通过致电以下方式调用此助手方法:

Marshal.serialize whatever

这与检查并不完全相同,因为它实际上是将对象序列化为哈希结构,但是它将为您提供类似的信息。


请注意,将可能的关联分开分为两组:单数关联(引用单个目标对象)和复数关联(它们是ActiverEcord Collection -collection -collection -collectionproxy对象,即它们是枚举的)。因为我们将关联的对象序列化为哈希,所以每个has_many关联都必须作为单独序列化对象的集合解析(例如,我们将集合中的每个关联映射为其序列化形式)。

应忽略belongs_to关联,因为两个方向上的映射关联将立即创建一个圆形依赖图。如果您想沿着"归属链"元帅,可以做

之类的事情
def self.trace
    parent_associations = obj.class.reflect_on_all_associations(:belongs_to).map {|x| x.name}
    obj.as_json.merge single_associations.reduce({}) { |memo, assoc| memo.merge({ assoc => obj.send(assoc).trace }) }.as_json
end

最新更新