Rails 5.1 API app, part 5: Deploying with confidence

« back to all blogs

In this final part of our Rails API app series we’ll talk about specs, code coverage, continuous integration and deployment - and how to be certain your application is working.

_This series is composed of six articles! Click here for a table of contents._

Testing your app

A lot of people will say: “You must do TDD! It’s the most important thing in the world!" It’s not the most important thing in the world. It’s a useful tool when you’re at least decently versed in the problem domain and the technology you’re using - when you can reason about what needs to be written without writing it. However when learning, exploring a new domain, or simply hacking, I feel the tests can come after the code. What is imperative is that they do indeed come.

There’s a ton of good testing tools for Ruby and Rails. Here’s my personal toolbox, which gets installed in almost every project I’m doing nowadays:

We’ll now go over those one by one, with a concrete example of what it does in the bookstore backend app. The whole setup is a bit too involved for a simple blog post, but you’re welcome to take a peek on GitHub at paweljw/bookstore-backend!


Rspec doesn’t really need an introduction for most people who have been doing Ruby and Rails for a while. I’d just like to point out what I love the most about Rspec: the composable examples and contexts.

We’re using both of these features in bookstore tests! Shared examples are great when a lot of behavior is identical between e.g. different contexts in one spec, or even between different specs. In the bookstore, the GET parts of the REST API behave the same whether you’re an admin or not, so I extracted these to a shared example:

shared_examples 'authors listing' do
  describe 'GET /api/v1/authors' do
    # ...

  describe 'GET /api/v1/authors/:id' do
    # ...

Then in the specs, I can simply do:

context 'with non-admin user' do
  include_context 'logged in user'
  it_behaves_like 'authors listing'
  # ...

context 'with admin user' do
  include_context 'logged in admin'
  it_behaves_like 'authors listing'


Which brings us to shared contexts - we’re including them above with include_context. These are defined like this:

shared_context 'logged in user' do
  let(:user) { create(:user) }
  before { allow_any_instance_of(DecodeAuthenticationCommand).to receive(:result).and_return(user) }

shared_context 'logged in admin' do
  let(:user) { create(:user, :admin) }
  before { allow_any_instance_of(DecodeAuthenticationCommand).to receive(:result).and_return(user) }

Using these contexts, we stub out authentication from our controllers and always return a user. (And we can do this, because the correctness of DecodeAuthenticationCommand is proved with a separate test. When all parts are sound, the app will work).

Factory Girl

Factory Girls provides an easy way to fabricate “fake” objects in tests, which are much more dynamic than fixtures. The DSL is trivial to use:

FactoryGirl.define do
  factory :author do
    name { Faker::Name.name }

And now I can call many helpers in my specs: build(:author), create(:author), and some more creative: create_list(:author, 10).

Factories support relations:

FactoryGirl.define do
  factory :book do
    title { Faker::Book.title }
    price { rand * 15 }

Book factory was given author without a block, so it checks whether it’s an association, and since we’ve defined an author factory - each fabricated book will trigger fabricating an author. Of course if I had a particular author I wanted, I could force that with create(:book, author: author).

One last great thing about Factory Girl factories are traits, which are optional sets of changes to the fabricated object:

FactoryGirl.define do
  factory :user do
    email { Faker::Internet.email }
    password 'password123'

    trait :admin do
      admin true

With this factory, when I do create(:user), the database default (false) is used for admin column. But when I do create(:user, :admin) the trait is applied and admin column is true.


You’ve seen me use Faker in factories above. Faker has some a metric hojillion of methods to create statistically probable e-mails, book titles, names, and much, much more. I recommend taking a look at what it can do.


This gem expedites creating database tests, allowing to write a spec like this:

describe Book do
  it { is_expected.to belong_to(:author) }

  it { is_expected.to validate_uniqueness_of(:title) }
  it { is_expected.to validate_presence_of(:title) }

  it { is_expected.to have_db_column(:title).of_type(:string) }
  # ...

Database Cleaner

Makes sure data from different test runs does not interfere. That should never be the case when we’re using transactions to write data during tests and roll them back before they actually hit the DB. That is sometimes the case. 🤷‍


Timecop allows for testing of time-sensitive things - here it’s about expirations for tokens. Since the expiration is a part of token content, I wouldn’t be able to test the output of AuthenticateUserCommand. With Timecop:

before { Timecop.freeze(2017, 1, 1, 0, 0, 1, 1) }
after { Timecop.return }

let(:expected_token) do
  'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0ODMzMTUyMDF9.' \

subject { described_class.call(user.email, 'password123') }

# The token is exact since the expiration is precisely 24 hours after what Ruby
# _thinks_ is the date and time.
it { expect(subject.result).to eq expected_token }


A tool to check how much of our code is covered with tests. We’ll talk more about this further down the line.

Continuous Integration with Travis CI

As long as your app is open source (and on GitHub, pretty much), the fine folks behind Travis CI graciously offer free continuous integration for it. It’s as easy as going to the site, singing up (with GitHub account), and clicking to add particular repositories. You control how Travis builds your code with a .travis.yml file. For reference, here’s one that bookstore uses:

language: ruby
  - postgresql
  - "RAILS_ENV=test"
  - RAILS_ENV=test rake db:create db:migrate
  postgresql: "9.6"
  - bundle exec codeclimate-test-reporter

