1

I have a model Tag, which is a tree model (using closure_tree) and thanks to the gem I can do stuff like:

Tag.includes(:children).first

to eager load its children. Now, I have an instance method, which also uses children. So when I get a tag by the mentioned query, iterate over the tag and its children and call this instance method, the bullet gem complains that I am doing a N+1 queries and advises me to include(children). This happens because I have not loaded the children of the children, so when calling the instance method, I make a separate query for the children of each sub-tag of the first-level tag.

I can solve this by doing the following:

Tag.includes(children: :children).first

In this case ActiveRecord eagerly loads the tag, its children and the children of its children. This, however, works if there are only 3 generations - the tag is a grandparent and then come the parents (which are children of the root tag) and the grandchildren (which are the children of the children of the root tag). If I have, say, 4 generations, than I must do:

Tag.includes(children: {children: :children}).first

So if I can determine the depth of the farthest grand child of a root tag, is there a way to conditionally construct my query - if depth is 2, use includes(:children), if depth is 3 - use includes(children: :children) and so on?

Alexander Popov
  • 23,073
  • 19
  • 91
  • 130

2 Answers2

0

Judging from the documentation at: https://github.com/mceachen/closure_tree#usage you should use

Tag.descendants
Tag.self_and_descendants

This will retrieves all the children, sub-children, etc.. until it ends on a leaf.

Typpex
  • 488
  • 4
  • 11
  • This is the way to determine the depth. However, I cannot use `Tag. Includes(:descendants)`. – Alexander Popov Dec 11 '14 at 06:16
  • but if you use descendants it will load all sub-children at once so you won't have N+1 queries anymore which is the original purpose of your post right ? – Typpex Dec 11 '14 at 06:51
  • Ok, I'll try it and give feedback :) – Alexander Popov Dec 11 '14 at 06:52
  • I'll give feedback today. – Alexander Popov Dec 16 '14 at 07:32
  • so this didn't work out. Doing `Tag.includes(:children).find(tag_id)` returns a normal tag object. The thing is - if I call `children` on it, it won't make a call to the db. `descendants` returns just a linear array of all tags, that are descendants of root, so that the parant-child relationships are not preserved. – Alexander Popov Dec 16 '14 at 08:28
  • check my answer to see my current solution. – Alexander Popov Dec 16 '14 at 09:26
  • If you execute Tag.includes(:children).find(tag_id) and you call children on it of course it will not make any call to the DB because the children already loaded thank to the include you just made. This is the purpose of include. Why not make this: tag = Tag.find(tag_id) all_childrens = tag.descendants Then you will get all children regardless of the depth.... – Typpex Dec 16 '14 at 10:32
0

The best solution I could come up with is this:

  def included_children(tag_id)
    depth = Tag.find(tag_id).leaves.last.depth
    eval(includes_string(depth))
  end

  def includes_string(depth)
    "{children: #{depth > 1 ? includes_string(depth - 1) : ':children'}}"
  end

I first find the depth of the deepest leave. This tells me how many times I must call :children. Then, I construct a string, based on the depth and evaluate the string to code, which I use in the includes method. Any improvement suggestions are welcomed, because this solution has several flaws and I find it totally unclean. Thank you.

Alexander Popov
  • 23,073
  • 19
  • 91
  • 130
  • depth = Tag.find(tag_id).leaves.last.depth I don't think it will guarantee you to get the deepest leaf. – Typpex Dec 16 '14 at 10:40
  • According to the doc: `tag.descendants` returns a scope of all children, childrens' children, etc., excluding self ordered by depth. and `tag.leaves` is scoped to all leaf nodes in self_and_descendants. – Alexander Popov Dec 16 '14 at 10:54
  • so you are trying to construct the string that you will put in your include so that your include will include all children and sub-children regardless of the depth ? – Typpex Dec 16 '14 at 15:58