Jun 29, 2016
Table of contents:
Over the past couple of weeks we’ve been looking at building out a registration process using Trailblazer and Ruby on Rails. First we looked at Building out a User Confirmation flow in Trailblazer, and last week we looked at Confirming Users with Trailblazer.
One thing we didn’t look at was adding the confirmation email that is sent to the user to allow them to confirm their email address.
A couple of weeks ago we looked at Getting started with Trailblazer Cells. A Cell is basically a View Model that allows encapsulation for your views. This can often greatly reduce the complexity of messy views!
However, Cells can also be used for your emails. In today’s tutorial we will be looking at Cells and Action Mailer.
I’ve already mentioned where we left off from last week in the introduction to this post so I won’t go over those links again. If you have missed the last couple of weeks of posts it might be a good idea to have a quick glance over those posts now.
You will also need to have an understanding of Action Mailer to get the most out of this tutorial. We’ve already covered Getting started with Action Mailer in Ruby on Rails and Testing Active Job and Active Mailer in Ruby on Rails so you can take a look back at those posts if you want to catch up.
A couple of weeks ago I added a UserMailer
class and a confirm_membership
method for sending the confirmation emails. If you remember back, you can think of the UserMailer
as essentially the same as a Controller.
One thing I didn’t do was to generate the actual confirmation link that will be in the email. Each confirmation link is unique and so it’s not something we can just hard code.
Now that we added the route in last week’s tutorial I can generate the link:
class UserMailer < ApplicationMailer
def confirm_membership(user)
@user = user
@url = confirmation_confirm_url(token: @user.confirmation_token)
mail(to: @user.email, subject: 'Confirm your Culttt Membership!')
end
end
You might be wondering why I’m not generating the link inside of the Cell. Isn’t the whole point of using Cells to benefit from encapsulation? Normally I would of just generated the url from inside the Cell, but it’s a pain in the arse to do things like that in Rails because generating a url ends up touching a lot of core components of the framework.
Next we need to add a couple of things to make this thing work.
Firstly I’m going to add the email view:
= concept("confirmation/confirm/cell/email", @user, url: @url)
As you can see this is really simple because we’re just delegating to the Cell. Here I’m passing the user and the url from the UserMailer
.
Next we can generate a preview of the email. This isn’t going to work because the Cell hasn’t be created yet, but we actually stumble across another problem first.
The concept
helper function does not exist because Trailblazer only add it to the Controller and Action View. To fix this we can add a new method to the ActionMailer
class that the UserMailer
inherits from:
class ApplicationMailer < ActionMailer::Base
default from: 'from@example.com'
layout 'mailer'
def concept(name, model = nil, options = {}, &block)
::Cell::Concept.cell(name, model, options, &block)
end
end
Now if you attempt to preview the email again you should now see the correct error because the Cell does not exist.
Next up we can create the email cell. The Cell is basically a View Model and so we can deal with any type of logic stuff in here:
class Email < Trailblazer::Cell
def show
render
end
def greeting
model.username or 'there'
end
def url
link_to 'here', @options[:url], class: 'qa-confirmation-link'
end
end
As usual we have the default show
method. We also have a couple of methods that we will need in the view. Firstly we have a greeting
method that will either return the users username or simply a placeholder string. And secondly we have a url
method that will generate the link and add the QA class for us.
Now that we have the Cell in place we can also add the template:
p Hey #{greeting}! p Click #{url} to confirm your account!
As you can see this is really simple for now. I hate having to deal with HTML emails and so I’ll kick that can down the road and deal with it another day. This very simple template will do for now.
If you fire up the preview again you should see it working correctly including the greeting and the url.
Finally we can add a couple of tests to ensure that the HTML is generated correctly:
class EmailTest < Cell::TestCase
include Rails.application.routes.url_helpers
def setup
Rails.application.routes.default_url_options[:host] = 'localhost:3000'
@default =
User::Create::Operation::Default.(user: attributes_for(:user)).model
@imported =
User::Create::Operation::Imported.(user: attributes_for(:imported_user))
.model
end
test 'has confirmation link' do
url = confirmation_confirm_url(token: @default.confirmation_token)
html = concept('confirmation/confirm/cell/email', @default, url: url).()
assert_equal(html.find('.qa-confirmation-link')[:href], url)
end
test 'has default user greeting' do
html = concept('confirmation/confirm/cell/email', @default, url: '').()
html.must_have_content("Hey #{@default.username}!")
end
test 'has imported user greeting' do
html = concept('confirmation/confirm/cell/email', @imported, url: '').()
html.must_have_content('Hey there!')
end
end
In these tests I’m checking that the confirmation link is correct and the greeting is set correctly depending on whether the user has a username or not.
As I’ve mentioned a couple of times now, Trailblazer makes it really easy to write these kinds of tests because we only need to invoke the cell, and not the whole Action Mailer process.
I hate having messy views. Once I start writing HTML I want to make it as clean as possible because I find HTML overwhelming and ugly. That’s why I use Slim!
Cells make it really easy to deal with the sprinkles of logic that you will inevitably need in your views.
They also make testing really easy so we can have more confidence in the contents of email without having to test the email via an integration test.
Using a Cell is today’s example is probably overkill, but hopefully it was a good illustration of what you can do with Cells and Action Mailer.