Provides a way to define callbacks of one ActiveRecord model into an associated one.
First, two simple Article
and Comment
models:
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. Post
denormalization
should be in Post
model in order to have less relationship between
models.
Here is how to do it with association_callabacks
:
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!
Note that :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 Gemfile
:
gem 'association_callabacks'
Then, just run bundle install
.
This project is fully tested with Rspec 3.
Just run bundle exec rake
(after a bundle install
).