How to wrap Rails mailer previews in a database transaction

Juraj Kostolanský 04 September 2020

Most of the web applications today need to send some kind of email. In Ruby on Rails, this is an easy task. Just use the Action Mailer, create the email content, set up the SMTP configuration and you are ready to go.

The hardest part of this process is the content creation. Especially if you are building a multi-language application, seeing the big picture just from the source code can be quite hard. Fortunately, Rails provides a way to see how these emails look right in a web browser, using the Action Mailer Previews. Just create a class that inherits from the ActionMailer::Preview and call the required mailer action.

# test/mailers/previews/user_mailer_preview.rb

class UserMailerPreview < ActionMailer::Preview
  def confirmation
    user = User.new(email: "tester@example.com")
    UserMailer.with(user: user).confirmation
  end
end

However, sometimes your mailer expects the ID of a record in the database, not the object itself. And sometimes you need to prepare this record, for example to create a new email confirmation token.

# test/mailers/previews/user_mailer_preview.rb

class UserMailerPreview < ActionMailer::Preview
  def confirmation
    user = User.first
    user.prepare_token!(:confirmation)
    UserMailer.with(user_id: user.id).confirmation
  end
end

But this should be just a temporary change. Sure, you can wrap the whole method in a database transaction and use the database rollback command. But… How exactly?

Fortunately, there is an easy way to accomplish this using some Rails monkey patching. Just create the following module:

# lib/core_extensions/mailer_preview_rollback

module CoreExtensions
  module MailerPreviewRollback
    def preview
      ActiveRecord::Base.transaction do
        super
        raise ActiveRecord::Rollback
      end
    end
  end
end

Create an initializer in your Ruby on Rails application and prepend this module:

# config/initializers/mailer_preview_rollback.rb

Rails.configuration.after_initialize do
  require "core_extensions/mailer_preview_rollback"
  Rails::MailersController.prepend CoreExtensions::MailerPreviewRollback
end

And voila! All of your mailer previews are now safely wrapped in a database transaction.

Juraj Kostolanský

Juraj Kostolanský

I'm a software engineer from Slovakia working as a full-stack Ruby on Rails web developer and system administrator.