A database migration solution 🚜
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
spec
src
.editorconfig
.gitignore
.travis.yml
LICENSE
README.md
shard.yml

README.md

Migrate

Built with Crystal Build status Docs Releases Awesome vladfaust.com Patrons count

A database migration tool for Crystal.

Become Patron

Installation

Add this to your application's shard.yml:

dependencies:
  migrate:
    github: vladfaust/migrate.cr
    version: ~> 0.4.0

This shard follows Semantic Versioning v2.0.0, so check releases and change the version accordingly.

Usage

Migration files

db/migrations/1.sql:

-- +migrate up
CREATE TABLE foo (
  id      SERIAL PRIMARY KEY,
  content TEXT NOT NULL
);

-- Indexes (it's just a comment, no utility function)
CREATE UNIQUE INDEX foo_content_index ON foo (content);

-- +migrate down
DROP TABLE foo;

db/migrations/2_create_bar.sql:

-- +migrate up
CREATE TABLE bar (
  id      SERIAL PRIMARY KEY,
  content TEXT NOT NULL
);

-- Indexes
CREATE UNIQUE INDEX bar_content_index ON bar (content);

-- +migrate down
DROP TABLE bar;

db/migrations/10_create_baz.sql:

-- +migrate up
CREATE TABLE baz (
  id      SERIAL PRIMARY KEY,
  content TEXT NOT NULL
);

-- Indexes
CREATE UNIQUE INDEX baz_content_index ON baz (content);

-- +migrate down
DROP TABLE baz;

Migration in the code

All migrations are applied in a single transaction. That means that if a migration is invalid, all statements in this transaction will be rolled back.

require "pg"
require "migrate"

migrator = Migrate::Migrator.new(
  DB.open(ENV["DATABASE_URL"]),
  Logger.new(STDOUT),
  File.join("db", "migrations"), # Path to migrations
  "version", # Version table name
  "version" # Version column name
)

migrator.current_version  # => 0
migrator.next_version     # => 1
migrator.previous_version # => nil

migrator.up
# =>  INFO -- : Migrating up to version 0 β†’ 1
# =>  INFO -- : Successfully migrated from version 0 to 1 in 37.602ms
migrator.current_version  # => 1
migrator.previous_version # => 0

migrator.down
# =>  INFO -- : Migrating down to version 1 β†’ 0
# =>  INFO -- : Successfully migrated from version 1 to 0 in 27.027ms
migrator.current_version # => 0

migrator.to(10)
# =>  INFO -- : Migrating up to version 0 β†’ 1 β†’ 2 β†’ 10
# =>  INFO -- : Successfully migrated from version 0 to 10 in 62.214ms
migrator.current_version # => 10
migrator.next_version    # => nil

migrator.redo
# =>  INFO -- : Migrating down to version 10 β†’ 2 β†’ 1 β†’ 0
# =>  INFO -- : Successfully migrated from version 10 to 0 in 30.006ms
# =>  INFO -- : Migrating up to version 0 β†’ 1 β†’ 2 β†’ 10
# =>  INFO -- : Successfully migrated from version 0 to 10 in 72.877ms
migrator.current_version # => 10

migrator.reset
# =>  INFO -- : Migrating down to version 10 β†’ 2 β†’ 1 β†’ 0
# =>  INFO -- : Successfully migrated from version 10 to 0 in 28.958ms
migrator.current_version # => 0

migrator.to_latest
# =>  INFO -- : Migrating up to version 0 β†’ 1 β†’ 2 β†’ 10
# =>  INFO -- : Successfully migrated from version 0 to 10 in 39.189ms
migrator.current_version # => 10

Errors

A special command +migrate error is available. It raises Migrate::Migration::Error when a specific migration file is run. A error can be either top-level or direction-specific. This is useful to point out irreversible migrations:

-- +migrate up
CREATE TABLE foo;

-- +migrate down
-- +migrate error Could not migrate down from this point
-- +migrate error Could not run this migration file at all

Cakefile

Note that Cakefile doesn't support task arguments (that means that Migrator#to will not be available). Also see cake-bake for baking Cakefiles (this could be helpful in Docker deployments).

require "pg"
require "migrate"

desc "Migrate Database to the latest version"
task :dbmigrate do
  migrator = Migrate::Migrator.new(ditto)
  migrator.to_latest
end

Usage:

$ cake db:migrate
 INFO -- : Migrating up to version 0 β†’ 1 β†’ 2 β†’ 10
 INFO -- : Successfully migrated from version 0 to 10 in 33.46ms

Sam.cr

require "sam"
require "migrate"

Sam.namespace "db" do
  migrator = Migrate::Migrator.new(ditto)

  task "migrate" do
    migrator.to_latest
  end
end

Usage:

crystal sam.cr -- db:migrate
ditto

Testing

  1. Create an empty PostgreSQL database (e.g. migrate)
  2. cd migrate
  3. env DATABASE_URL=postgres://postgres:postgres@localhost:5432/migrate crystal spec

Contributing

  1. Fork it ( https://github.com/vladfaust/migrate.cr/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors