cult3

Using Trailblazer Cells in Action Mailer

Jun 29, 2016

Table of contents:

  1. Getting up to speed
  2. Generating the confirmation link
  3. Adding the concept helper method
  4. Creating the Email cell
  5. Adding the template
  6. Adding some tests
  7. Conclusion

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.

Getting up to speed

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.

Adding the concept helper method

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.

Creating the Email cell

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.

Adding the template

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.

Adding some tests

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.

Conclusion

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.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.