Things I Learned about Rails Today

A blog about development in Ruby on Rails

Test your API with RSpec

I have been writing a lot of tests for our API lately, and I wanted to share a technique that has helped me build a test suite that is easy to understand and write. Our API keeps a pretty consistent pattern which makes it a perfect candidate for RSpec shared examples. Similar to RSpec shared context which I discussed in another post, shared examples are a great way to clean up test code and create useful abstractions.

To include a shared example in a spec you have to use one of the following methods:

1
2
3
4
include_examples "name"      # include the examples in the current context
it_behaves_like "name"       # include the examples in a nested context
it_should_behave_like "name" # include the examples in a nested context
matching metadata            # include the examples in the current context

These methods allow you to pass parameters that can be used in the example group. I utilize this for my api tests to provide a factory name, the path for the request and the model:

1
2
3
4
5
6
7
8
9
# /spec/requests/account_spec.rb

require 'rails_helper'

describe 'Account API' do
  describe '/api/accounts' do
    it_behaves_like 'an index route', Account, :account, '/api/accounts'
  end
end

In a Rails project, it is usually recommended to include shared examples under the support folder, so that they are autoloaded and available to use in all your tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#/spec/support/example_groups/index_route

shared_examples "an index route" do |model, factory, path|
  let(:resources) { model.to_s.tableize }

  it "responds with a collection of the resource", type: :request do
    resource = FactoryGirl.create(factory)

    get path

    json = JSON.parse(response.body)
    expect(response.status).to eq 200
    expect(json[resources].length).to eq 1
  end
end

Now when I want to test another API endpoint that follows the RESTful index pattern, I can reuse this spec. Another benefit is that if I were to change the behavior for the index action by requiring an oauth token, I would only need to make the change in one place. Shared examples are also composable, so you could create a shared example for a restful resource by combining shared examples for all the different route types:

1
2
3
4
5
6
7
shared_examples "a RESTful resource" do |model, factory, path|
  it_behaves_like "an index route", model, factory, path
  it_behaves_like "a create route", model, path
  it_behaves_like "a show route", model, factory, path
  it_behaves_like "an update route", model, factory, path
  it_behaves_like "a destroy route", model, factory, path
end


Duck Typing

One of my big breakthroughs in understanding object oriented design in Ruby came when I learned about duck typing. Wikipedia provides a nice definiition of the concept:

duck typing is a style of typing in which an object’s methods and properties determine the valid semantics, rather than its inheritance from a particular class or implementation of an explicit interface.

This means that in Ruby it does not matter if an object is a specific type or class, only that it can respond to a given message. For example, in the stringify method below, it does not matter if the argument is an integer or symbol or some other class, only that it can respond to the to_s method.

def stringify(a)
  a.to_s
end

stringify(1)
=> "1"

stringify(:hello)
=> "hello"

class NewClass
  def to_s
    "works for custom objects too!"
  end
end

stringify(NewClass.new)
=> "works for custom objects too!"

Not having to care about a specific object’s class enables a lot of flexiblity for swaping out collaborator objects. To illustrate this, let’s take a look at an example:

File Storage with Duck Typing

My app generates reports, and I would like to save these reports. Initially, I just want to save the reports to my local disk.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Report
  def initialize(name, data)
    @name = name
    @data = data
  end

  def save_to(storage)
    storage.store(@name, @data)
  end
end

class FileStorage
  def store(file_name, data)
    File.open("reports/#{file_name}", 'w') do |file|
      file.write(data)
    end
  end
end

report = Report.new('Sales Figures', 'sales data blah...')
file_store = FileStorage.new
report.save_to(file_store)

Later, I decide that it would also be nice to have the ability to upload reports to S3. With duck typing, implementing this new feature is as simple as creating a new class for uploading to S3 which implements a store method:

1
2
3
4
5
6
7
8
9
10
11
12
13
class S3Storage
  def initialize(bucket_name)
    @bucket = AWS::S3.new.buckets[bucket_name]
  end

  def store(file_name, data)
    @bucket.objects.create(file_name, data)
  end
end

report = Report.new('Sales Figures', 'sales data blah...')
s3_store = S3Storage.new('my_bucket')
report.save_to(s3_store)

Start thinking about objects in terms of the messages they receive, and you will be rewarded with the flexibility that duck typing allows.


Using RSpec Shared Context for DRYer Specs

Do you ever find that many of your specs have a similar setup that you are repeating over and over? I often find this happening when I’m writing feature specs that require a logged in user. For example:

