Rails counter_cache problem
April 11th, 2008
I ran into a strange Ruby on Rails counter_cache problem today. Given the following example models:
class Poll < ActiveRecord::Base
has_many :poll_choices
has_many :poll_votes
end
class PollChoice < ActiveRecord::Base
belongs_to :poll
has_many :poll_votes
end
class PollVote < ActiveRecord::Base
belongs_to :poll, :counter_cache => :votes_count
belongs_to :poll_choice, :counter_cache => :votes_count
end
We want to ensure that the Poll maintains the total vote count. We also want the PollChoice to maintain the votes for that specific choice. In our controller we might be tempted to add a PollVote through either the Poll or PollChoice association with PollVote, but that’s where the problem appears.
It turns out that both of the following approaches will only update the votes_count for one or the other instance, but not both.
@poll.poll_votes.create(:poll_choice_id => @poll_choice.id)
OR
@poll_choice.poll_votes.create(:poll_id => @poll.id)
Instead, if we create the PollVote directly we will get the desired result of both the Poll and PollChoice having their votes_count updated appropriately.
PollVote.create(:poll_id => @poll.id, :poll_choice_id => @poll_choice.id)
Strange behavior or expected result?
Update: Another solution is to not assign using the ID, but instead assign using the object itself.
@poll.poll_votes.create(:poll_choice => @poll_choice)
OR
@poll_choice.poll_votes.create(:poll => @poll)
A quick test showed this worked as well. (Thanks Arya A)
April 15th, 2008
10:59 PM
Have you tried doing :poll => @poll instead of assigning the id directly? That’s just a guess though.
Also, is there any particular reason you are not doing it this way?
Poll has many PollChoices Poll has many PollVotes through PollChoices
Not that that solves your counter cache problem.
April 16th, 2008
07:20 PM
This is expected. Counter caches are never updated unless the object is created through the association. Makes sense, since there’s no hook to attach the update to, unless it’s through the association.
In a case like this, you should probably create a custom function, like Poll.vote(new_vote) which will manage the process of updating the counter caches and creating the new PollVote object.
April 19th, 2008
10:35 PM
@Arya A, making the assignment against the poll seems to work as well, good call. However, it still seems strange that it does not work given the ID.