User registration

What we will build

Now that our User model is in place, we need a way to allow users to create an account on our application. To do this, we are going to create a new page with a sign-up form:

When users enter a valid email and password, they will be redirected to the dashboard page with a success message:

If the user enters invalid information, they will be redirected back to the registration form with a minimalistic error message:

Now that we have a clear idea of what we want, it's time to start writing some code!

Signing up users

The first step is to create the routes that we will use:

# config/routes.rb

Rails.application.routes.draw do
  root "pages#home"
  resource :dashboard, only: :show

  resource :registration, only: %i[new create]

  get "up" => "rails/health#show", as: :rails_health_check
end

We use the singular resource method instead of the plural resources method. This is because our users are only expected to register once so it makes more sense to use a singular route in that case. If we open a terminal and run bin/rails routes -g registration, we should see the following output:

Prefix Verb URI Pattern                 Controller#Action
registration_index POST /registration(.:format)     registration#create
  new_registration GET  /registration/new(.:format) registration#new

The next step is to add the corresponding controller:

# app/controllers/registrations_controller.rb

class RegistrationsController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)

    if @user.save
      redirect_to dashboard_path, notice: "You have successfully registered!"
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:user).permit(:email, :password)
  end
end

This is the most idiomatic Ruby on Rails controller. We see this code repeated all the time in Rails controllers.

The #new action instantiates a new User and assigns it to a @user instance variable so that we can access it in the view.

The #create action instantiates a new User with the parameters submitted by the user in the registration form. If the parameters are valid, the user is saved to the database and redirected to the dashboard with a success message. If the parameters are invalid, the user is redirected back to the registration form with an error message.

Note that even if the route name is singular, the controller is plural. With Rails conventions, controller names should always be plural.

Now that our controller is ready, we can create the corresponding view:

<%# app/views/registrations/new.html.erb %>

<h1>Sign up</h1>

<%= form_with model: @user, url: registration_path do |f| %>
  <%= tag.div(@user.errors.full_messages.to_sentence) if @user.errors.any? %>

  <div>
    <%= f.label :email %>
    <%= f.email_field :email %>
  </div>

  <div>
    <%= f.label :password %>
    <%= f.password_field :password %>
  </div>

  <%= f.submit "Sign up" %>
<% end %>

This view uses the form_with helper to create a form that will submit the user's registration information to the RegistrationsController#create action. If there are any errors, they will be displayed at the top of the form.

Let's also add a link in the navbar to the registration form:

<%# app/views/layouts/application.html.erb %>

<!DOCTYPE html>
<html>
  <head>
    <%# ... %>
  </head>
  <body>
    <header>
      <nav>
        <ul>
          <li><%= link_to "Home", root_path %></li>
          <li><%= link_to "Dashboard", dashboard_path %></li>
          <li><%= link_to "Sign up", new_registration_path %></li>
        </ul>
      </nav>
    </header>
    <main>
      <%# ... %>
    </main>
  </body>
</html>

Let's go ahead and try to create a new user with invalid params. You should be redirected to the registration form with an error message. If you try to create a new user with valid params, you should be redirected to the dashboard with a success message.

User registration tests

Let's add some automated tests to make sure this behavior will continue to work as expected. Let's write a first test to make sure there are no errors when trying to access the registration form:

# test/controllers/registrations_controller_test.rb

require "test_helper"

class RegistrationsControllerTest < ActionDispatch::IntegrationTest
  test "#new" do
    get new_registration_path

    assert_response :success
  end
end

It might seem like these types of tests are not very useful, but they are. They make sure that the route exists and that the view is rendered without any errors. In fact, I make these types of tests for every route in my applications.

Next we can add a test for the #create action. We are only going to test the happy path here where valid parameters are submitted.

# test/controllers/registrations_controller_test.rb

require "test_helper"

class RegistrationsControllerTest < ActionDispatch::IntegrationTest
  # ...

  test "#create" do
    assert_difference "User.count", 1 do
      post registration_path, params: { user: { email: "[email protected]", password: "password" } }
    end

    assert_redirected_to dashboard_path
  end
end

In this test, we checked when submitting valid params, the user is saved to the database and is redirected to the dashboard.

Let's run all our tests to make sure everything is working as expected:

bin/rails test

If they are green, we can move on to the next chapter where we will add the ability to log in.