Skip to content

Preload the tree based on a current element depth

Regis Millet edited this page Mar 23, 2021 · 7 revisions

Preload the tree

Why ?

You would like to call ".children" on every element in a recursive method to generate a beautiful tree, either in your views, or to render a nested JSON object.

But doing so, you will have a lot of N+1 queries (one call to your database per .children use). Which is really bad for loading speed. So, it would be best to somehow preload your tree, but...

How ?

Simply add the below "preload_tree" class method in your model.

class Category < ActiveRecord::Base
  acts_as_nested_set :order_column => :name, dependent: :destroy

  def self.preload_tree(actual_depth=nil)
    # get max_depth
    max_depth = Category.unscoped.maximum(:depth)
    actual_depth ||= self.minimum(:depth)

    return self.all if max_depth.nil? || actual_depth.nil?

    preloading = :children

    (max_depth - actual_depth).times do
      preloading = {children: preloading} # you can include some other preloads here, if you want, like this: [preloading, :articles]
    end

    self.includes(preloading) # or preload, just a matter of taste here
  end
end

Then anywhere you want:

Category.roots.preload_tree.each do |cat_root|
  category.children.each do |cat_level1|
    cat_level1.children.each ...
  end
end

You get the idea. It works best with recursive methods.

How does this work?

Simple, it actually uses the Active Record logic to preload associations. We are simply generating a big: .includes({:children=>{:children=>{:children=>:children ...}}}

That will do only 1 DB call per depth between min and max depth.

You can also set the "actual depth" if you are working on children of a none root element.

Hope this will help others!