Ruby on Rails Forms. Let’s Build a Sign-Up Form!

Rails has a lot of built-in helpers that assist in generating HTML markup. HTML forms can have complex structures that's why using these helpers can simplify quite a lot the process of building such forms that are very important for any web application. In this tutorial, we'll deal with building an HTML form using the Rails helpers. We will create a simple app with a Customer model and build a customer registration form. We’ll use Rails 6.1.4 and Ruby 3.0.2.

Set Up the Application

We’ll call our app forms. First of all, let’s generate this app! From your terminal type:

rails new forms

After the app is created we can go to that folder:

cd forms

Next, I want to add the bcrypt gem so that we can use the has_secure_password method, which will be added to our  Customer model in order to set and authenticate against a BCrypt password. In the Gemfile, we will uncomment this line:

gem 'bcrypt', '~> 3.1.7'

And then we run:

bundle install

Create a Customer model

Since the user will be our customer we need to know some details, so the Customer model will have quite a few attributes. Let's generate the migration:

bin/rails g model Customer first_name:string last_name:string gender:string email:string password_digest:string phone:string born_on:date city:string subscribe:integer

We do not store the password as plain text in the database. That’s why we need a password_digest field. Because the method has_secure_password that we’ll add to our model requires a password_digest field in the database to set a BCrypt password. When a user sets up a password, it will be automatically encrypted using the BCrypt function made available to us when we installed the bcrypt gem, and will then be saved in the database in the password_digest column.  Let’s open the customer.rb file in the app/models/ folder and add this method:

has_secure_password

The customer will be not subscribed by default. When the customer will confirm that they want to subscribe to our newsletter the status will change to “subscribed”. We'll provide our customers with a checkbox to subscribe. To achieve that, we’ll use enums for our model. In Rails, an enum is an attribute that maps to integers in the database but can be queried by name. We edit the migration file in the db/migrate/ folder in order to set a default:

class CreateCustomers < ActiveRecord::Migration[6.1]
  def change
    create_table :customers do |t|
      t.string :first_name
      t.string :last_name
      t.string :gender
      t.string :email
      t.string :password_digest
      t.string :phone
      t.date :born_on
      t.string :city
      t.integer :subscribe, default: 0
      t.timestamps
    end
  end
end

Let’s declare these statuses in our app/models/customer.rb file, by adding this line:

enum subscribe: {non_subscribed: 0, subscribed: 1}

After that, we run:

bin/rails db:migrate

Create a Customer Controller

Let’s now generate a Customer controller.  From your terminal type:

bin/rails g controller Customers new create show

We’ll have a form for the customer to enter their details and a show view to see these details.
Let’s now edit our methods in the controller. In the new method, we’ll make a @customer instance variable accessible in the view:

def new
  @customer = Customer.new
end

Also, we’ll specify the strong parameters in the private method customer_params at the bottom of the controller:

private
def customer_params
  params.require(:customer).permit(:first_name, :last_name, :gender, :email, :password, :password_confirmation, :phone, :born_on, :city, :subscribe)
end

In the create method we’ll redirect to the show.html.erb page to view the customer's details:

def create
  @customer = Customer.new(customer_params)
  if @customer.save
    redirect_to customer_path(@customer)
  else
    redirect_back fallback_location: root_path
    flash[:notice] = "Your account could not be created."
  end
end

And inside the show method we’ll find the Customer by its id:

def show
  @customer = Customer.find(params[:id])
end

Set the Routes

Also, we need to set routes for these actions. 

Let’s set the root route to the Customers new action where we’ll put our form.
In the config/routes.rb file we delete the auto-generated routes and put this at the top:

root 'customers#new'

And then we add another line for the other two actions:

resources :customers, only: [ :create, :show]

Build our Form

Let’s start building our form.  First, let’s delete the app/views/customers/create.html.erb file because we don’t need that. Then we open the new.html.erb file in the app/views/customers/ directory and delete the text that was automatically added when we generated the controller.  Instead, we’ll put the following text:

