I recently had a requirement to create a single form with data from more than one record. This is a simple request, but I had never done it before in Rails.
After experimenting for a while (and reading quite a few forum posts and StackOverflow answers) I came up with a working solution. Hopefully this will save someone a little time in the future.
First, let’s create a simple Rails app for managing users. Each user will have a first name, last name, and e-mail address. I’m using scaffolding to generate the code.
rails new multi_edit
cd multi_edit
bundle install
rails g scaffold User first_name:string last_name:string email:string
rake db:migrate
Next, add the new routes for editing all users. I tried to stick to the RESTful convention with these. I’m just using the word ‘all’ in place of the :id.
match 'users/all/edit' => 'users#edit_all', :as => :edit_all, :via => :get
match 'users/all' => 'users#update_all', :as => :update_all, :via => :put
Now, let’s add the edit_all
method to our UsersController
to get started. The only thing it needs to do is get all users.
def edit_all
@users = User.all
end
Next, we build the form for editing all users.
<%= form_for :user, :url => update_all_path, :html => { :method => :put } do %>
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>E-Mail</th>
</tr>
<% @users.each do |user| %>
<%= fields_for "user[]", user do |user_fields| %>
<tr>
<td><%= user_fields.text_field :first_name %></td>
<td><%= user_fields.text_field :last_name %></td>
<td><%= user_fields.email_field :email %></td>
</tr>
<% end %>
<% end %>
</table>
<div class="actions">
<%= submit_tag %>
</div>
<% end %>
The interesting part of this code starts around line 9. As expected, we iterate over the users with @users.each
.
The next line tells Rails to name the fields for each user with array notation. For example, user_fields.text_field :first_name
will output a text field named user[1][first_name]
.
The params
hash will include a ‘user’ key that contains a hash of information for each user. The key for each of these hashes will be the user id.
Now that we know what the params
will look like, it’s pretty straight-forward to write the update_all
method.
def update_all
params['user'].keys.each do |id|
@user = User.find(id.to_i)
@user.update_attributes(params['user'][id])
end
redirect_to(users_url)
end
We iterate over each key in params['user']
, then find and update the user associated with that id. I am leaving error checking as an exercise for the reader…
The complete source code for this simple application is on my GitHub page at https://github.com/anthonylewis/multi_edit