In July of 2017, CircleCI released their new platform, 2.0. It’s much more powerful and flexible, but it’s also more complex to get up and running with for Rails apps. Let’s walk through it…
A Base Configuration
The CircleCI configuration file is now in .circleci/config.yml
. Initially,
it should start looking something like this:
---
version: 2
jobs:
build:
working_directory: ~/your-app-name
steps:
- checkout
This doesn’t do all that much (and wouldn’t work on its own). But it’s the basic shell of the config file we’ll need. These parts will be there throughout.
Choosing a base Docker Image
For our use case, the best choice is the Docker executor, which will compose the environment from a set of Docker images. CircleCI provide set of pre-built images that we’ll use. These cover lots of different Ruby versions, with variations for common requirements like a browser and/or JavaScript.
These variations can be specified with tags on the Docker image, like so:
---
version: 2
jobs:
build:
working_directory: ~/your-app-name
docker:
- image: circleci/ruby:2.4.1
environment:
RAILS_ENV: test
steps:
- checkout
# Setup the environment
- run: cp .sample.env .env
# Run the tests
- run: bundle exec rake
This adds the Ruby image for 2.4.1
. If we need a browser, we might use the
2.4.1-browsers
tag (for Selenium, for example). If we have JavaScript tests,
we might use 2.4.1-node
and if we have both we might want
2.4.1-node-browsers
.
Now we have an environment we can run in, we’ll also go about including what we need to run the tests. Yours might vary here.
Services
A Rails app is rarely without a database and we can introduce that with another Docker image. We’ll use PostgreSQL here, but others would work in the same manner:
---
version: 2
jobs:
build:
working_directory: ~/your-app-name
docker:
- image: circleci/ruby:2.4.1
environment:
PGHOST: localhost
PGUSER: your-app-name
RAILS_ENV: test
- image: postgres:9.5
environment:
POSTGRES_USER: your-app-name
POSTGRES_DB: your-app-name_test
POSTGRES_PASSWORD: ""
steps:
- checkout
# Setup the environment
- run: cp .sample.env .env
# Setup the database
- run: bundle exec rake db:setup
# Run the tests
- run: bundle exec rake
This adds a postgres
image, but also adds some environment configuration.
We provide similar values to what we’re using in config/database.yml
.
Specifying POSTGRES_DB
on the postgres
image ensures that the correct
database is created.
Additionally here, we add the rake
commands for configuring our database.
Our configuration file should now be successfully running tests. It was at this point that I found I needed to iterate on the base Docker image to make sure I was covering all of my dependencies and most runs were passing successfully.
Avoiding race conditions
One problem we should take care of though is race conditions to do with how the additional services come up. It’s possible that Postgres might take slightly longer to be available and so our tests might start running …and then all fail.
To solve this, we can use dockerize
to wait for the appropriate port to be
available. Add this as a run step:
- run: dockerize -wait tcp://localhost:5432 -timeout 1m
Caching dependencies
Running bundle
for each test run can be slow and is easy to cache. To give
us a little speed improvement, we can do something like this:
# Restore Cached Dependencies
- type: cache-restore
name: Restore bundle cache
key: your-app-name-{{ checksum "Gemfile.lock" }}
# Bundle install dependencies
- run: bundle install --path vendor/bundle
# Cache Dependencies
- type: cache-save
name: Store bundle cache
key: your-app-name-{{ checksum "Gemfile.lock" }}
paths:
- vendor/bundle
This will try and restore any cached dependencies using a checksum based on
your lock file. So if you add more things and it’s invalid it won’t try and
use something which isn’t useful. Installing dependencies to a specific path
allows us to use cache-save
to save that particular directory.
Putting it all together
All of these come together to look like this:
---
version: 2
jobs:
build:
working_directory: ~/your-app-name
docker:
- image: circleci/ruby:2.4.1
environment:
PGHOST: localhost
PGUSER: your-app-name
RAILS_ENV: test
- image: postgres:9.5
environment:
POSTGRES_USER: your-app-name
POSTGRES_DB: your-app-name_test
POSTGRES_PASSWORD: ""
steps:
- checkout
# Restore Cached Dependencies
- type: cache-restore
name: Restore bundle cache
key: your-app-name-{{ checksum "Gemfile.lock" }}
# Bundle install dependencies
- run: bundle install --path vendor/bundle
# Cache Dependencies
- type: cache-save
name: Store bundle cache
key: your-app-name-{{ checksum "Gemfile.lock" }}
paths:
- vendor/bundle
# Wait for DB
- run: dockerize -wait tcp://localhost:5432 -timeout 1m
# Setup the environment
- run: cp .sample.env .env
# Setup the database
- run: bundle exec rake db:setup
# Run the tests
- run: bundle exec rake
You can see this configuration in action on administrate.