【问题标题】:Eager load self - join table recursively递归加载自连接表
【发布时间】:2026-01-23 00:30:01
【问题描述】:

我有一个模型

class MyModel < ActiveRecord::Base
  belongs_to :parent, :class_name => 'MyModel'
  has_many :children, :class_name => 'MyModel', :foreign_key => 'parent_id'
end

我想抓取所有没有父对象的对象,并将它们与所有子对象一起获取。换句话说,我想要所有的父母和他们的孩子,以及他们孩子的孩子等等。所以它必须以递归方式急切加载。 如果我这样做了

    @result = MyModel.includes(:children)
             .where('parent IS ?', nil)

我只有一个级别的孩子的父母。但我没有得到孩子的孩子。是否有可能递归地获得所有这些?是否可以添加计算每个父母的孩子数量?

【问题讨论】:

    标签: ruby recursion activerecord eager-loading


    【解决方案1】:

    我建议您使用像 awesome_nested_set 这样的嵌套集合 gem。这将为您提供很多功能来帮助处理类似对象的集合,并包括存储子项计数的功能。

    【讨论】:

    • 然后查看 gem 代码并从中获得灵感——这就是开源的美妙之处
    【解决方案2】:

    您可以通过传入嵌套哈希来递归地预加载一定数量的自联接

    @result = MyModel.includes(
      children: {
        children: {
          children: {
            children: :children
          }
        }
      }
    ).where('parent IS ?', nil)
    

    或者更好的是动态生成这个哈希

    这是未经测试的(基本上是伪代码)

    scope :recursivly_include_children, -> (max_depth) {
      includes(generate_nested_children_hash(max_depth))
    }
    
    def self.generate_nested_children_hash(current_hash = {}, level = 0, remaining_levels)
      if remaining_levels > 0
        // add one level to the current_hash here
    
        generate_nested_children_hash(current_hash, level+1, remaining_levels-1)
      else
        current_hash
      end
    end
    

    然后在控制器中获取最多10个级别的预加载子项:

    @result = MyModel.recursivly_include_children(10).where('parent IS ?', nil)
    

    【讨论】: