3

I am using a counter_cache to let MySQL do some of the bookkeeping for me:

class Container
  has_many :items
end

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

Now, if I do this:

container = Container.find(57)
item = Item.new
item.container = container
item.save

in the SQL log there will be an INSERT followed by something like:

UPDATE `containers` SET `items_count` = COALESCE(`items_count`, 0) + 1
    WHERE `containers`.`id` = 57

which is what I expected it to do. However, the container[:items_count] will be stale!

...unless I container.reload to pick up the updated value. Which in my mind sort of defeats part of the purpose of using the :counter_cache in favor of a custom built one, especially since I may not actually want a reload before I try to access the items_count attribute. (My models are pretty code-heavy because of the nature of the domain logic, so I sometimes have to save and create multiple things in one controller call.)

I understand I can tinker with callbacks myself but this seems to me a fairly basic expectation of the simple feature. Again, if I have to write additional code to make it fully work, it might as well be easier to implement a custom counter.

What am I doing/assuming wrong?

sehnsucht
  • 55
  • 5

2 Answers2

3

You shouldn't expect the value of the items counter to be automatically updated in your Container instance. Keep in mind that most Rails applications operate in a multiple process (and often multiple server) configuration. If one process adds an Item that is associated with a Container instance on a different process, the value of the counter will become stale.

star-d
  • 31
  • 3
  • 1
    The burden of keeping data in a consistent state is on the model. That's why we have transactions at different isolation levels and optimistic concurrency if the congestion is high enough to warrant it. That somebody may change the data elsewhere doesn't only apply to counter_cache, it's a universal problem - any two `SELECT`s in a row may produce inconsistent data if the second table has been updated in between. In this regard I don't see why a race condition resulting from someone else adding an Item to my Container is any different from them changing `container.items` before I access it. – sehnsucht Nov 26 '12 at 04:27
1

You haven't actually made the container aware of the item though, which is effectively what you do when you container.reload! it. Have you tried:

container = Container.find(57)
item = container.items.create()

This will make the item in the context of the container and build those associations for you.

toxaq
  • 6,745
  • 3
  • 46
  • 56
  • That works. I just find it inconsistent that it works one way and not the other. – sehnsucht Aug 26 '13 at 12:57
  • For me this this do not works! Only after reload counter is updated. I tried various style `create`, `create!`, `build.save!`. – pablo Oct 15 '13 at 22:05