Rails Bug With Has Many Collections Empty? and Build
After my long rant about problems in Ruby on Rails has_many collections and empty? calls, we spent some more time and really condensed the problem. I can explain this problem much more clearly now.
Let's take the following code:
What do you think will be printed out when we
Well, if you're like me, you'd think it would return an array with one Bar in it. And, if you were like me, you'd be wrong.
Turns out that the call to
Because of this caching, our last line of code here is printing out an empty array. This effectively deletes the original Bar we added via
This has to be a bug. In fact, I'm filing a bug at Rails' Trac system.
How do you work around this? Just change your usage of
The above code will work just fine!
Many thanks to Wenyi for researching this with me.
Let's take the following code:
f = Foo.new
f.bars.build
f.bars.empty?
pp f.bars
What do you think will be printed out when we
pp f.bars
?Well, if you're like me, you'd think it would return an array with one Bar in it. And, if you were like me, you'd be wrong.
Turns out that the call to
f.bars.empty?
doesn't realize you've ever added a new Bar to the collection of bars. Therefore, it will go into the database and do a select count(*)
to determine the size of the collection. It will return zero, because at this point you haven't saved anything into the database. Not only is going to the database to determine the size of the collection wrong (in the line above, I've added a Bar though build
so empty?
should know there's at least one), but empty?
in the process is caching the empty collection!Because of this caching, our last line of code here is printing out an empty array. This effectively deletes the original Bar we added via
build
.This has to be a bug. In fact, I'm filing a bug at Rails' Trac system.
How do you work around this? Just change your usage of
empty?
to length.zero?
:
f = Foo.new
f.bars.build
f.bars.length.zero?
pp f.bars
The above code will work just fine!
Many thanks to Wenyi for researching this with me.