Reputation: 2819
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
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