Documentation

Migrations

Browse documentation

Read this page as HTML, Markdown, or structured JSON—or open the published Markdown with an AI assistant. Gemini receives the prompt through your clipboard because its signed-out page does not reliably prefill URL text; paste when the new tab opens. External assistants need the public site URL.

Migrations

This section is based on Jennifer Docs.

To generate a migration run crystal src/sam.cr generate:migration your_migration_name

Migration DSL

The generator will create template file for you with a consistent name pattern timestamp_migration_name.cr. The empty file looks like this:

Crystal
class YourCamelcasedMigrationName20170119011451314 < Jennifer::Migration::Base
  def up
  end

  def down
  end
end

The up method is where your database changes go, whereas, down is used for reverting your changes back.

Example for creating table:

Crystal
create_table(:addresses) do |t|
  t.reference :contact # creates field contact_id with Int type and allows null values
  t.string :street, {:size => 20, :sql_type => "char"} # creates string field with CHAR(20) db type
  t.bool :main, {:default => false} # sets false as default value
end

Data Types and Mappings

internal alias PostgreSQL MySql Crystal type
#integer int int Int32
#string varchar(254) varchar(254) String
#bool boolean bool Bool
#char char - String
#float real float Float32
#double double precision double Float64
#short smallint smallint Int16
#timestamp timestamp timestamp Time
#date_time datetime datetime Time
#blob blob blob Bytes
#var_string varchar(254) varstring String
#json json json JSON::Any
#enum enum enum String

Also if you use PostgreSQL array types are available a well: Array(Int32), Array(Char), Array(Float32), Array(Float64),Array(Int16), Array(Int32), Array(Int64), Array(String).

All of them accepts additional options:

  • :sql_type- gets exact (except size) field type;
  • :null- represent nullable if field (by default isfalsefor all types and field);
  • :primary- marks field as primary key field (could be several ones but this provides some bugs with query generation for such model - for now try to avoid this).
  • :default- default value for field
  • :auto_increment- marks field to use auto increment (properly works only withInt32fields, another crystal types have cut functionality for it);
  • :array- mark field to be array type (Postgres only)

Also there is#fieldmethod which allows to directly define sql type (very suitable for enums in Postgres).

To drop table just write

Crystal
drop_table(:addresses) # drops if exists

To alter existing table use next methods:

  • #change_column(name, [new_name], options)- to change column definition; Postgres has slighly another implementation of this than mysql one - check source code for details;
  • #add_column(name, type, options)- add new column;
  • #drop_column(name)- drops existing column
  • #add_index(name : String, field : Symbol, type : Symbol, order : Symbol?, length : Int32?)- adds new index (Postgres doesn't support length parameter and only support:uniquetype);
  • #drop_index(name : String)- drops existing index;
  • #rename_table(new_name)- renames table.

Also next support methods are available:

  • #table_exists?(name)
  • #index_exists?(table, name)
  • #column_exists?(table, name)
  • #data_type_exists?(name)for Postgres ENUM

Also plain SQL could be executed as well:

Crystal
execute("ALTER TABLE addresses CHANGE street st VARCHAR(20)")

All changes are executed one by one so you also could add data changes here (inupmethod) but if execution ofupmethod fails -downmethod will be called and all process will stop - be ready for such behavior.

To be sure that your db is up to date before run tests of your application, add:

Output
Jennifer::Migration::Runner.migrate

Enum

Now enums are supported as well but it has different implementation for adapters. For mysql is enough just write down all values:

Crystal
create_table(:contacts) do |t|
  t.enum(:gender, values: ["male", "female"])
end

Postgres provide much more flexible and complex behavior. Using it you need to create it firstly:

Crystal
create_enum(:gender_enum, ["male", "female"])
create_table(:contacts) do |t|
  t.string :name, {:size => 30}
  t.integer :age
  t.field :gender, :gender_enum
  t.timestamps
end
change_enum(:gender_enum, {:add_values => ["unknown"]})
change_enum(:gender_enum, {:rename_values => ["unknown", "other"]})
change_enum(:gender_enum, {:remove_values => ["other"]})

For more details check source code and PostgreSQL docs.