Ruby on Rails Forms. Let’s Build a Sign-Up Form!
In Ruby on Rails August 16, 2021
Updated on May 13, 2023
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.