Tuesday, February 23, 2010

How do I make a form that handles multiple models?


This question is asked quite frequently. Scaffolding and simple Rails tutorials show you how to create one model per form. Unfortunately, sometimes this is not sufficient.

In this article we will make one form that creates two models: Project and Task (where a project has many tasks). What I would like to do is have the first task be created the same time the project is created (in the same form). Here's the new action in the controller:

# in the projects_controller.rb
def new
@project = Project.new
@task = Task.new
end

Here we are creating the models which will contain the default values - we can set any default values in this action and they will be reflected in the form.

Next we make the form containing fields for these two models. Of course you could add as many fields as you need to for each model:

# in the projects/new.rhtml
New Project

<%= error_messages_for :project %> <%= error_messages_for :task %>

<%= start_form_tag :action => 'create' %> Project Name: <%= text_field :project, :name %>

First Task: <%= text_field :task, :name %>

<%= submit_tag 'Create' %>

<%= end_form_tag %>

Pretty simple. Now comes the tricky part, the create action. First the code, then I'll explain it.

def create
  @project = Project.new(params[:project])
  @task = @project.tasks.build(params[:task])
  if @project.save
    redirect_to :action => 'index'
  else
    render :action => 'new'
  end
end
The @project instance variable is set normally, but what's up with how the task is created? We use a fancy method to help us out here. The @project.tasks.build method creates a task (just like Task.new) and adds it to the @project at the same time. Pretty cool huh?

Next we call @project.save. This method is actually doing a lot. First it checks if the project and all of its tasks are valid. If something is invalid it returns false, otherwise it saves both project and task and returns true. This save method is very convenient and handles everything for us automatically.

Why do we bother setting the @task instance variable you may wonder? We need it in case the validation fails and we render the 'new' form again.

The creation of the project along with the task is finished, but how about editing? Normally, with this kind of set up, I use separate forms for editing. In this case I would list the tasks on the project 'show' page with an edit link next to each one.

0 comments:

Post a Comment

post answers

 

Copyright 2008 All Rights Reserved Revolution Two Church theme by Brian Gardner Converted into Blogger Template by Bloganol dot com