Linus
Linus

Reputation: 2819

Eager load associations in nested set model

I'm using nested set to represent nested comments (1 discussion which has many comments, the comments can be replied to) and I would like to (if possible) to eager load the answers to comments.

At the moment I load all root nodes and iterate over them, if they have descendants I render them. However, if there are a lot of root nodes with answers, this triggers a lot of DB requests.

A comment has these columns: rgt, lft, parent_id

I tried to create a relationship like this:

class Comment < ActiveRecord::Base
  has_many :answers, -> (node) { where("lft > ? AND rgt < ?", node.lft, node.rgt) }, class_name: "Comment", foreign_key: :parent_id
end

This fetches the answers to a comment when called on a Comment instance. However, if I try to eager_load it (Discussion.comments.includes(:answers)) it blows up since node is nil.

Is it possible to eager load something like this?

Upvotes: 2

Views: 381

Answers (1)

Andrey Turkin
Andrey Turkin

Reputation: 719

I think, I've found a solution. If I see it right, your data model looks like this:

Discussion
----------
...

Comment
----------
discussion_id:int
parent_id:int
lft:int
rgt:int

Then AR model classes will be:

class Discussion < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :discussion
end

To eager load discussion (id == 1) with all its tree of comments and answers use:

d = Discussion.includes(:comments).find(1)

This gives a collection of all comments for given discussion in memory (d.comments).

Now we can manipulate this collection to get answers for any particular comment without additional DB queries. Just add this to Discussion class:

def comment_roots
  self.comments.select {|c| c.parent_id.nil? }
end

and this to Comment class:

  def answers
    self.discussion.comments.select {|c| c.parent_id = self.id }
  end

  def answers_tree
    self.discussion.comments.select {|c| self.lft < c.lft && c.rgt <   self.rgt }
  end

Example:

d.comment_roots.first.answers

Upvotes: 1

Related Questions