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.
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: email@example.com
Pulling in the 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 testenvironment, 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:
- Add a
requirestatement to pull in ActiveRecord
- 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'
See the next section for an example.
Let’s say we want to manage Resources. First, let’s create a class for them. Put it in
app.rb, above the first
class Resource < ActiveRecord::Base validates :name, presence: true, uniqueness: true end
Now that we have our Resource class, we can implement the CRUD operationsfor it. We start by extending the
get '/' do json Resource.select('id', 'name').all end
Next up is the
get '/:id' do resource = Resource.find_by_id(params[:id]) if resource halt 206, json resource else halt 404 end end
post '/' do resource = Resource.create(params) if resource json resource else halt 500 end end
patch '/:id' do resource = Resource.find_by_id(params[:id]) if resource resource.update(params) else halt 404 end end
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.
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.
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!