First, two simple
class Article < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :article end
Then, you often need to denormalize
Comment stuff into
Post or vice versa.
Here is the standard way to denormalize
last_comment_at on posts:
class Comment < ActiveRecord::Base belongs_to :article after_create :update_post_last_comment_at after_destroy :update_post_last_comment_at def update_post_last_comment_at post.update_attributes!(last_comment_at: post.comments.order('created_at').last.try(:created_at)) end end
But, there is a problem here: we define
Post denormalization callbacks into
Comment model. IMHO, this is the wrong place.
should be in
Post model in order to have less relationship between
Here is how to do it with
class Post < ActiveRecord::Base has_many :comments after_create :update_last_comment_at, source: :comments after_destroy :update_last_comment_at, source: :comments def update_last_comment_at update_attributes!(last_comment_at: comments.order('created_at').last.try(:created_at)) end end
You just have to specify
:source option to your callbacks, and that's all!
:source option must be an existing association name.
Note that association callbacks methods can take associated record as argument, above code can be:
class Post < ActiveRecord::Base has_many :comments after_create :update_last_comment_at def update_last_comment_at(comment) update_attributes!(last_comment_at: comment.created_at) end end
Association callbacks can also be defined as block:
class Post < ActiveRecord::Base has_many :comments after_destroy source: :comments do |post| post.decrement!(:comments_count) end end
Another solution is to use ActiveModel Observers, but for a better project comprehension, I really prefer placing denormalization directly into model. Observers are more designed for emails notifications, cache sweepers, etc.
Just add this into your
Then, just run
Executing test suite
This project is fully tested with Rspec 3.
bundle exec rake (after a