Rails, Nested Forms and collection_select

I spent a bit of time on this tonight and thought I’d post about it.

I have a Property model that has a has_one relationship with an Address model. I want to be able to CRUD these two objects as one single entity, however. Luckily, nested forms were recently introduced into Rails 2.3.2 and they seemed like they’d simplify the whole process. Which they did once I put everything together. First, my models:

class Property < ActiveRecord::Base
  def initialize(attributes=nil)
    super
    #we override the initialize method and ensure we always have 
    #an address instance created. This makes sure we don't get any
    #nil reference errors in our forms
    self.build_address unless self.address
  end
 
  #accepts_nested_attributes_for enables the property's address object
  #to be updated at the same time as the property object
  accepts_nested_attributes_for :address, :allow_destroy => true
end
 
class Address < ActiveRecord::Base
  has_one :property
  # the Address model has a :city string column that will get populated from
  # a drop-down list in our view
end

This is the basic stuff that any tutorial on nested forms in Rails will tell you about, with the exception of creating a default instance of the address in the property constructor. This is most likely specific to the has_one case. Most of the examples I have seen deal only with has_many, where behind the scenes rails creates an empty list and we’ll never end up with nil reference errors in our view. This was the first issue I had to solve. Now let’s move on to our new.html.erb view:

<% form_for @property do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :name, "Title" %>
    <%= f.text_field :name %>
  </p>
  <p>
    <% f.fields_for :address do |af| %>
      <p>
        <%= af.label :address_one, "Address One" %>
        <%= af.text_field :address_one %>
      </p>
      <p>
        <%= af.label :city, "City" %>
        <%= af.collection_select(:city, City.all, :name, :name) %>
      </p>
    <% end %>
  </p>
  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

The trick here is to make sure you qualify the collection_select for cities with the specific fields_for instance. If you don’t, the form will render and appear to be ok until you post to the create action or view the generated source:

<p>
<%= af.label :city, "City" %>
notice how I've removed the af. qualifier on collection select, and added in a
new parameter :address. This is how most tutorials show you how to use it
The problem with this is that the rendered html does not generate properly for
our nested form scenario
<%=collection_select(:address, :city, City.all, :name, :name) %>
</p>

Once you qualify the collection_select helper properly, you should get generated html that looks like this:

<label for="property_address_attributes_city">City</label>
        <select id="property_address_attributes_city" name="property[address_attributes][city]"><option value="Lethbridge">Lethbridge</option></select>

Now when you post the form, Rails nested form magic just works. My controller looks just as you’d expect (ie; no different than the non-nested form scenario)

  def create
    @property = Property.new(params[:property])
    respond_to do |format|
      if @property.save
        flash[:notice] = 'Property was successfully created.'
        format.html { redirect_to(@property) }
      else
        format.html { render :action => "new" }
      end
    end
  end

Hopefully I’ve explained this properly. Feel free to ask questions in the comments if I haven’t.

2 thoughts on “Rails, Nested Forms and collection_select

  1. Anoon

    You should get rid of the frame at the top of this page. I don’t need to know where your resume is all the time, if at all. Nice tips though :) .