<p><%= flash[:notice] %></p>
<h1>Sign Up for an Account</h1>

To build our form, we’ll use the main form_with helper. This method can take several arguments. When it is called with no argument, it will post to the current page. In our case, we'll use it with the model argument:

<%= form_with model: @customer do |form| %>
<% end %>

Using this form_with helper with the model argument will post to the /customers URL and will populate the form fields with values corresponding to the Customer model. The form_with helper will generate the following HTML:

<form action="/customers" accept-charset="UTF-8" method="post">
</form>

And also it will generate a hidden <input> field which is a security feature of Rails called cross-site request forgery protection.

We'll now start generating form controls associated with the Customer model.

The form_with method yields a form builder that has several helpers for generating fields, checkboxes, and radio buttons. These helpers will generate the appropriate HTML markup. 
These helpers usually take a few arguments among them an object_name, a method, and an options hash. When used with a model argument, the form_with helper will use that model object. In our case, all the helpers within our form will be scoped with customer[...].  All we need to specify is the method we want. Rails will also generate an id from the object name and the method used. Like this: <object_name>_<method>.

First, we'll use the text_field helper for the customer's first and last name. We pass the :first_name, respectively the :last_name methods to these helpers. Like this:

<%= form.text_field  :first_name %>

and

<%= form.text_field  :last_name %>

The generated HTML looks like this:

<input type="text" name="customer[first_name]" id="customer_first_name">

and

<input type="text" name="customer[last_name]" id="customer_last_name">

We need some labels for these fields. For that, we'll use the label helper with the  :first_name argument and the :last_name argument, respectively.

<%= form.label :first_name %>

 This helper generates the following HTML:

<label for="customer_first_name">First name</label>

And the other helper:

<%= form.label :last_name %>

generates the following HTML:

<label for="customer_last_name">Last name</label>

The form looks like this for now:

<%= form_with model: @customer do |form| %>
  <p>
    <%= form.label :first_name %>
    <%= form.text_field  :first_name %>
  </p>
  <p>
    <%= form.label :last_name %>
    <%= form.text_field  :last_name %>
  </p> 
<% end %>

We wrap all form controls inside a <p> tag.

Now, we add a form control for the customer to enter their email address. To do this, we use the email_field helper:

<p>
  <%= form.label :email %>
  <%= form.email_field :email %>
</p>

This will generate an <input> HTML element of type email.

Now, we need to collect our customer’s gender. Of course, only if they want to share this information with us. For this, we’ll use radio buttons. Rails has a radio_button helper that generates an <input> element of type radio.  We add labels to these radio buttons, too.

<p>
  <%= form.radio_button :gender, "female" %>
  <%= form.label :gender_female, :female %>
  <%= form.radio_button :gender, "male" %>
  <%= form.label :gender_male, :male %>
</p>

For the password and password_confirmation attributes, we'll use the type='password' on the <input> elements. Rails has a helper for this kind of input, too: the password_field helper. Let's add two of these helpers: one for the password and another for the password_confirmation method:

<p>
  <%= form.label :password %>
  <%= form.password_field :password %>
</p>
<p>
  <%= form.label :password_confirmation %>
  <%= form.password_field :password_confirmation %>
</p>

The type of these two <input> elements will be set to "password".
For the phone attribute, we'll use the phone_field helper:

<p>
  <%= form.label :phone %>
  <%= form.phone_field :phone %>
</p>

This will generate an <input> element of type tel.

The  born_on attribute refers to the customer’s date of birth. For this attribute, we'll use an <input> element of type date. This element allows the user to choose a date.  But we don’t want to show “Born on”  to the customer because it doesn’t sound right. Instead, we will say “Date of birth”. To do this, we need to pass a content argument to the helpers to overwrite the default  “Born on”.

<p>
  <%= form.label :born_on, 'Date of Birth' %>
  <%= form.date_field :born_on %>