1
2
3
4
5
6
7
8
9
10
11
feature 'some super cool feature' do
  background 'login an admin' do
    user = create(:user, role: 'admin')
    user.confirm!
    login_as(user, scope: :user)
  end

  scenario 'my super cool scenario' do
  ...
  end
end

If my login or authentication process changes, I have to manually go into each spec and change the code. I also find that this setup code clutters the spec and makes it harder to read. If we could extract the setup code and replace it with an easy to read single line, it would solve both of these problems.

RSpec shared contexts provide just this type of solution. Using a shared context we can extract the setup into its own file, and rewrite our feature spec in a cleaner way:

1
2
3
4
5
6
7
feature 'some super cool feature' do
  include_context 'a logged in admin'

  scenario 'my super cool scenario' do
  ...
  end
end

To create a shared context:

1
2
3
4
5
6
7
8
9
# ./spec/support/shared_contexts/a_logged_in_admin.rb    

shared_context 'a logged in admin' do
  before 'create and login admin' do
    user = create(:user, role: 'admin')
    user.confirm!
    login_as(user, scope: :user)
  end
end

Since all files located in spec/support are automatically required in the default spec_helper.rb, we can call the context from any spec. Now when we need to change how a user is logged in, we just have to change the code in our shared context and without having to change our specs.


Testing Outgoing Command Messages With RSpec

As the size and complexity of the applications I have been working on has grown, the importance of writing good unit test has become increasingly apparent. The goal is to have a test suite that is fast and able to withstand changes.

One of the best approaches I have found to writing good unit tests is discussed by Sandi Metz in Practical Object Oriented Design in Ruby and also in a talk she gave at RailsConf in 2013.

She has a rule about testing outgoing command messages that says, “expect to send outgoing command messages”. This means that we should assert that the method actually issues the command message, but we don’t assert the result of that message.

For example, suppose I have a party, and I want users to be able to RSVP:

1
2
3
4
5
class User
  def rsvp(party)
    party.add_guest(self)
  end
end

I want to assert that when user.rsvp(party) is called, the add_guest message is sent to the party object. Mocks are the perfect tool for testing these command messages because they allow us to test that the correct message is being passed without worrying about how the other object actually deals with that message:

1
2
3
4
5
6
7
8
9
10
describe User do
  describe '#rsvp' do
    it "tells the party to add the user as a guest" do
      user = User.new
      party = double('party')
      expect(party).to receive(:add_guest).with(user)
      user.rsvp(party)
    end
  end
end

This allows us to test the rsvp method without having to actually deal with the Party class’ implementation of add_guest. But what happens if we change the public interface for add_guestto allow a guest specify how many to friends they plan to bring to the party?

1
2
3
4
5
6
7
8
9
10
11
class Party
  def add_guest(user, number_of_companions)
    ...
  end
end

class User
  def rsvp(party)
    party.add_guest(self)
  end
end

Our rsvp method is now broken because the add_guest method requires two arguments, but we are only passing it one. Unfortunately our test for rsvp still passes because our test double does not reflect the actual interface that it is mocking.

To solve this problem I recently started using a mocking framework called Bogus. It ensures that your test doubles have the same interface as the real class.

1
2
3
4
5
6
7
8
9
10
11
12
require 'bogus/rspec'

...
  describe '#rsvp' do
    it "tells the party to add the user as a guest" do
      user = User.new
      party = fake(:party)
      user.rsvp(party)
      expect(party).to have_received.add_guest(user)
    end
  end
...

Now when we run the test we receive the following failure:

1
2
3
4
5
6
Failures:

  1) User#rsvp tells the party to add the user as a guest
     Failure/Error: party.add_guest(self)
     ArgumentError:
       wrong number of arguments (1 for 2)

Great! Our test double now implements the same interface as the actual Party class and alerts us that our rsvp method is not passing the correct number of arguments. Let’s update rsvp:

1
2
3
4
5
class User
  def rsvp(party)
    party.add_guest(self, 0)
  end
end

And run our test:

1
2
3
4
5
6
Failures:

  1) User#rsvp tells the party to add the user as a guest
     Failure/Error: expect(party).to have_received.add_guest(user)
     ArgumentError:
       tried to stub add_guest(user, number_of_companions) with arguments: #<User:0x007fa49210caa0>

Whoops! Looks like our stub for add_guest doesn’t implement the correct interface for add_guest. Let’s update it:

