Integrating ActiveRecord with an SQLite3 database and RSpec Shoulda matchers into your Sinatra app
Jun 5, 2015
I ran into a lot of trouble getting this set up! Finally, everything fell into place.
While planning out a Sinatra app for a new project, I decided to use ActiveRecord to handle my data storage.
Adding ActiveRecord to your Sinatra app
Thankfully, there’s the very helpful sinatra-activerecord gem. While we’re at it, we’ll add the sqlite3 gem:
gem 'sinatra-activerecord'
gem 'sqlite3'
Having done this, we can establish a database connection in our Sinatra app.
require 'sinatra/activerecord'
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: 'db.sqlite3'
)
Great! Now we can add those lovely active-record Rake tasks into our Rakefile:
require 'sinatra/activerecord/rake'
# Make sure to require your Sinatra app to get the database connection
require './app'
If we run rake -T, we’ll now see a plethora of new rake tasks, all database-related:
rake db:create # Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config)
rake db:create_migration # Create a migration (parameters: NAME, VERSION)
rake db:drop # Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config)
rake db:fixtures:load # Load fixtures into the current environment's database
rake db:migrate # Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)
rake db:migrate:status # Display status of migrations
rake db:rollback # Rolls the schema back to the previous version (specify steps w/ STEP=n)
rake db:schema:cache:clear # Clear a db/schema_cache.dump file
rake db:schema:cache:dump # Create a db/schema_cache.dump file
rake db:schema:dump # Create a db/schema.rb file that is portable against any DB supported by AR
rake db:schema:load # Load a schema.rb file into the database
rake db:seed # Load the seed data from db/seeds.rb
rake db:setup # Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the database first)
rake db:structure:dump # Dump the database structure to db/structure.sql
rake db:structure:load # Recreate the databases from the structure.sql file
rake db:version # Retrieves the current schema version number
Creating an ActiveRecord migration and model
Great! Let’s create our migration to add a User model:
rake db:create_migration NAME=create_users_table
Just like in rails, this command will create the db/migrate folder and inside it a migration file. Here, we can write up our migration:
class CreateUsersTable < ActiveRecord::Migration
# Run with `rake db:migrate`
def up
create_table :users do |table|
table.string :username
table.string :password
end
end
# Run with `rake db:rollback`
def down
drop_table :users
end
end
Running rake db:migrate will get our database and Users table up and good to go.
Now we can add our ActiveRecord model for a User:
class User < ActiveRecord::Base
end
This next part is vital. You need to establish the connection to the database before requiring the model in the Sinatra app:
ActiveRecord::Base.establish_connection(
adapter: 'sqlite3',
database: 'db.sqlite3'
)
require_relative './models/user'
Doing otherwise requires you to re-establish the database connection for the user model.
Testing your ActiveRecord model with Shoulda matchers
I want to add some validations to my User model, namely, enforcing the presence of the username and password fields. We also want each user to have a unique username.
I’ll test this using RSpec and shoulda-matchers:
group :test do
gem 'rspec'
gem 'shoulda-matchers'
end
When you set up your spec_helper file, it’s very important to require the Sinatra app before the test gems:
require File.expand_path '../../app', __FILE__
require 'rspec'
require 'rack/test'
require 'sinatra'
require 'shoulda/matchers'
We’re now ready to test our User model. Don’t forget to require your spec_helper:
describe User do
it { is_expected.to validate_presence_of(:username) }
it { is_expected.to validate_uniqueness_of(:username) }
it { is_expected.to validate_presence_of(:password) }
end
Running these will, of course, fail. So let’s implement these validations!
validates :username, :password, presence: true
validates_uniqueness_of :password
That about does it! We can now use rails-like models and validation tests in our Sinatra app.
I hope this is helpful. It took me forever to get it working!