Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update counter_cache when updating a model?

I have a simple relationship:

class Item
  belongs_to :container, :counter_cache => true
end

class Container
  has_many :items
end

Let's say I have two containers. I create an item and associate it with the first container. The counter is increased.

Then I decide to associate it with the other container instead. How to update the items_count column of both containers?

I found a possible solution at http://railsforum.com/viewtopic.php?id=39285 .. however I'm a beginner and I don't understand it. Is this the only way to do it?

like image 751
Martin Petrov Avatar asked Apr 22 '11 17:04

Martin Petrov


People also ask

What's a counter cache?

Instead of counting the number of responses every time the articles are displayed, a counter cache keeps a separate response counter which is stored in each article's database row. The counter updates whenever a response is added or removed.

What is counter cache in Rails?

Counter caches are a built in Rails feature that allows you to track the associated records count by doing almost nothing.


1 Answers

It should work automatically. When you are updating items.container_id it will decreament old container's counter and increament new one. But if it isn't works - it is strange. You can try this callback:

class Item
  belongs_to :container, :counter_cache => true
  before_save :update_counters

  private
  def update_counters
    new_container = Container.find self.container_id
    old_container = Container.find self.container_id_was
    new_container.increament(:items_count)
    old_container.decreament(:items_count)
  end
end

UPD

To demonstrate native behavior:

container1 = Container.create :title => "container 1"
#=> #<Container title: "container 1", :items_count: nil>
container2 = Container.create :title => "container 2"
#=> #<Container title: "container 2", :items_count: nil>
item = container1.items.create(:title => "item 1")
Container.first
#=> #<Container title: "container 1", :items_count: 1>
Container.last
#=> #<Container title: "container 1", :items_count: nil>
item.container = Container.last
item.save
Container.first
#=> #<Container title: "container 1", :items_count: 0>
Container.last
#=> #<Container title: "container 1", :items_count: 1>

So it should work without any hacking. From the box.

like image 73
fl00r Avatar answered Nov 15 '22 00:11

fl00r