Create multipart forms in rails without using multiple routes. A controller mixin handles loading and saving data based on partials on a single route.

How to use

Add the gem to your gemfile

gem "acts_as_multipart_form"

Install the migration and config

rails generate acts_as_multipart_form:install
rake db:migrate

Add the mixin and methods to your controller

PeopleController < ApplicationController
  acts_as_multipart_form :name => :hire_form, :parts => [:personal, :job]

def hire_form
  @form_subject.multipart_form_controller_action = params[:multipart_form_part]

def personal

def personal_update
  return { valid: @form_subject.update_attributes(params[:person])}

def job

def job_update
  return { valid: @form_subject.update_attributes(params[:person])}

This example splits the fields for the Person model across two parts.

Each part needs two methods for the displayed form and data update steps. The data update steps need to return a hash with the key valid set to true or false. If part based validations are needed, the controller action must be set. It can be done in either the main action, hire_form, or only on the actions that need it.

Each form has a polymorphic relationship with a single record. This relationship is specified by the model key's value. This is how the form is loaded on the index page and the only association it has with the rest of the system. The record is stored in the @form_subject variable.

See MultipartFormInController for additional documentation.

Add the mixin to your model

multipart_formable :forms => [:hire_form]

This is currently only used for validations. If this is set, you can use a validation like this to only validate a field if the save is coming from a specific multipart form part.

validates_presence_of :name, :if => :hire_form_personal_update?

Add the route

resources :people do
  match 'edit/:id', to: 'people#hire_form', :via => [:get, :post]
  match 'new', to: 'people#hire_form', :via => [:get, :post] # optional

Setting up your form

These examples use formtastic but it is not required.


<%= render :partial => "multipart_form/breadcrumb" %>

<%= semantic_form_for @form_subject, :url => person_hire_form_path(:id =>, :multipart_form_part => @next_multipart_form_part), :html => { :method => :post } do |f| %>
  <%= f.semantic_errors %>
  <%= render :partial => @multipart_form_part, :locals => {:form => f}  %>
  <%= f.submit "Submit" %>
<% end %>


<%= form.inputs do %>
  <%= form.input :name %>
  <%= form.input :address %>
<% end %>


<%= form.inputs do %>
  <%= form.input :hire_date %>
  <%= form.input :salary %>
<% end %>

Adding form part links to the index page

On the controller

@people = Person.all
load_multipart_form_index_links(:hire_form, @people)

On the index view

<%= render "multipart_form/index_links",  :locals => {:form_subject => person } %>

Creates links to each form part for the given form subject. The load_multipart_form_index_links must be called to load the data for the links. The configuration options in the initializer can be used to change the output of the partial.

Future work

  • Fix the index links loading so that the user does not have to explicitly call a method when they are needed

  • Improve code quality of the mixin where it makes variables available to views

  • Find a fix for having to explicitly create the form subject in the code. The current system pushes us towards models that only hold the form subject id. The problem is that it is possible to use a multipart form without a specific form model. This leads to orphaned or blank records.

  • Increase test coverage for the multipart_form_handler

Contributing to acts_as_multipart_form

  • Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet

  • Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it

  • Fork the project

  • Start a feature/bugfix branch

  • Commit and push until you are happy with your contribution

  • Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.

  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

Copyright © 2011 Jeremiah Hemphill. See LICENSE.txt for further details.