Creating multiple instances of a model from a single form in Rails
August 29, 2020
I recently wanted to implement a feature where I could invite a list of users from a single form to a platform. It wasn’t quite as straightforward as creating single model instances and took a little bit more rejiggering to make it work.
First, we need a form to create a single user:
<div class="form-group">
<div class="row">
<div class="col-md-4 form-group">
<%= form.label :first_name, "First Name" %>
<%= form.text_field :first_name %>
</div>
<div class="col-md-4 form-group">
<%= form.label :last_name, "Last Name" %>
<%= form.text_field :last_name %>
</div>
<div class="col-md-4 form-group">
<%= form.label :email, "Email" %>
<%= form.text_field :email %>
</div>
</div>
</div>
Nothing terribly exciting here, just a regular ol’ HTML form to collect the first_name
, last_name
, and email
of
a potential new user. This will be the partial we use (we’ll call the file _users_input_row.html.erb
) on our page.
Next, I wanted to create a dynamic form that had a single user input by default, but a button that would add additional rows for each additional user I intended to invite.
<%= form_with url: users_path, method: 'post' do |f| %>
<h5 class="modal-title">
Invite users!
</h5>
<%= fields_for 'users[]', User.new do |form| %> <%= render 'user_input_row', form: form %>
<template> <%= render 'user_input_row', form: form %>
</template>
<div class="users">
<button onclick="addUser()">Invite Another User</button>
</div>
<% end %>
<button class="btn">Send Request</button>
<% end %>
There are couple of unique things happening here:
fields_for 'users[]'
This collects the data from the form inputs into an array called “users”. Without the []
, we would not be able to
send multiple users at once.
<template>
The template tag will keep some HTML on “standby” until a user clicks the “Invite Another User” button.
The code inside of the template tag remains hidden until some Javascript (the addUser
function in our case) is
executed to display it. If you’re not familiar with template elements, you can read more at the
MDN Docs or see an example
here.
Now that we’ve set up our view layer, it’s time to wire up the controllers. Since we set up the fields as an array of users, we can see an array of User hashes coming into our controller:
// In the rails server...
...
Parameters: {... "users"=>[{"first_name"=>"Astrid", "last_name"=>"Leung", "email"=>"astrid@oxford.com"}], ...}
...
That means we can access our array of users in the controller by calling params[:users]
. With Rails 4+, we will also
need to explicitly permit each of the user attributes, which we can do when iterating over the array. When that’s all
said and done, we can invite each user to the platform 🎉
class UsersController < ApplicationController
def create
params[:users].each do |params|
User.invite!(user_params(params))
end
redirect_to users_path
end
private
def user_params(params)
params.permit(:first_name, :last_name, :email)
end
end