The Neuronen Schmiede concerns itself with topics related to software development. One focus are robust web applications.

Adding Migrations to a Lotus Project

Permalink

Until Lotus supports migrations out of the box you have to roll your own solution.1 Thankfully this is not so difficult, let me show you how to do it. The following setup will actually work in any Ruby project that already leverages Sequel. In the end you will have following things:

  • A place to store your migration files.
  • A Rake task to run the migrations.
  • A Rake task to rollback the last migration.

The projects Rakefile is the place to start. First you tell Rake where to find your custom Rake tasks and then you add an environment task on which other tasks can depend upon. This task is responsible for loading the applications environment. Your migration tasks need the environment in order to know which database should be used for the migrations.

Rakefile - Tell Rake where to find the custom tasks

Rake.add_rakelib 'lib/tasks'

Rakefile - Add the environment task

task :environment do
  require_relative './config/environment'
  Lotus::Application.preload!
end

The environment task uses Lotus::Application.preload! to load the Lotus environment. This does not load any of your application code. If you want to load the application code use Lotus::Application.preload_applications! but be aware of the healthy migration habits and don't rely on application specific code in your migrations.

Both the migrate and rollback tasks are heavily inspired by the official Sequel migrations guide. The tasks depend on the environment task you wrote earlier and require the Sequel gem with the migration extension. In order to run all migrations you need a connection to a database and a directory where the migrations are stored.

The environment variable DATABASE_URL is used to connect to the database, make sure to change this so it matches your own project environment. Your migrations are supposed to be in the db/migrations directory.

Rolling back a migration is a little more complicated. You need to tell Sequel a target version you want to roll back to. Since you want to roll back a single version per rollback invocation you can automate the process of finding the correct version. Be aware that Sequel supports an IntegerMigrator and a TimestampMigrator. There are a few differences but for the task at hand only two are relevant: The naming of the migration files and where Sequel stores information about the schema.

This article focuses on the TimestampMigrator, therefore the schema information is stored in the schema_migrations table which can be queried to find the second to last migration. Then you have to extract the version, that's the timestamp at the beginning, from the filename. Once you have determined the version you can use the same command as in the migrate task and provide an additional target argument.

lib/tasks/db.rake

namespace :db do
  desc 'Run migrations'
  task :migrate => :environment do
    require 'sequel'

    Sequel.extension :migration
    db = Sequel.connect ENV.fetch('DATABASE_URL')

    Sequel::Migrator.run db, 'db/migrations'
  end

  desc 'Rollback last migration'
  task :rollback => :environment do
    require 'sequel'

    Sequel.extension :migration
    db = Sequel.connect ENV.fetch('DATSU_LOTUS_DATABASE_URL')
    table_name = :schema_migrations

    if db.tables.include?(table_name) && db[table_name].count > 1
      last_two_migrations = db[table_name].order(:filename).last(2)
      filename = last_two_migrations[1][:filename]
      version = /^(\d{14})\D*/.match(filename).captures[0].to_i
    else
      version = 0
    end

    Sequel::Migrator.run db, 'db/migrations', target: version
  end
end

Having all this in place the only thing left to do for you is writing migrations. Sequel has an excellent documentation on what schema modifications are possible and the migrations guide will help you understand what can be done in a migration.

You can run your migrations with bundle exec rake db:migrate and roll back the last migration with bundle exec rake db:rollback.

An example migration with this setup might look like this:

db/migrations/20150404175000_create_identities_table.rb

Sequel.migration do
  change do
    create_table(:identities) do
      primary_key :id
      String :email, null: false
      String :password_digest, null: false

      index :email, unique: true
    end
  end
end
  1. You can track the progress for the generator, migrate and rollback support on GitHub.