Mar 30, 2016
Table of contents:
In last week’s tutorial we set up the Model layer of this registration flow, which included the User
, Role
, and Assignment
models, as well as Unit Tests to assert that the correct business logic was being enforced correctly.
Registration is a unique aspect of building an application that involves processes and rules that only apply when the user is first signing up to your application.
A good way of encapsulating these special types of processes is through Form Objects. We’ve previously looked at Form Objects in Rails in Using Form Objects in Ruby on Rails and Using Form Objects in Ruby on Rails with Reform.
In today’s tutorial we’re going to pick up where we left off last week by adding a Form Object for encapsulating the registration flow. If you missed last week’s post, I would encourage you to read that post before continuing with this post.
Before we get into the code, first I will quickly outline what we’re going to be building in today’s tutorial.
The registration process is a unique part of your application in that it will probably have validation rules that only apply in this one circumstance.
For example, you might require that the user confirms their password by typing it twice, or that they have checked the box to agree to your terms and conditions.
You might also want to create additional models when the user is created for an account or subscription. In my case, I want to automatically assign the user to the guest
role.
A naive approach to trying to solve this problem is to add this functionality to the User
model. But as you can see, we will end up adding extraneous validation rules and additional responsibility to the User
Model where it shouldn’t be concerned.
Instead, we can encapsulate this functionality in a Form Object. The Form Object will deal with the business rules of registering as a new user, and will handle creating any additional models and relationships. This will also make it very easy to come back to this process at some point in the future because it will be immediately obvious where the responsibility for the registration process lies.
So with that brief introduction out of the way, lets get down to building the registration Form Object!
As I mentioned in Using Form Objects in Ruby on Rails with Reform, I prefer to use Reform as the base of my Form Objects instead of rolling my own. I tend to find that Open Source projects have already dealt with the edge-cases I’m inevitably going to stumble across at some point in the future.
So the first thing to do is to add Reform to the project. Add the following two Gems to your Gemfile
:
gem 'reform'
gem 'reform-rails'
And then run the following command in Terminal to install them:
bundle install
Next we need to add the Form Object and the associated Unit Test files.
Create a new directory called forms
under app
and then create a new file called register_form.rb
:
class RegisterForm < Reform::Form
end
As you can see, this class should inherit from Reform::Form
.
Next create a new directory called forms
under the test
director and then create a new file called register_form_test.rb
:
require 'test_helper'
class RegisterFormTest < ActiveSupport::TestCase
def setup
@user = User.new
@form = RegisterForm.new(@user)
end
end
The first responsibility of the register form is to ensure that the correct properties have been sent via the request. In this example we’re only going to be requiring an email address and a password.
So first up we can write a test to ensure that those properties are required:
require 'test_helper'
class RegisterFormTest < ActiveSupport::TestCase
def setup
@user = User.new
@form = RegisterForm.new(@user)
end
test 'email should be required' do
@form.validate({})
assert_includes(@form.errors[:email], "can't be blank")
end
test 'password should be required' do
@form.validate({})
assert_includes(@form.errors[:password], "can't be blank")
end
end
Now if you run the following command in Terminal, you will see both of those tests fail:
bin/rake test test/forms/register_form_test.rb
To make these tests pass, we can tell the RegisterForm
class to require these properties:
class RegisterForm < Reform::Form
property :email
property :password
validates :email, presence: true
validates :password, presence: true
end
Here I’ve added the two properties to the form and then I’ve added validation rules to both to ensure that they are both required.
Now if you run those tests again, you should see them pass.
Next we need to assert that the value that has been provided for the email property is in fact a valid email address.
To make this assertion, we can add the following test:
test 'email should be a valid email address' do
@form.validate(email: 'invalid')
assert_includes(@form.errors[:email], 'is not an email')
end
And once again, we can run this test to watch it fail. With a failing test in place, we can add the validation rule to make it pass:
class RegisterForm < Reform::Form
property :email
property :password
validates :email, presence: true, email: true
validates :password, presence: true
end
Here I’m using the email
validation rule that we wrote in last weeks tutorial.
And finally we can add a test to assert that the email address that has been provided is unique:
test 'email should be unique' do
create(:user, email: 'name@domain.com')
@form.validate(email: 'name@domain.com')
assert_includes(@form.errors[:email], 'has already been taken')
end
As you can see, first I create a new user in the database with a given email address by using the Factory Girl create
method.
Next I pass the same email into the form and then assert that the correct error has been set.
To make this test pass we can add the unique
validation rule to the Form Object:
require 'reform/form/validation/unique_validator.rb'
class RegisterForm < Reform::Form
property :email
property :password
validates :email, presence: true, email: true, unique: true
validates :password, presence: true
end
Notice how I’m requiring Reform’s unique validator? This provides a better way to check for uniqueness than the default Active Record method.
Now that we’ve got those basic validation rules out of the way, we can turn our attention to the actual process of saving the new user.
Normally if you didn’t have to deal with any other business rules you would be done with the Form Object. But in this case, I want to automatically assign the user to the guest
role.
A good way of dealing with this is to override the save
method on the form to provide our own custom behaviour.
But first, we will write a test that asserts the behaviour we’re looking for:
test 'should create new unconfirmed guest user' do
@form.validate(email: 'name@domain.com', password: 'password')
assert(@form.save)
assert_not(@user.confirmed?)
end
If you run the tests again, you will see them all pass. By default Reform will take the supplied parameters and call save on the Model for us, therefore everything in this test should pass.
But we also need to assert that the user has a given role. To make this assertion we can add the following extra line to this test:
test 'should create new unconfirmed guest user' do
@form.validate(email: 'name@domain.com', password: 'password')
assert(@form.save)
assert_not(@user.confirmed?)
assert(@user.role?(:guest), 'user does not have the role of :guest')
end
Here I’m asserting that the user has a role of :guest
. If you run these tests again you should see this assertion fail.
With the failing assertion in place we can now add the code to make it pass.
First we can add our own save
method to the RegisterForm
:
def save
sync
model.save
end
In this method first I’m calling the sync
method to sync the properties of the form to the model
instance.
Next I’m calling save
on the model
to save the properties.
Here we’ve effectively replicated what was going on in the form before we overide the save
method. If you run the tests again you should get the same error as before.
So now we’ve overridden the save
method, we now have a place to assign the role to the user:
require 'reform/form/validation/unique_validator.rb'
class RegisterForm < Reform::Form
property :email
property :password
validates :email, presence: true, email: true, unique: true
validates :password, presence: true
def save
sync
model.roles << Role.find_by(name: :guest)
model.save
end
end
Now if you run the tests again, you should see them still fail! The reason why the test is still failing is because we need to seed the database with the guest
role.
First create a new file under test/factories
called role.rb
:
FactoryGirl.define do
factory :role do
name Faker::Lorem.word
end
end
Here I’m just setting the default name of the Role to a random work from Faker
.
Next we can create the guest
role during the test:
test 'should create new unconfirmed guest user' do
create(:role, name: 'guest')
@form.validate(email: 'name@domain.com', password: 'password')
assert(@form.save)
assert_not(@user.confirmed?)
assert(@user.role?(:guest), 'user does not have the role of :guest')
end
Now if you run those tests again you should see them all pass!
In today’s tutorial we’ve encapsulated the registration process in a Form Object. This gives us a single place to deal with specific registration validation, as well as setting up any additional models or associations that will need to be created at the same time.
I find the Form Object pattern a really nice way to encapsulate these important processes.
Alternative methods would be to crowbar this functionality into the User
model, or into the Controller, but both of those methods have a lot of negatives with not very much in the way of positives.
Next week we will be continuing with this registration flow mini-series by looking at adding the Controller and View for registering, as well as the Functional Tests to assert everything is working as it should.
You can find the code for this week’s tutorial on Github.