We’ll talk about Code Climate more in a while. For now the important parts here are the environment under env, attaching postgresql with services and addons, and setting up the environment in the before_script. For language: ruby Travis just runs bundle exec rake to test. Rspec hooks straight into that.

bookstore-backend on Travis CI

bookstore-backend on Travis CI

Be aware that since Travis is free, it’s not fast by any measure. Bookstore is a tiny app, and the tests can routinely take upwards of five minutes.

Continuous integration works great when you observe proper git hygiene:

Pull request view, note that Code Climate has finished and Travis CI is still running its check

Pull request view, note that Code Climate has finished and Travis CI is still running its check

When you have specs and they are being run automatically every time you even think about merging something (= when you issue a pull request) - that’s when you can merge with confidence.

Code Climate

Code quality is important. Great code is easier to maintain, and at least Ruby’s style guide is purposefully written in such a way that when you try to write a huge monolith of code which will be impenetrable to anyone (includnig You Of Two Weeks From Now) it cleverly disallows it.

I use two linters locally: Rubocop and Reek. Rubocop cares for my style, while Reek makes sure my code doesn’t smell. These are ran as I type, but I found that there are several other nice tools which I wouldn’t want to run as I type. These (currently) are:

The perfect time to run these is on pull request creation as well!

Enter Code Climate. Similarly to Travis, it’s free for open-source projects on GitHub and hooks into pull requests. It’ll take your code, run some scanners on it and show the warnings and errors in a unified way. Which scanners are ran is defined by a .codeclimate.yml file. For reference, here’s the one used by bookstore-backend:

    enabled: true
    enabled: true
    enabled: true
      - ruby
      - spec/
    enabled: true
    enabled: true
        enabled: false
    enabled: true
  - Gemfile.lock
  - "**.erb"
  - "**.rb"
  - "**.rake"
- bin/
- config/
- public/
- tmp/
- db/

As you can see, besides the tools I talked about earlier, I’ve also added fixme and duplication. These are custom reviewers, or “engines”, designed by Code Climate (and both extremely useful).

It’s worth noting that both Travis and Code Climate are not Rails and Ruby-specific, they work just as well on other languages.

Code Climate also has a coverage reporter. It works by capturing coverage on Travis, using this part of .travis.yml:

    repo_token: XXXYYYZZZ...
  - bundle exec codeclimate-test-reporter

And then sends it to Code Climate.


Our code is now tested regularly and automatically, and we have several spiffy badges on our GitHub page to prove this.

Badges on the bookstore-backend GitHub page

Badges on the bookstore-backend GitHub page

So how do we deploy? Like I promised you in part 0, we’ll see how to deploy the app to Heroku, completely free.

After registering, you’ll be greeted with a dashboard:

Click on new in top right to create an app. It’ll need to be named. That name will be in your URL later.

On the next screen, you will have an option to authenticate with GitHub and attach a repository.

We also want to enable automatic deploys from master branch:

Completing this process drops us down into the app’s dashboard.

From top left we can attach a PostgreSQL database. The free version is limited to 10,000 rows, but for us this is more than enough.

Provisioning PostgreSQL sets a DATABASE_URL environment variable for our app:

You’ll see a username and password in there, I redacted mine. Remember how we set up our production database settings in Rails?

  url: <%= ENV['DATABASE_URL'] %>

Boom! It attached automagically. Nothing to it.

On the topic of addons, I highly recommend attaching Papertrail as well - it’s sometimes helpful to look at the logs. It should capture the logs automatically right after it’s provisioned.

Since we didn’t push to master since setting up the app, no deploys have been done yet. We’ll need to go into the “Deploy” menu and run one by hand. It’s at the far bottom of the page.

We go back to the dashboard and see that the deploy completed.

Time to whip out Postman again and see if it responds.

Not authorized! Well, that’s right, we don’t have any users yet. Hmm… so how would we go about adding them?

We’ll need console access. For that, the Heroku Toolbelt is required - you’ll have install and authorize it yourself, as setup varies per operating system.

After that, we can run in our shell:

% heroku run --app bookstore-backend rails c

This will connect to Heroku and give us control of a shell in which rails c is running. Similarly, we could substitute bash for rails c and that’ll give us an interactive shell in which we can run e.g. Rake.

In the spawned console, we can just set up a user like usual:

User.create(email: 'p@steamshard.net', password: 'redacted', admin: true)

With this user I can authenticate through Postman normally.

That’s it!

We’ve made a complete Rails 5 API app, from rails new to deploying to production. We’ve learned about Docker, JSON Web Tokens, modeling reality with code, testing and Heroku deployments.

This series has grown as I wrote; I never imagined it’ll be six parts just to finish the back end! I’ve decided to split off the frontend in Vue.js to it’s own series, starting (hopefully) next Saturday. It’ll be another fun Saturday project, and I hope you join me for that as well.

Thank you for going on this journey with me, dear Reader - I hope you’ve learned something! I certainly have.

Top image credit: https://commons.wikimedia.org/wiki/File:Soyuz_TMA-05M_rocket_launches_from_Baikonur_4.jpg (Public Domain)

« back to all blogs