How to count comments like reddit (infinite nested model) ?
Hey Chris,
I have an item
model which gets submitted. With
has_many :comments, as: :commentable, dependent: :destroy
then in the comment
model I have
belongs_to :commentable, polymorphic: true
has_many :comments, as: :commentable
I probably have to do something with recursion
(as discussed here: https://stackoverflow.com/a/35248329/6430382 )
Someone said: https://gorails.com/episodes/counter-caches but that is only 1 level deep as far as I can see.
Please advise
Hey Robert,
That's a great question. The answer to this isn't super obvious. When you'd normally just do a COUNT, you can't do that without counting each of the related ones and then sum all those up. The more nested things get, the slower this becomes.
One solution I would suggest is to build your own counter cache here manually. Instead of using Rails for it, you can add a comments_count
integer field to your model and then add callbacks to increment and decrement this number on create and destroy. That will work similar to the Rails counter cache like I mentioned, but you'll loop through till you find the parent object to increment.
For example, with nested comments, you'd have model associations that look like this
Post -> Comment -> Comment -> Comment
When you create that last comment, you need to loop up through the parents until you get to the Post. This won't be a Comment model, so we can check the commentable_type
column to see if we've gone up the stack far enough to find that model.
This is just psuedo code, so you'll probably have to change it to be make it work, but roughly the idea would be this:
class Comment
belongs_to :commentable
after_create :increment_count
after_destroy :decrement_count
def increment_count
parent = commentable
# Keep looping until we get to the parent which isn't a Comment model
while parent.is_a? Comment
parent = parent.commentable
end
parent.increment! :comments_count
end
def decrement_count
parent = commentable
# Keep looping until we get to the parent which isn't a Comment model
while parent.is_a? Comment
parent = parent.commentable
end
parent.decrement! :comments_count
end
end
This will work best on a new app without commenst already because it won't back-fill existing counts. For that, you'd probably want to do something like the "deep_count" method mentioned on SO there, but it only needs to happen once after your counter cache column was added.