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?
For one thing, I don't think it's appropriate to write something like l.storables << Storable.create
.
By writing this, two things happens:
Storable.create
creates a new Storable object with location_id
nil
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.