In a nutshell, when we create background jobs, we pass an object, with the data to use, to the worker. In most cases, we want the background job to reference a Model object (e.g. ActiveRecord), so to make things simpler, we just pass the ID of the object to the worker. Then Rails and Sidekiq take care of everything for us (this includes finding the object in the database, then serializing and de-serializing it).
In this template we have taken a different route. Instead of passing an existing Model object, we’ll pass an ad-hoc one, created on the spot and stored in a hash that we’ll pass to the worker in JSON format. Then it is up to us to tell the worker how to de-serialize it, reconstituting the original hash from the JSON string, to be able to operate on the data passed.
All this will become more clear with an example.
Our app will have a simple contact page with a custom form that the user fills in with some requested information. Upon submission of the form, the app will create a background job to send an email, deferring the responsibility to Sidekiq.
1. Prep Work
Add Letter Opener and Launchy gems to your Gemfile:
gem "letter_opener"
gem "launchy"
In development, letter_opener
allows us to simulate the process of sending the email by creating it as a temporary file. That way we will avoid sending the actual email over the network, which is messy and brittle to test.
Launchy automatically opens the created temp file in a browser window so that the sending process becomes automatic and we have real-time confirmation that the email was sent correctly.
Modify the config/environments/development.rb
:
# to be appraised of mailing errors
config.action_mailer.raise_delivery_errors = true
# to deliver to the browser instead of email
config.action_mailer.delivery_method = :letter_opener
2. Mailer
Generate a Mailer
This project starts with a basic new Rails application without controllers, models, or views. Generate a mailer:
$ rails g mailer VisitorMailer
Create the Mailer Action
Next, receive the information needed to build the email (name, email address, and body of email) in the Mailer action. Instance variables defined here are available to the corresponding view (the email template).
Keep in mind that the contact_email
method is the one that the worker (background process) will execute.
class VisitorMailer < ActionMailer::Base
def contact_email(name, email, message)
@name = name
@email = email
@message = message
mail(from: @email,
to: 'javier@badaboom.com',
subject: 'New Visitor\'s Email')
end
end
Create the E-Mail Template
The “view” associated with the Mailer method is the actual template for the email to be sent. Make two versions, in HTML and text format, with the information passed through instance variables.
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1><%= @name %> (<%= @email %>)</h1>
<p>
<%= @message %>
</p>
</body>
</html>
3. The Resource
Generate the Controller
Now that the Mailer is set and done, generate the VisitorsController.
$ rails g controller visitors
We add two actions. The index
actions displays the basic contact form. Once submitted, it reaches the contact
action where we extract the form parameters.
The form information is packaged into a hash and subsequently JSONified so we can pass it as an argument to the worker (a Sidekiq requirement).
class VisitorsController < ApplicationController
def index
end
def contact
h = JSON.generate({ 'name' => params[:name],
'email' => params[:email],
'message' => params[:message] })
PostmanWorker.perform_async(h, 5)
# if instead of sidekiq I was just sending email from rails
# VisitorMailer.contact_email(@name, @email, @message).deliver
redirect_to :root
end
end
Make a Small Form for the View
Simplicity personified:
Update the Routes
We haven’t done it yet and we cannot defer anymore. It’s time to establish our routes and root.
Rails.application.routes.draw do
post 'visitors/contact', to: 'visitors#contact'
root 'visitors#index'
end
The Model?
Nope, no model. The controller just passes the information received from the form directly to the worker.
4. The Background Worker
Install Sidekiq
Add to your Gemfile and don’t forget to bundle
up.
gem 'sidekiq'
Create a Worker
In this step, follow the instructions from the Sidekiq’s README and docs, and create a worker responsible for delivering emails: a Postman worker.
The key here is that the worker needs a JSON object as simple as possible. Usually this would be the ID from a Model object, in which case Sidekiq would serialize and de-serialize the object referenced by the ID.
In our case, the information is not stored in the database, so we create a JSON hash that we passed to the worker for queuing in Redis. Now, the key is that we also need to de-serialize this JSON object upon arrival to re-create the hash. Once re-constituted, the hash gives us access to the data that we need in order to call the ActionMailer and deliver the email.
class PostmanWorker
include Sidekiq::Worker
def perform(h, count)
h = JSON.load(h)
VisitorMailer.contact_email(h['name'], h['email'], h['message']).deliver
end
end
The results show up in the browser when the email is sent:
Add Dashboard
The nifty Sidekiq Dashboard runs on Sinatra, which we have to add to the Gemfile.
gem 'sinatra', '>= 1.3.0', :require => nil
Add it to the routes:
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'
This makes it available (depending on your setup) in localhost:3000/sidekiq
Be aware that anybody can access this dashboard once in production, so check for ways to secure its access.
5. Final Setup
Make sure that you have run bundle
, and install Redis if it’s not already in place (I recommend using brew
on Mac OS X whenever possible to avoid headaches).
Then all that is left to do is to start each service in its own tab, like this:
$ rails s
$ redis-server
$ bundle exec sidekiq
You can also use Foreman and save yourself opening tabs and running things separately.
We haven’t included tests in this guide. So it is up to you to get these components of the app covered with the necessary tests. Good luck, and have fun with your new background tasks!
Ready to become a professional software developer? Learn more about getting trained in advanced software development »