Displaying multiple form fields for Nested Models

Posted by Daniel on 06/21/2009

accepts_nested_attributes_for

Rails 2.3 brought around the ability to seamlessly nest models in forms. This works wonderfully, and saves a bunch of time, but what if you want to have multiple instances of said nested models? It is easier than you would think. Consider the following:

Models:
class Order < ActiveRecord::Base
  has_many :deliveries
  accepts_nested_attributes_for :deliveries, :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
end

class Delivery < ActiveRecord::Base
  belongs_to :order
  validates_presence_of :delivery_date
end
Order Controller:
class OrdersController < ApplicationController
  
  def new
    @order = Order.new
    @flavors = Flavor.find(:all)
    3.times {@order.deliveries.build}
  end
  
  def create
    @order = Order.new(params[:order])
    if @order.save
      flash[:notice] = "Successfully created order."
      redirect_to @order
    else
      render :action => 'new'
    end
  end

end
The form view:
<% form_for @order do |f| %>
  <%= f.error_messages %>
  <p>
    <%= f.label :first_name %><br />
    <%= f.text_field :first_name %>
  </p>
  <p>
    <%= f.label :last_name %><br />
    <%= f.text_field :last_name %>
  </p>
  <p>
    <%= f.label :address %><br />
    <%= f.text_field :address %>
  </p>
  <p>
    <%= f.label :city %><br />
    <%= f.text_field :city %>
  </p>
  <p>
    <%= f.label :state %><br />
    <%= f.text_field :state %>
  </p>
  <p>
    <%= f.label :zip %><br />
    <%= f.text_field :zip %>
  </p>
  <div id="delivery">
      <% f.fields_for :deliveries do |delivery| %>
        <div class="deliveries">
            <p>
                <%= delivery.label :delivery_date %><br />
                <%= delivery.datetime_select :delivery_date %>
            </p>
            <p>
                <%= delivery.label :quantity %><br />
                <%= delivery.text_field :quantity %>
            </p>
        </div>
      <% end %>
  </div>
  <p><%= f.submit "Submit" %></p>
<% end %>

A look at the generated HTML:

Great, but what if I want the deliveries dynamically?

To my knowledge, there isn't an easy way to add nested attribute fields in a manner other than handling the form fields through javascript. This seems difficult. Instead of choosing to do this, I've decided to build the form using Rails, then hiding and then showing the the form fields dynamically. Consider the following changes to the code:

The javascript, written using jQuery:
$(document).ready(function() {
    //hide all of the delivery fields
    $('.deliveries').hide();
    //show the first delivery
    $('.deliveries:first').show();
    //find any form elements that have errors, and show their delivery parent
    $('.fieldWithErrors').parents(".deliveries").show();
    //add a link to add deliveries
    $('#delivery').append('<a href="javascript: void(0)" id="addDelivery">Add Deliveries</a>');
    //start monitoring the link
    $('#addDelivery').click(function() {
        //if there are delivery fields hidden, show the first one
        if ($('.deliveries:hidden').length > 0) {
            $('.deliveries:hidden:first').slideDown();
        };
        //if there are no more hidden delivery fields, remove the last link
        if ($('.deliveries:hidden').length < 1) {
            $('#addDelivery').remove();
        }
    })
})

Find the demo app here: http://github.com/danielwestendorf/nested_attributes_demo/tree/master

Comments.

1. Dagnan wrote:

Hi! This post can be considered as an easy way to add fields dynamically (with jQuery): http://railsforum.com/viewtopic.php?id=28447


Name: (required)

Email: (not shown or published)

Website Url:


(Use Markdown Syntax, <pre> your code </pre>)