Question

Given I have the following models:

class Location < Active::Record
  has_many :storables, foreign_key: :bin_id
  # ...
end

class Storable < Active::Record
  belongs_to :bin, class_name: :Location, counter_cache: true
  # ...
end

When I run the following spec, the counter_cache doesn't increment correctly. Method #1 and #2 work as expected, but NOT #3. What gives?

describe "location storables" do
  specify "adding a storable increments the counter cache" do
    l = Location.create
    l.storables_count.should == 0 #=> PASSES

    # method 1
    s = Storable.create(bin: l)
    l.reload
    l.storables_count.should == 1 #=> PASSES

    # method 2
    l.storables.create
    l.reload
    l.storables_count.should == 2 #=> PASSES

    # method 3
    l.storables << Storable.create
    l.reload
    l.storables_count.should == 3 #=> FAILS, got 2 not 3
  end
end

I'm really confused by the counter_cache half working. I can't spot a configuration problem either.

Using Rails 3.2.12 on this project.

UPDATE

Upgrading to rails 4 didn't help. Also, if I change method #3 to the following, the test passes:

# method 3
l.storables << Storable.create
puts "proxy    : #{l.storables.count}" #=> 3
puts "relation : #{Storable.count}"    #=> 3
puts "cache    : #{l.storables_count}"    #=> 2

Location.reset_counters(l.id, :storables) # corrects cache

l.reload
l.storables_count.should == 3 #=> PASSES

Why isn't this happening automatically?

Was it helpful?

Solution

For one thing, I don't think it's appropriate to write something like l.storables << Storable.create.

By writing this, two things happens:

  1. Storable.create creates a new Storable object with location_id nil

  2. l.storables << updates the object created, sets location_id to l.id, and somehow forgets to update the counter cache.

It might be ActiveRecord's fault, since it should have been smarter, but you've actually executed two SQL(insert into storable & update storable set location_id = something) simply to insert a new storable record. Anyway it's a bad idea, and if you have a foreign key constraint on location_id, the first insert would even fail.

So use l.storables << Storable.new instead

PS: With l.storables << Storable.create, since the return value of Storable.create is not a new record, it's a bit hard for l to decide what to do. In some cases, it needs to increment its own counter cache, in other cases, it needs to increment its own counter cache and decrement someone else's counter cache, or it might need to do nothing.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top