分层的、内存中的关联,做工不差

Heirarchical, in-memory associations without poor workmanship

我需要一个由 ActiveRecord 提取的嵌套的分层数据结构,当然都是早期加载的,我可以 JSONify。 TaskSetTaskTodoLineItem 都相关联,因为后者是前者的子代。很明显,我不是 Ruby 天才:我想出的最好的是

   @hsh = TaskSet.all.
                  includes(:tasks => {:todos => :lineitems} ).
                  map{ |ts| ts => ts.tasks.
                    map{ |t| t => t.todos.
                      map{ |td| td => td.lineitems }}}.
                  as_json

这对于非常小的 N(对于每个级别)来说是可以接受的,而对于更大的 N 根本无法使用。我正在寻找一种不涉及重复出现的 N+1 问题的解决方案。


奖金:

有没有办法抢占到另一个数据结构的音译并让 AR 预先加载内存中的所有深度关联,以便 @task_set.as_json 可以在不访问数据库的情况下完成同样的事情?

我觉得

@hsh = TaskSet.all
              .includes(:tasks => {:todos => :lineitems} )
              .joins(:tasks, :todos, :lineitems)
              .as_json

会这样做。

它将进行 1 个数据库查询。我不知道它能多高效地将数据结构转换为JSON,或者数据结构中有多少项。

参见Rails Guides Eager Loading

下面是一个示例,说明如何使用单个数据库查询来查询关联模型: Preload, Eagerload, Includes and Joins

不清楚您在哪里调用此代码,因此这可能不适用,但我假设您是在控制器中执行此操作。如果是这样的话,我会推荐以下方法

首先在您的控制器中加载所有任务集并预加载相关模型

def index
  @task_sets = TaskSet.includes(tasks: { todos: :lineitems })
end

这将对数据库进行 4 次调用,但会将所有关联的模型加载到内存中,这样遍历关联和内部关联就不会导致更多的数据库调用(即没有 N+1 问题)。

要构造实际的 JSON,我强烈建议使用 JSON 生成器,例如 jbuilder or RABL。由于 jbuilder 附带了最新版本的 Rails,我将使用它作为示例

app/views/task_sets/index.json.jbuilder下创建一个jbuilder视图,构建你需要的JSON。

json.array! @task_sets do |task_set|
  json.some_attribute task_set.some_attribute

  json.tasks task_set.tasks, :attribute_1, :attribute_2
  # ... #
end

使用 JSON 构建器比简单地调用 as_json 给您更多的控制权,尤其是在您包含子对象的情况下。希望这有帮助。