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

时间:2017-03-13 08:12:09

标签: ruby-on-rails ruby activerecord rails-activerecord

... 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?)

...谢谢!

4 个答案:

答案 0 :(得分:2)

我建议使用支持关联的ActiveModel::Serializers

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

答案 1 :(得分:0)

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

假设嵌套关联如: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。当你有很多关联时,这种语法就不清楚了。

答案 2 :(得分:0)

您可以尝试deep_pluck

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

答案 3 :(得分:0)

该问题的其他答案为序列化已知关联提供了简洁的解决方案,以下是一个综合工具,可对任何对象序列化整个基于关联的依赖关系图,而无需专门枚举每个所需的关联:

[这是我对“ Inspect object with associations”给出的答案的转贴]

# 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 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