## Building a Multi-Model Form
Another feature of your average blog is the ability to tag posts. To implement this feature your application needs to interact with more than one model on a single form. Rails offers support for nested forms.
To demonstrate this, we will add support for giving each post multiple tags, right in the form where you create the post. First, create a new model to hold the tags:
$ rails generate model tag name:string post:references
Again, run the migration to create the database table:
$ rake db:migrate
Next, edit the `post.rb` file to create the other side of the association, and to tell Rails (via the `accepts_nested_attributes_for` macro) that you intend to edit tags via posts:
@@@ ruby
class Post < ActiveRecord::Base
validates :name, :presence => true
validates :title, :presence => true,
:length => { :minimum => 5 }
has_many :comments, :dependent => :destroy
has_many :tags
accepts_nested_attributes_for :tags, :allow_destroy => :true,
:reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
end
@@@
The `:allow_destroy` option on the nested attribute declaration tells Rails to display a “remove” checkbox on the view that you’ll build shortly. The `:reject_if` option prevents saving new tags that do not have any attributes filled in.
We will modify `views/posts/_form.html.erb` to render a partial to make a tag:
@@@ html
<% @post.tags.build %>
<%= form_for(@post) do |post_form| %>
<% if @post.errors.any? %>
<%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:
<% @post.errors.full_messages.each do |msg| %>
- <%= msg %>
<% end %>
<% end %>
<%= post_form.label :name %>
<%= post_form.text_field :name %>
<%= post_form.label :title %>
<%= post_form.text_field :title %>
<%= post_form.label :content %>
<%= post_form.text_area :content %>
Tags
<%= render :partial => 'tags/form',
:locals => {:form => post_form} %>
<%= post_form.submit %>
<% end %>
@@@
Note that we have changed the `f in form_for(@post) do |f|` to `post_form` to make it easier to understand what is going on.
This example shows another option of the render helper, being able to pass in local variables, in this case, we want the local variable `form` in the partial to refer to the `post_form` object.
We also add a `@post.tags.build` at the top of this form, this is to make sure there is a new tag ready to have it’s name filled in by the user. If you do not build the new tag, then the form will not appear as there is no new Tag object ready to create.
Now create the folder `app/views/tags` and make a file in there called `_form.html.erb` which contains the form for the tag:
@@@ html
<%= form.fields_for :tags do |tag_form| %>
<%= tag_form.label :name, 'Tag:' %>
<%= tag_form.text_field :name %>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<%= tag_form.label :_destroy, 'Remove:' %>
<%= tag_form.check_box :_destroy %>
<% end %>
<% end %>
@@@
Finally, we will edit the `app/views/posts/show.html.erb` template to show our tags.
@@@ html
<%= notice %>
Name:
<%= @post.name %>
Title:
<%= @post.title %>
Content:
<%= @post.content %>
Tags:
<%= @post.tags.map { |t| t.name }.join(", ") %>
Comments
<%= render :partial => "comments/comment",
:collection => @post.comments %>
Add a comment:
<%= render "comments/form" %>
<%= link_to 'Edit Post', edit_post_path(@post) %> |
<%= link_to 'Back to Posts', posts_path %> |
@@@
With these changes in place, you’ll find that you can edit a post and its tags directly on the same view.
However, that method call `@post.tags.map { |t| t.name }.join(", ")` is awkward, we could handle this by making a helper method.