1
2
3
4
5
6
7
8
9
...
  describe '#rsvp' do
    it "tells the party to add the user as a guest" do
      ...
      expect(party).to have_received.add_guest(user, 0)
      ...

Finished in 0.006 seconds
1 example, 0 failures

Another great feature of Bogus is that it supports mocking a duck type. For example, suppose that in addition to parties, we want users to be able to rsvp to dinners. We can implement this with a duck type by creating an add_guest method for our new Dinner class:

1
2
3
4
5
6
7
8
9
10
11
class Dinner
  def add_guest(user, number_of_companions)
  ...
  end
end

class User
  def rsvp(guestable)
    guestable.add_guest(self, 0)
  end
end

And now in our test we can mock our duck type interface:

1
2
3
4
5
6
7
8
9
10
describe User do
  describe '#rsvp' do
    it "tells the guestable to add the user as a guest" do
      user = User.new
      guestable = fake(:guestable) { [Party, Dinner] }
      user.rsvp(guestable)
      expect(guestable).to have_received.add_guest(user, 0)
    end
  end
end

Mocks are a great way to focus on the messages that your objects are sending when testing. Give them a try and experience the benefits of a faster more resilient test suite.


Rake

Rake is something I have been using since day one with Rails, but that I really haven’t gone any deeper with than the basic rails rake tasks. Today I had to write a rake tasks that ran some checks and updated records.

Here’s what I learned:

  1. This article by Jason Seifer was a great introduction to rake. The big idea is that rake has namespaces and inside those namespaces you define tasks.

     namespace :feed do
       desc "Feed dog" 
       task :dog => :environment do
         puts "fed the dog"
       end
    
       desc "Feed cat"
       task :cat => :environment do 
         puts "fed the cat"
       end
    
       desc "Feed dog and cat"
       task :all => [:dog, :cat]
     end
    
  2. Rake tasks should be tested. Thoughtbot provides a really nice shared context that makes testing very easy.


Continuous Deployment

I have been hearing a lot recently about continuous deployment especially in the context of building a lean startup, so I decided to try and get one of my projects running with continuous deployment.

Here’s what I learned today:

  1. Travis CI is an open source tool for continuous integration and deployment.

  2. There are several steps to setting up your project with Travis CI:

    • The getting started guide does a good job explaining the basics.

    • For my project, I needed to make a few customizations the biggest one was creating a test database that my test could use. Here is my .travis.yml:

               language: ruby
               rvm:
               - 2.0.0
               env:
               - DB=postresql
               script:
               - bundle exec rake db:test:prepare --trace
               - bundle exec rspec
               before_script:
               - psql -c 'create database my_app_test;' -U postgres 
      
  3. Travis CI integrates nicely with Heroku, so that your code be deployed after a successful build. I was having some problems deploying using Anvil, so I switched to using git. Here is what I added to my .travis.yml for deploying to Heroku:

     deploy:
       provider: heroku
       api_key:
         secure: encrypted-key
       strategy: git
       app: epicodus-qa
       run: "rake db:migrate"
    

Database Indexes

Prior to today, I knew database indexes were something you used to help speed up a query, but I didn’t really understand their importance. I read this great Thoughtbot blog to learn more about them.

Here is what I learned today:

  1. When you perform a database query on a column without an index the database will look at each row and compare its value with the query value. If you don’t have many records this isn’t a big deal, but when you are talking about thousands or millions of rows that is a lot of work! Indexes provide a quick reference for the database, so it can retreive the matching records without having to look at every row.

  2. If you are using PostgreSQL (and apparently most other SQL databases), the primary key is index automatically. This means that in Rails the “id” column is indexed automatically because Rails tells the database the “id” column is the primary key.

  3. Basically any column you have to query for a set of records should be indexed.

  4. There are two downsides to adding an index. First, anytime you INSERT or UPDATE, the database must write both the record and the index reference which means more work for the database. Second, if you are creating an index for an existing set of records it could take a while. The Thoughtbot blog talks about a database migration that took 7 hours!


First!

They say starting is the hardest part. My initial idea for this blog is to post the things I learned during the day and the resources that helped me learn them.

Here is what I learned today:

  1. You can host a blog on Github using Github Pages. There is a framework called Octopress to help make things easy. I followed the documentation for initial setup and launched this blog.

  2. Deploying the changes for the blog can be done with the following steps:

    Generate the blog with

    rake generate

    Deploy

    rake deploy

    Commit the source for the blog.

    git add . git commit -m 'your message' git push origin source

  3. You can preview changes to the blog before deploying using the following

    rake preview