Skip to main content

How to count comments like reddit (infinite nested model) ?

Ruby • Asked by Robert Hopman

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.


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.


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 24,647+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.