1

I have a post controller that has many comments. The post model has a field called has_comments which is a boolean (so I can quickly select from the database only posts that have comments). To create a new comment for a post, I use the create action of my comments controller.

After I create the comment I need to update my post's has_comments field and set it to true.

I can update this field from the create action of my comments controller, but that doesn't seem right - I feel that I should really be using the post's update action, but I'm not sure if it's right to call it (via send?) from the create action of the comments controller.

Where should the code for updating the post be? Thank you!

Yuval Karmi
  • 26,277
  • 39
  • 124
  • 175

3 Answers3

2

Why clutter your database with another column when the interface is programmatic? Make has_comments a method on Post:

def has_comments
  comments.size > 0
end

Then implement a counter_cache as suggested to reduce the query load.

EDIT: Alternatively, after implementing a counter cache, you could use a named_scope on Post to retrieve all posts that have comments, using a single query, if that's the main goal:

class Comment
  belongs_to :post, :counter_cache => true
end

class Post
  named_scope :with_comments, {:conditions=>"comments_count > 0"}
end 

EDIT: You can also avoid the famous n+1 query problem by a judicious use of :include:

posts = Post.find(:all, :include => :comments)
Dave Sims
  • 5,060
  • 3
  • 24
  • 26
  • This will also avoid inconsistencies (e.g. in the case of a deleted comment), which you'd have to consider otherwise – averell Jun 02 '10 at 16:07
  • Agreed. Using a new column+callbacks is heavy and brittle for what should be a simple method. – Dave Sims Jun 02 '10 at 16:33
  • then how would you go about selecting ALL posts that have a comment from the database without fetching all the posts, then counting the comments for each one, and then filtering the result? This is a lot more memory intensive than to simply change a value in a column. is it not? – Yuval Karmi Jun 02 '10 at 19:43
  • If the main goal is to retrieve all posts with comments, then a named_scope probably makes more sense, something like I've added above. – Dave Sims Jun 02 '10 at 20:51
1

You could use before_save callback in your model.

Even better way would be to use built in :counter_cache option which automatically caches the number of comments for each post. (see http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001835 under options for belongs_to)

Slobodan Kovacevic
  • 6,848
  • 3
  • 29
  • 33
  • thanks. which model would I put the before_save callback in? I don't think the parent (i.e. post model) get saved when a comment is created/updated. But putting code that updated the post (parent) inside the comment model isn't very appropriate, is it? What do I have the post controller's update method for, then? – Yuval Karmi Jun 02 '10 at 05:37
  • You should put it in comments before_save. It's ok to update Post when new Comment is created. Basically the Rails is doing the same when you set :counter_cache for belongs_to (which is in Comment model). – Slobodan Kovacevic Jun 02 '10 at 07:28
  • 1
    Actually, `before_save` is not the correct callback. I would use after_save instead, and on the `Comment` model, as mentioned below. Because if something would go wrong at save-time, you have updated the `has_comments` field incorrectly. But, using the `:counter_cache` is a very good solution and will handle all the heavy work for you. – nathanvda Jun 02 '10 at 09:58
  • Counter cache is the most amazing solution... has it only worked. See my post about it: http://stackoverflow.com/questions/2955820/how-to-implement-a-counter-cache-in-rails – Yuval Karmi Jun 02 '10 at 19:44
1

use after_save in comment model

def after_save
  #this will set "has_comment" of the Specified Post to true if it's not true already
  self.post.update_attribute('has_comment', true) unless self.post.has_comment
end
Salil
  • 46,566
  • 21
  • 122
  • 156
  • Thank you. Is it appropriate to update a model's parent (the comment's post) through the child's model (comment model)? Why would I not send for the `update` method of the posts controller here? – Yuval Karmi Jun 02 '10 at 05:43
  • No update method will update all the data with data which already saved in database and set has_comment =true.also update_attributes of update method call all the callbacks if set in Post model whereas update_attribute doesn't, so it's increase your execution time. – Salil Jun 02 '10 at 05:47