A couple of weeks ago I built out the action for creating new users in Culttt using a Trailblazer Operation (Using Inheritance for Trailblazer Operations).

In this application I need 3 different ways of creating new users. Firstly, I need to import users from the old WordPress application. Secondly, I need to create confirmed users to seed the database. And finally, I need the default Operation that will be used as part of the registration process.

When a user registers with the application using the default Operation, I will send a confirmation email that will allow the user to confirm their email address.

But I also need a way to allow imported users to confirm their email address, as well as choose a username, and a password.

In today’s tutorial we’re going to be looking at adding the functionality to request a confirmation email. This will touch upon a couple of interesting aspects of Trailblazer Operations that we haven’t covered so far, and it will hopefully reinforce how Trailblazer fits inside of a typical Rails application.

So with that being said, lets get started!

How is this going to work?

Before we jump into the code, lets have a quick review of what we’re going to build.

We’re first going to need two new routes. One for displaying a form that accepts an email to send the confirmation email to, and one to accept the POST request from the form.

We will need a Controller to accept those requests and we need to create the UserMailer class, and email view template.

We will need to update the User::Create::Operation::Default class to send a confirmation automatically after saving the new user and we will need to create a new Operation for requesting a confirmation email.

We will need to create a new Cell class and view template for displaying the form, and of course we will need a bucket load of tests to make sure everything is working as it should.

Phew! We best get started then!

Setting up the routes

The first thing we need to do is to add two new routes to the routes.rb under the config directory:


Rails.application.routes.draw do  
# Join  
get "join", to: "join#new"  
post "join", to: "join#create"

# Login  
get "login", to: "login#new"

# Confirmation  
get "confirmation/request", to: "confirmation/request#new"  
post "confirmation/request", to: "confirmation/request#create"  
end  

If you are familiar with routing in Rails (Defining URL routes in Ruby on Rails), this should be fairly straight forward for you.

There is going to be a request action and a confirm action for this confirmation process.

As you might have noticed, I’m going to separate these two actions into two separate controllers under the Confirmation namespace.

I would rather keep this as two small Controllers, rather than one bigger Controller.

Adding the Controller

Next up I will add the Controller to handle the requests:

module Confirmation  
class RequestController < ApplicationController  
def new  
form Confirmation::Request::Operation  
end

def create  
run Confirmation::Request::Operation do |op|  
return redirect_to login_url  
end

render :new, status: 400  
end  
end  
end  

There isn’t a great deal going on in this Controller, which is a lovely side-effect of using Trailblazer. If you have been following along with these Trailblazer tutorials this is going to look very familiar.

I’ve added the Confirmation::Request::Operation even though I haven’t created it yet because I know exactly what this Controller is going to look like when all the pieces are in place.

At this point I will also add the view form the #new method:

h1 Confirm your membership  
= concept("confirmation/request/cell", @operation)  

Due to the fact that we’re using a Cell to encapsulate this view, it is very simple.

Generating the User Mailer

Before we start creating the Operation class, first we need to generate a new UserMailer class using Rails’ generate command:

bin/rails generate mailer UserMailer  
[/bash]