</p>

For the city attribute, we'll use the select helper. This helper can take five arguments: the object, the method, the choices array, which is nil by default, the options hash, and the html_options hash. We’ll pass to this helper an array of choices. For illustrative purposes, I selected a few cities from the United States.

<p>
  <%= form.label :city %>
  <%= form.select :city, ['New York City', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'] %>
</p>

This generates the following HTML:

<p>
  <label for="customer_city">City</label>
  <select id="customer_city" name="customer[city]">
    <option value="New York City">New York City</option>
    <option value="Los Angeles">Los Angeles</option>
    <option value="Chicago">Chicago</option>
    <option value="Houston">Houston</option>
    <option value="Phoenix">Phoenix</option>
  </select>
</p>

We want to ask our customers if they want to subscribe to our newsletter. For that, we use the check_box helper and pass the subscribe method to it. When checked, it has the value "subscribed". Left unchecked, its value will be "non_subscribed", meaning the customer does not subscribe to our newsletter.

<p>
  <%= form.label :subscribe, "Subscribe to our_newsletter:" %>
  <%= form.check_box :subscribe, {}, "subscribed", "non_subscribed" %>
</p>

This generates the following HTML:

<p>
  <label for="customer_subscribe">Subscribe to our_newsletter:</label>
  <input name="customer[subscribe]" type="hidden" value="non_subscribed">
  <input type="checkbox" value="subscribed" name="customer[subscribe]" id="customer_subscribe">
</p>

The form must be submitted. For that, we'll use the submit helper. This helper takes two arguments: a value and an options hash. The default value is “Create Customer” for the new action and “Edit Customer” for the edit action. We put this at the bottom of our form and change the default value to "Register":

<p><%= form.submit "Register" %></p>

This is the final form:

<%= form_with model: @customer do |form| %>
  <p>
    <%= form.label :first_name %>
    <%= form.text_field  :first_name %>
  </p>
  <p>
    <%= form.label :last_name %>
    <%= form.text_field  :last_name %>
  </p> 
  <p>
   <%= form.label :email %>
   <%= form.email_field :email %>
  </p>
  <p>
    <%= form.radio_button :gender, "female" %>
    <%= form.label :gender_female, :female %>
    <%= form.radio_button :gender, "male" %>
    <%= form.label :gender_male, :male %>
  </p>
  <p>
    <%= form.label :password %>
    <%= form.password_field :password %>
  </p>
  <p>
    <%= form.label :password_confirmation %>
    <%= form.password_field :password_confirmation %>
  </p>
  <p>
    <%= form.label :phone %>
    <%= form.phone_field :phone %>
  </p>
  <p>
    <%= form.label :born_on, 'Date of Birth' %>
    <%= form.date_field :born_on %>
  </p>
  <p>
    <%= form.label :city %>
    <%= form.select :city, ['New York City', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'] %>
  </p>
  <p>
    <%= form.label :subscribe, "Subscribe to our_newsletter:" %>
    <%= form.check_box :subscribe, {}, "subscribed", "non_subscribed" %>
  </p>
  <p><%= form.submit "Register" %></p>
<% end %>   

Create the Show View

Finally, we want to display the customer’s details on the show view. We open the app/views/customers/show.html.erb file, delete the auto-generated text, and add these lines:

<p>First name: <%= @customer.first_name %></p>
<p>Last name: <%= @customer.last_name %></p>
<p>Email: <%= @customer.email %></p>
<p>Gender: <%= @customer.gender %></p>
<p>Phone: <%= @customer.phone %></p>
<p>Date of birth: <%= @customer.born_on %></p>
<p>City: <%= @customer.city %></p>
<% if @customer.subscribed? %>
  <p>You subscribed to our newsletter.</p>
<% else %>
  <p>You've not subscribed to our newsletter.</p>
<% end %>

Conclusion:

We generated our form relatively easy, using the Rails built-in form helpers.

Post last updated on May 13, 2023