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 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
2 comments

Popular posts from this blog