This should create a new `user_mailer.rb` file under the `mailers` directory. In this new class I will add a method for confirming a membership:  
```ruby  
class UserMailer < ApplicationMailer  
def confirm_membership(user)  
@user = user  
@url = # generate confirmation url  
mail(to: @user.email, subject: "Confirm your Culttt Membership!")  
end  
end  

If you are new to Action Mailer, take a look at Getting started with Action Mailer in Ruby on Rails.

I haven’t set the @url variable yet as we don’t have the confirmation routes. I’ll come back to this when we implement this functionality.

Adding the User Mailer to the User Create Operation

With the User Mailer class generated and ready to go we can update the User::Create::Operation::Default class to automatically send a confirmation email when a new user is saved:

class Default < Base  
contract User::Create::Contract::Default

def process(params)  
validate(params[:user]) do |f|  
generate_digest  
generate_token  
f.save  
send_confirmtion_email  
end  
end

def send_confirmtion_email  
UserMailer.confirm_membership(model).deliver_later  
end  
end  

I will also update the test to ensure that the email is sent when a new user is created with this Operation:

test "create default user" do  
res, op = User::Create::Operation::Default.run(user: attributes_for(:user))

mail = ActionMailer::Base.deliveries.last

assert(res)  
assert_not(op.model.confirmed?)  
assert_not(op.model.imported?)  
assert_equal(op.model.email, mail[:to].to_s)  
assert_equal("Confirm your Culttt Membership!", mail[:subject].to_s)  
end  

Here I’m getting the last email that was sent via Action Mailer and then asserting that the email and subject line are correct.

Building out the Operation

Next up we need to build out the Operation that will accept the POST request from the user when a confirmation email is requested.

The POST request will contain an email address that should be a registered, but unconfirmed email address that exists in the database.

This Operation is a bit different to the Operations we’ve seen in previous tutorials because we are not creating or updating a model:


module Confirmation::Request  
class Operation < Trailblazer::Operation  
contract do  
undef :persisted?  
attr_reader :user

property :email, virtual: true

validate :email_is_unconfirmed  
validates :email, presence: true, email: true

def email_is_unconfirmed  
@user = User.find_by(email: email, confirmed_at: nil)

errors.add(:email, :not_found) unless @user  
end  
end

def process(params)  
validate(params[:user]) do |f|  
UserMailer.confirm_membership(contract.user).deliver_later  
end  
end  
end  
end  

As you can see from this Operation, it’s a bit different from what we’ve seen so far. In this Operation, we need to find the user by their email address, but only if the user is not confirmed.

If the user is found, we can use the Mailer class from earlier to dispatch the confirmation email.

Here are the tests for this Operation:

require "test_helper"

module Confirmation::Request::OperationTest  
class RequestCase < ActiveSupport::TestCase  
test "require email" do  
res, op = Confirmation::Request::Operation.run(user: {})

assert_not(res)  
assert_includes(op.errors[:email], "can’t be blank")  
end

test "require valid email" do  
res, op = Confirmation::Request::Operation.run(user: {email: "invalid email"})

assert_not(res)  
assert_includes(op.errors[:email], "is invalid")  
end

test "require unconfirmed email" do  
res, op = Confirmation::Request::Operation.run(user: {email: "name@domain.com"})

assert_not(res)  
assert_includes(op.errors[:email], "not found")  
end

test "ignore confirmed users" do  
@user = User::Create::Operation::Confirmed.(user: attributes_for(:user)).model

res, op = Confirmation::Request::Operation.run(user: {email: @user.email})

assert_not(res)  
assert_includes(op.errors[:email], "not found")  
end

test "send confirmation email" do  
@user = User::Create::Operation::Default.(user: attributes_for(:user)).model

res, op = Confirmation::Request::Operation.run(user: {email: @user.email})

mail = ActionMailer::Base.deliveries.last

assert(res)  
assert_equal(@user.email, mail[:to].to_s)  
assert_equal("Confirm your Culttt Membership!", mail[:subject].to_s)  
end  
end  
end  

As you can see, these tests are pretty similar to the tests we’ve written in previous weeks.

First I write a test for each business rule that I want to enforce, and then finally I write a test to confirm the happy path.

Creating the Cell

As with last week’s tutorial (Getting started with Trailblazer Cells) I’m going to be encapsulating the form for this functionality in a Cell:

module Confirmation::Request  
class Cell < Culttt::Cells::Form  
def show  
render  
end  
end  
end  

Once again I’m extending Culttt::Cells::Form from last week’s tutorial. This is also a really basic example of a Cell class, and so there isn’t any additional logic that is required in the class itself.

I will also need the template file:

– if form.errors.any?  
ul  
– form.errors.full_messages.each do |msg|  
li = msg

= form_tag confirmation_request_url, class: "confirmation-request-form" do |f|  
div  
= label_tag :email, nil, class: "qa-email-label"  
= text_field_tag :email, nil, class: "qa-email-input"  
div  
= submit_tag "Request Confirmation", class: "qa-submit"  

This is your typical Rails form using the form_tag method, rather than the usual form_for method. As we’re using a Cell to render this form, this file lives under the concept directory.

I will also include some simple assertions to ensure that all of the correct form fields are present in the Cell template:

require "test_helper"

module Confirmation::Request::CellTest  
class CellTest < Cell::TestCase  
controller Confirmation::RequestController

test "has correct markup" do  
html = concept("confirmation/request/cell", Confirmation::Request::Operation.present({})).()

html.must_have_selector("form.confirmation-request-form")  
html.must_have_selector("label.qa-email-label")  
html.must_have_selector("input.qa-email-input")  
html.must_have_selector("input.qa-submit")  
end  
end  
end  

If you missed the setup to this kind of testing from Getting started with Trailblazer Cells, I would recommend that you go back and take a look. I really love how easy it is to test Cells in isolation.

Adding Controller Tests

With all of the functionality in place, we can add some Controller tests to set through the process and ensure everything is working as it should:

require "test_helper"

module Confirmation  
class RequestControllerTest < ActionController::TestCase  
test "display form" do  
get :new

assert_response(:success)  
end

test "fail with invalid email" do  
post :create, user: {email: "invalid email"}

assert_response(400)  
end

test "fail with not found email" do  
post :create, user: {email: "name@domain.com"}

assert_response(400)  
end

test "fail with confirmed email" do  
@user = User::Create::Operation::Confirmed.(user: attributes_for(:user)).model

post :create, user: {email: @user.email}

assert_response(400)  
end

test "send confirmation email" do  
@user = User::Create::Operation::Default.(user: attributes_for(:user)).model

assert_difference "ActionMailer::Base.deliveries.size" do  
post :create, user: {email: @user.email}  
end

assert_response(302)  
assert_redirected_to(login_url)  
end  
end  
end  

Hopefully this reads well and you’re familiar with what we’re testing. I’m basically just walking through the business rules and checking that they fail. I don’t check the actual errors for each case because I’m already check them at the Unit level.

In the final test I’m asserting that the request was successful and that the email was sent correctly.

Conclusion

Hopefully today’s tutorial was beneficial in that it was an example of where we’re using an Operation to perform a process that is not directly creating or updating a 1-to-1 resource.

Despite this we have still used many of the same techniques that we’ve been building up over the last couple of weeks.

Building out this functionality can often be boring. This kind of boiler plate code is basically the same for just about every type of web application.

But hopefully you can see how nice and simple Trailblazer makes it so you don’t have to worry about too much complexity.