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

Ask A Question

Notifications

You’re not receiving notifications from this thread.

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

Robert Hopman asked in Ruby

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.

Join the discussion

Want to stay up-to-date with Ruby on Rails?

Join 34,885+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    logo Created with Sketch.

    Ruby on Rails tutorials, guides, and screencasts for web developers learning Ruby, Rails, Javascript, Turbolinks, Stimulus.js, Vue.js, and more. Icons by Icons8

    © 2020 GoRails, LLC. All rights reserved.