stefankroes / ancestry

Organise ActiveRecord model into a tree structure
MIT License
3.73k stars 460 forks source link

Eager loading with Ancestry ? #80

Open Sephi-Chan opened 13 years ago

Sephi-Chan commented 13 years ago

Hi,

I didn't see anything in the documentation or sources about eager loading.

As I plan to use Ancestry to build some kind of forum so it's an important feature to me : being able to eager load the author of each message, and so on.

How do you deal with that ?

Thanks! ;)

andrejj commented 13 years ago

Hi, I think it works fine but I found an issue (and a workaround) regrding the arrange method which builds a hash - tree of the results.

:node - an element of a tree (Node has_ancestry) :styles - has_and_belongs_to_many association I want to eager load :position - ordering of children

First try. Should work but it didn't. tree= node.subtree.arrange(:include=>[:styles], :order=>[:position]) The query which loads associated styles is performed, but all the all the associated styles evaluate to empty arrays. node.styles -> []

After some digging ancestry's source I managed to make it work: tree = Node.arrange_nodes(node.subtree.all(:include=>[:styles],:order => "(case when ancestry is null then 0 else 1 end), ancestry, position"))

In conclusion it can be done.

nontone commented 12 years ago

what about doing this with same model.

ie Comment has_ancestry and can have other comments as children? how would you go about doing this?

siraz-provectus commented 10 years ago

class Comment < ActiveRecord::Base has_ancestry has_many :comment_children, class_name: 'Comment', foreign_key: 'ancestry' end

Comment.roots.eager_load(:comment_children)

stefankroes commented 10 years ago

All ancestry methods like subtree or descendants are just where clauses. You can chain any includes on joins call on them.

AlexStanovsky commented 9 years ago

There is a solution for eager loading in this Gem: https://github.com/Natural-Intelligence/index_tree

Which is not depended on the hight of the tree. It should work with Ancestry Gem

marnen commented 9 years ago

@stefankroes:

All ancestry methods like subtree or descendants are just where clauses.

That's irrelevant, isn't it? Rails eager loading needs an association name, not a where clause, and Ancestry doesn't appear to implement children as an association.

tommyalvarez commented 6 years ago

+1. I need the possibility to eager load subtree, i need to do something like this recursively through all the descendants:

purchase_order_consumable.children.each do |child|
  auto_assign_proc.call(child)
end

And the N+1 queries is killing me.

@siraz-provectus how is that suppose to work? column 'ancestry' is a string separated by '/' for each children, if rails tries to join by that column it will fail.

leoc commented 5 years ago

Has anyone found an elegant solution yet?

I suppose when the ancestry column is of type Array at least in postgres one could adjust the join clause for such an association to eager_load?

fikrikarim commented 5 years ago

Still waiting for answer too :)

gabrielmarques0 commented 4 years ago

Any updates on this?

wilson29thid commented 4 years ago

Hi all, just wanted to share a solution that worked for me. Can't promise it will fit your use case but it did mine quite well. I have a list of hierarchical units, each with their own members (via a join table called assignments). I wanted to print a roster of the nested units with their members.

    units = Unit.find_by_id(1)
                .subtree
                .active

    @unit_tree = units.arrange(order: :abbr)

    @assignments = Assignment.active
                             .includes(user: :rank)
                             .where(unit_id: units.ids)
                             .group_by(&:unit_id)

This completely avoids the n+1 problem for my use case. I then render it in the template by checking @assignments[unit.id]. Note that my template code has too much logic in it; I know I need to move it to a helper or something but I'm in early stages of development at the moment.

<% def draw_tree(node) %>
  <ul>
    <% node.each do |parent, children| %>
      <li>
        <strong><%= parent.abbr %></strong>
        <% if @assignments[parent.id] %>
          <ul>
            <% @assignments[parent.id].each do |assignment| %>
              <li><%= assignment.user.short_name %></li>
            <% end %>
          </ul>
        <% end %>

        <% if children.any? %>
            <% draw_tree(children) %>
        <% end %>
      </li>
    <% end %>
  </ul>
<% end %>

<% draw_tree(@unit_tree) %>

Anyway, just thought I'd share in case it's useful to anyone else.

katorres02 commented 2 years ago

Hi all, I did a workaround for this using the gem 'batch-loader', '~> 2.0.1'. In my case I have a graphql api and a model called location with an implementation of the ancestry gem

class Location < ActiveRecord::Base
  has_ancestry
end

module Types
  class LocationType < Types::BaseObject
    field :id, ID, null: false
    field :children, [Types::LocationType], null: false

    def children
      ancestry_id = object.ancestry.nil? ? object.id : (object.ancestry.to_s + '/' + object.id.to_s)
      BatchLoader::GraphQL.for(ancestry_id).batch(default_value: []) do |ancestry_ids, loader|
        Location.where(ancestry: ancestry_ids).order('sequence_key ASC').each do |location|
          loader.call(location.ancestry) { |locations| locations << location }
        end
      end
    end
end