How to set up Sinatra with ActiveRecord

In my series Learning Docker, I document my steps to use Sinatra with Docker. Part of this journey is to set up Sinatra with a database, and because I love ActiveRecord I want to use it as well. In this post, I am going to show you how to do that.

Introduction

In case you are not familiar with my series and its goals, I’ll quickly give you an overview:

For one of my projects I decided to take a look at Docker, and build my application using services. Most of these I want to build as JSON APIs with Sinatra, since I like how lightweight yet extensible it is. The first parts of the series dealt mostly with setting up a very basic Sinatra app and getting Docker to run.

If you want to inspect the source code of the application as it is now, follow this link: docker-sinatra-api@0.2.0

Pulling in the dependencies

The first step to get Sinatra running with ActiveRecord is define and install the required dependencies.

Let’s start by adding the gem pg to our Gemfile. This is the database adapter for PostgreSQL, which are are going to use in production. We are also adding sqlite3 for the development and test environment.

group :production do
  # Use Postgresql for ActiveRecord
  gem 'pg'
end

group :development, :test do
  # Use SQLite for ActiveRecord
  gem 'sqlite3'
end

Next, we need to configure our application. As I mentioned, I am a big fan of ActiveRecord, and want to use it to handle my database connection for me. While we are at it, we are also adding Rake. ActiveRecord’s gem includes all the tasks you might be used to from Ruby on Rails, and with Rake you can use them in just the same way as in Ruby on Rails:

# Use ActiveRecord as the ORM
gem 'sinatra-activerecord', '~> 2.0'

# Use rake to execute ActiveRecord's tasks
gem 'rake'

Now that we have the right tools in place, let’s look at the configuration.

Configuring the database

To make everything as comfortable as possible for you, we are going to follow the conventions of Ruby on Rails and put the database configuration in config/database.yml. With SQLite for the development and test environment, and PostgreSQL for production, our configuration file looks like this:

default: &default
  adapter: sqlite3
  pool: 5
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

test:
  <<: *default
  database: db/test.sqlite3

production:
  adapter: postgresql
  encoding: unicode
  pool: 5
  host: <%= ENV['DATABASE_HOST'] || 'db' %>
  database: <%= ENV['DATABASE_NAME'] || 'sinatra' %>
  username: <%= ENV['DATABASE_USER'] || 'sinatra' %>
  password: <%= ENV['DATABASE_PASSWORD'] || 'sinatra' %>

The production environment is configured via environment variables, what allows us to keep our sensitive passwords out of the code base and out of version control. In case the variables are not set, we fall back to default values that are also going to configure in Docker as the credentials for the database. This makes it easy to use the application with Docker for development purposes.

Configuring the application

The next step is to make ActiveRecord available to our application. While we have installed the gem and provided its configuration, we are not yet able to use it within our code.

There are two things we need to do:

  1. Add a require statement to pull in ActiveRecord
  2. Tell it where to find its configuration

Look at the beginning of our app.rb file to see how this is done:

require 'sinatra'
require 'sinatra/json'
require 'sinatra/activerecord'

set :database_file, 'config/database.yml'

This is all that is necessary to get ActiveRecord working with a Sinatra application. You can now define your classes just like you would in Ruby on Rails.

See the next section for an example.

Using ActiveRecord

Let’s say we want to manage Resources. First, let’s create a class for them. Put it in app.rb, above the first get statement.

class Resource < ActiveRecord::Base
  validates :name, presence: true, uniqueness: true
end

Now that we have our Resource class, we can implement the CRUD operations for it. We start by extending the #index action:

get '/' do
  json Resource.select('id', 'name').all
end

Next up is the #show action:

get '/:id' do
  resource =  Resource.find_by_id(params[:id])

  if resource
    halt 206, json resource
  else
    halt 404
  end
end

Followed by #create:

post '/' do
  resource = Resource.create(params)

  if resource
    json resource
  else
    halt 500
  end
end

#update:

patch '/:id' do
  resource = Resource.find_by_id(params[:id])

  if resource
    resource.update(params)
  else
    halt 404
  end
end

And #delete:

delete '/:id' do
  resource = Resource.find_by_id(params[:id])

  if resource
    resource.destroy
  else
    halt 404
  end
end

These are very basic implementations, just to show you that everything works as expected. For an application that is aimed for production, I strongly advice you to do a more robust implementation than this.

Creating the table

Before we can do anything with this newly written code, we need to create the table for our Resource class. One of the nicest things about ActiveRecord is the support for migrations, which define changes you make to the database as code that can be run in a reproducible way. So let’s create a migration. Remember Rake?

$ rake db:create_migration NAME=create_resources

The output of this command is the file path to your newly created migration. Open it, and add the following instructions:

class CreateResources < ActiveRecord::Migration
  def change
    create_table :resources do |t|
      t.string :name, null: false, default: ''

      t.timestamps, null: false
    end

    add_index :resources, :name, unique: true
  end
end

Just like you would in Ruby on Rails, you can run this migration with the following command:

$ rake db:migrate

The migration created the table resources for you, with an implicit field id, the specified field name and timestamps containing the times of creation and the last update.

You can now start the application and browse to its URL. The output from the #index method should be [], since we don’t have created any resources yet.

Congratulations! You have successfully combined Sinatra with ActiveRecord.

Summary

Setting up Sinatra with ActiveRecord is not difficult. We installed the necessary dependencies via our Gemfile, configured the database connection in config/database.yml, created and ran a migration with Rake and finally added a little bit of configuration to our app.rb to make it use ActiveRecord.

The provided code examples are very basic implementations of what is possible, and I strongly recommend that you build a more solid API if you want to try Sinatra. Two things that immediately come to mind when thinking about areas to improve are authentication and pagination. Right now, everyone can create and delete resources like he wants. That is almost never what you want, though.

If you want to study the code more, have a look at the GitHub repository for my series on Learning Docker. The following version of the code is the result of this post:

docker-sinatra-api@0.3.0

You might want to check out the series as well, since it described how to pack everything we did today into a Docker container and make it portable. Start with the first post:

If you have any questions or suggestions for improvement, feel free to leave a comment below. I would love to hear from you.

Hope to see you in the next post!

Jan David