When Empty Isn’t Empty With Rails Active Record Collections
So I'm writing some Rails code, just playing around, and I run into a very strange situation. I've found a situation where Rails' Active Record code is calculating the results to
Here's my simple models:
and
Notice that in Query, I've added a
Here's the simple nested form. From the models, a Query has many Measures:
Here's what I was doing in my controller. Again, very simple:
Now, I wouldn't normally have the
Well, it turns out, that call to
Even though I added Measures via
When I rendered the
Sigh.
So, here's what we learned. Don't call
Here's the best way to handle this situation, completely avoiding the call to
empty?
incorrectly, even if I add elements to the collection. So I had a has_many
collection with elements inside of it, but calls to length
or empty
were returning 0
and true
.Here's my simple models:
class Query < ActiveRecord::Base
has_many :measures
validates_presence_of :cube_name
def measure_attributes=(attrs)
attrs.each {|a| measures.build(a)}
end
end
and
class Measure :query
has_one :condition, :as => :conditionable
end
Notice that in Query, I've added a
measure_attributes
method which automatically handles the construction of new Measure instances from the form parameters.Here's the simple nested form. From the models, a Query has many Measures:
<% form_for :query, :url => {:action => :new} do |f| %>
Cube Name: <%= f.text_field :cube_name %>
<% @query.measures.each do |measure| %>
<div>
<% fields_for \"query[measure_attributes][]\", measure do |mf| %>
Measure Name: <%= mf.text_field :name %>
<% end %>
</div>
<% end %>
<%= submit_tag %>
<% end %>
Here's what I was doing in my controller. Again, very simple:
class QueriesController < ApplicationController
def new
@query = Query.new(params[:query])
@query.measures.build if @query.measures.empty?
end
end
Now, I wouldn't normally have the
new
method handle it this way (I would have both a create
and a new
), but this was test code. My check against empty?
was basically saying "if this is the first time I've hit this method, throw in a blank Measure so the form will show at least show one Measure."Well, it turns out, that call to
empty?
is calculated by checking the database! Here's the actual query:
SELECT count(*) AS count_all FROM measures WHERE (measures.query_id = NULL)
Even though I added Measures via
measure_attributes
, ActiveRecord here doesn't seem to acknowledge that at this point.When I rendered the
new
template after the form is submitted, the loop in the RHTML (@query.measures.each
) doesn't iterate, because it thinks there's no measures.Sigh.
So, here's what we learned. Don't call
length
or empty?
on an Active Record managed has_many
collection unless you understand how it's calculating the size of the contents (via the database). (Also, I still don't quite understand when it goes to the database and when it uses the actual items I've manually added.)Here's the best way to handle this situation, completely avoiding the call to
empty?
:
class QueriesController 'new'
end
end