I very careful about calling something a bug, so there's of course the
possibility that I'm simply doing something wrong, but I've
encountered some strange behavior:
I have a model with a has_many association, let's call them customer
and orders.
When I call customer.orders.build on an existing customer that doesn't
have any orders yet, and then want to let a partial iterate over the
orders to render each one, nothing happens.
When, however, I call customer.orders.length or customer.orders.each
{} right after build, it works.
So if I have a customer that doesn't have any orders yet and I do
this:
Note that target is a getter for @target, load_target is not involved.
Looking further down, render_partial_collection has this line
return " " if collection.empty?
empty? is equivalent to size.zero? for association collections and
#size does some stuff, so I guess we are getting close. The branch we
enter does take into account new records and performs a count_records
in case there was something in the database, the result is correct. In
fact the flow execution goes on, but *after* that line
collection.empty? is true (so the #map call afterwards does nothing
and no template gets rendered). My conjecture is that the call to
#size resets the proxy somehow.
I may complete the walkthrough later but that's what I've got by now.
Next target is count_records.
Note that target is a getter for @target, load_target is not involved.
Looking further down, render_partial_collection has this line
return " " if collection.empty?
empty? is equivalent to size.zero? for association collections and
#size does some stuff, so I guess we are getting close. The branch we
enter does take into account new records and performs a count_records
in case there was something in the database, the result is correct. In
fact the flow execution goes on, but *after* that line
collection.empty? is true (so the #map call afterwards does nothing
and no template gets rendered). My conjecture is that the call to
#size resets the proxy somehow.
Size looks like this:
def size
if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
@target.size
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?
(Array)
unsaved_records = Array(@target.detect { |r| r.new_record? })
unsaved_records.size + count_records
else
count_records
end
end
Unrelated to this and maybe I'm hallucinating, but isn't that second
branch wrong ? should it not be
unsaved_records = @target.select { |r| r.new_record? })
(since detect just returns the first element for which the block
returns true, or nil)
If we look inside count_records we can see that your conjecture is
correct. count_records gets the count (reads the counter cache or
executes the relevant sql query) and then it does
@target = and loaded if count == 0
My reasoning is that this is an optimisatiohn: if you do foos.count
and you get back 0 then you can assume that foos is indeed an empty
array - there is no need to actually load the foos array. Of course in
this case this optimisation is screwing things up.
The culprit was precisely the detect/select stuff you pointed out
(well, I guess your example was indeed hand made to show a consequence
of the detect call.)