How to count comments like reddit (infinite nested model) ?
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.
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.
Your pseudo code works out of the box :)
First tried it with counter cache, that works. But your method seems more elegant. And it also works.