View Source BitcrowdEcto.Migrator behaviour (bitcrowd_ecto v1.0.0)

Release migration logic for repositories.

Can deal with normal repositories & multi-tenant repositories.

Usage

In the simplest case, add the Migrator to your repository:

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app, adapter: Ecto.Adapters.Postgres

  use BitcrowdEcto.Migrator
end

and call the migrator from your release:

$ bin/my_app eval 'MyApp.Repo.up()'

Multi-Tenant repositories

For multi-tenant repositories, you need to provide a list of tenants (= PG schemas) by overriding the known_prefixes/0 function on your repository:

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app, adapter: Ecto.Adapters.Postgres

  use BitcrowdEcto.Migrator

  def known_prefixes do
    ["tenant_a", "tenant_b"]
  end
end

This will make the migrator apply migrations from the priv/repo/tenant_migrations directory onto schemas tenant_a and tenant_b. The schemas will be created if necessary.

Mix tasks for development

In normal development without multi-tenancy, the usual Ecto mix tasks will work just fine.

When using tenant schemas, the normal Ecto mix tasks will only apply the "global" (i.e. non-prefixed migrations) from priv/repo/migrations, which is not enough. You can define your own Mix tasks calling the up/0 and down/1 functions on your repository:

defmodule Mix.Tasks.MyApp.Migrate do
  use Mix.Task

  @shortdoc "Migrates our repository"
  @moduledoc "Migrates the repository including the tenant schemas"

  @impl true
  def run(args) do
    Mix.Task.run("app.config", args)

    MyApp.Repo.up()
  end
end

defmodule Mix.Tasks.MyApp.Rollback do
  use Mix.Task

  @shortdoc "Rolls back our repository"
  @moduledoc "Rolls back the repository including the tenant schemas"

  @impl true
  def run(args) do
    Mix.Task.run("app.config", args)

    {[to: to], _} = OptionParser.parse!(args, strict: [to: :integer], aliases: [])

    MyApp.Repo.down(to)
  end
end

Stopping the application if migrations are down

In some setups (e.g. parallel starts of Kubernetes jobs for migrations and your app workload) you may want to abort startup if migrations haven't been applied. Use the ensure_up!/0 function to raise an exception in such cases.

$ bin/my_app eval 'MyApp.Repo.ensure_up!()'
[...]
** (RuntimeError) Migrations not up!

  20220101120000_create_foo_table.exs
[...]

$ echo $?
1

$ bin/my_app eval 'MyApp.Repo.up()'
[...]

$ bin/my_app eval 'MyApp.Repo.ensure_up!()'
[...]

$ echo $?
0

Summary

Callbacks

Rolls back both the main schemas/tables and the tenant schemas to a given version.

Checks that all migrations have been run or raises an exception otherwise.

Called when the migrator experiences an exception.

Returns the list of prefixes used on this repository.

Migrates both the "main" (i.e. non-tenant) schemas/tables and the tenant schemas to their latest version.

Callbacks

@callback down(to :: non_neg_integer()) :: :ok

Rolls back both the main schemas/tables and the tenant schemas to a given version.

Link to this callback

ensure_up!()

View Source (since 0.5.0)
@callback ensure_up!() :: :ok | no_return()

Checks that all migrations have been run or raises an exception otherwise.

Link to this callback

handle_migrator_exception(t, stacktrace)

View Source (since 0.4.0)
@callback handle_migrator_exception(Exception.t(), Exception.stacktrace()) :: any()

Called when the migrator experiences an exception.

Link to this callback

known_prefixes()

View Source (since 0.1.0)
@callback known_prefixes() :: [String.t()]

Returns the list of prefixes used on this repository.

@callback up() :: :ok

Migrates both the "main" (i.e. non-tenant) schemas/tables and the tenant schemas to their latest version.