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
Thanks a lot dude!
Hey, great article. Not sure the version of Rails you’re using when you put this together, but I was running into some issues with your post, and it turned out to be the use of single quotes in the controller.
params[‘user’].keys.each do |id|
should actually be
params[“user”].keys.each do |id|
Note the use of double quotes rather than single. Other than that it was extremely helpful!
Anthony, thanks for the great post. I have one question (I posted on StackOverflow and referenced your site). I am using Rails 4 and it requires defining permissible fields inside the controller. I am running into an error and was hoping you might be able to help? Thank you!
Hey again, Anthony. I think I found a solution for Rails 4 strong parameters. I wanted to share just in case your other readers may want to know, too. I modified the code to fit your model above.
def update_all
params['user'].keys.each do |id|
@user = User.find(id.to_i)
@user.update_attributes(user_params(id))
end
redirect_to(users_url)
end
This should be added to the bottom of the controller the update_all action is in:
def user_params(id)
params.require(:user).fetch(id).permit( :first_name, :last_name, :email )
end
THANKS A LOT!!! 😀
Now you can:
User.update(params['user'].keys, params['user'].values)
Magnificent, thanks so much Anthony. Really helped a newbie out. I’m learning to love Rails but things like params are still bending my mind a little. Thanks due also to Bob Walden & Roy McKenzie for sharing their tips too – very generous of them and I believe Roy’s tip is required to make this work with Rails 4.
Worked in the first try, thank you so much!
This was brilliant thanks!
The only problem I encountered is that I’m using “friendly-idized” resources, which means my ids are actually slugs. It caused trouble with Rails 4 strong parameters, but I fixed it by defining a new “params” method using the slugs:
@model.update_attributes(
params.require(
:model
)[key]
.permit(
…
)
Not too DRY but alright!
Thanks a lot,
Cheers
This article was of great inspiration for me,
especially this line:
fields_for "user[]", user do |user_fields|
you are saviour man. @ Roy McKenzie you tool
Thanks a lot dude! This really helped me out.