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)

3 Responses to “Rails counter_cache problem”

  1. 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.

     
  2. 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.

     
  3. @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.

     

Leave a Reply