Regina Lee

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

Written By Regina Lee

Currently residing in Brooklyn, NY 🗽. Enjoying hobbies that begin with the letter "c" - climbing, ceramics, and cooking