Wednesday, April 25, 2012

Deploying with Capistrano in /mnt on EC2 machines

I use Ubuntu on my EC2 instances. These are based on standard AMIs issued by Ubuntu at These instances all have ephemeral storage mounted at /mnt by default, with a size of 145GB for small instances.

Since /mnt is backed by ephemeral storage, this directory is an ideal location for deploying Rails applications, since you will not typically want to include your source controlled application code in any backup of your instances. Besides, /mnt has a large size, compared to a few GBs for the root partition.

My rails apps are deployed using Capistrano at /mnt/deployment/ on the target EC2 instances.

I run my deployment as a non-root user. Since /mnt is mounted as root by EC2, this can be a problem, especially since I do not like to use sudo in deployment scripts unless absolutely necessary. Note that even if you chown /mnt to be owned by something non-root, it will revert to root (and lose all data, being ephemeral) whenever the instance is stopped and started again.

The solution I have found is to add an upstart script, which I named /etc/init/mounted-ephemeral.conf, following the conventions of other such scripts.

$ sudo cat /etc/init/mounted-ephemeral.conf 
start on mounted MOUNTPOINT=/mnt


console output

exec chown user.eng /mnt

This script ensures that the ownership of /mnt is always set to the user I want, whenever the machine starts up.

Monday, April 2, 2012

Unit testing emails in Rails using RSpec

In this post I am using a specific example of a controller action that sends an email. However, the outlines of the testing approach should be applicable in a wider variety of cases.


1. app/controllers/users_controller.rb:

class UsersController < ApplicationController

  def signup
    email = params[:email]

2. app/mailers/user_mailer.rb:

class UserMailer < ActionMailer::Base
  default from: ""

  def signup(email)
    mail(:to => email, :subject => "Welcome!")

3. app/views/user_mailer/signup.html.haml (contents of the email):

A quick brown fox jumped over the lazy dog.

There are three parts to writing comprehensive specs for this setup. The first part, corresponding to the controller, should test that the controller indeed sends the correct email. The second part, corresponding to the mailer, should test that the email is set up correctly, i.e, it has the correct sender, recipient, subject and so on. The third part is about testing the contents of the email itself, corresponding to the email view.


Part I: Is the correct email being sent? (Controller)

In spec/controllers/users_controller_spec.rb:

require "spec_helper"

describe UsersController do
  describe "POST signup" do
    it "sends a verification email." do
      email = random_email

      post :signup, email: email
      expect_email UserMailer.signup(email)

The bolded part is key. It is a simple helper method that tests that the email has all the expected headers, since headers define the email.

P.S. "random_email" is another helpful method that I end up using frequently.

My implementation of the helper method, which I define in spec/support/email.rb is as follows:

def expect_email(email)
  delivered = ActionMailer::Base.deliveries.last
  expected =  email.deliver

  delivered.multipart?.should == expected.multipart?
  delivered.headers.except("Message-Id").should == expected.headers.except("Message-Id")

The test excludes "Message-Id" from the headers, since that is going to be different for every instance of otherwise exactly identical emails.

Part II: Does the email have the right headers? (Mailer)

In spec/mailers/user_mailer_spec.rb:

require "spec_helper"

describe UserMailer do
  describe ".signup" do
    let(:recipient) { random_email }
    let(:email) { UserMailer.signup(recipient) }

    it { email.from.should == [ "" ] }
    it { == [ recipient ] }
    it { email.subject.should == "Welcome" }

The above should be fairly self explanatory.

Part III: Does the email have correct contents? (View)

This is no different from any other view tests, therefore I have omitted this part.

I hope you found this post useful in keeping your code lean and clean.