README.md

# EctoAutoMigrator

Ecto documentation proposes a way to run migrations outside of using mix here:

- https://hexdocs.pm/phoenix/releases.html#ecto-migrations-and-custom-commands

However, in a number of cases, eg whole system apps such as Nerves and the like,
we actually want to run the migrations at app startup.

The solution to this is to create a genserver compatible module, which itself
runs the migrations and we can then insert this into our children (just after
we start the Repo perhaps) in application.ex (or wherever we start the repo)

However, we likely still want control over when we run the migrations, eg
while developing some new migrations you don't want every invocation of mix
running your migrations, nor watchers helpers, or editor plugins continuously
causing the migrations to fire. So we conditionally run the migrations based on
a config entry "run_migrations", which itself would be recommended to be set
based on an environment variable

eg:

```elixir
  config :my_app, run_migrations: System.get_env("RUN_MIGRATIONS")
```

To implement our module it should generally be sufficient to create a new Migrator
module. Optionally any of the callback functions can be overridden, eg the original
use case for this module was where the migrations absolutely must not fail. To achieve
this a custom "migrate()" function could be used which performs some sensible recovery
action on migration failure, eg destroying the database and migrating from scratch,
or restoring a backup, or ignoring the migration and raising an error, etc.

eg it's often sufficient to do

```elixir
defmodule MyApp.Repo.Migrator
  use Ecto.AutoMigrator
end
```

and then in application.ex add "MyApp.Repo.Migrator" to your list of children,
somewhere after the Repo is started

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `ecto_auto_migrator` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:ecto_auto_migrator, "~> 0.1"}
  ]
end
```

## Advanced usage

My use case was for a headless system, so I needed to ensure startup could succeed. For this
use case I therefore decided to blow away the DB if migrations failed and try to re-run from scratch
(Obviously choose an appropriate retry strategy for your own situation, eg continue and log an error, etc)

This can be implemented as follows:

```elixir
defmodule Database.Repo.Migrator do
  use Ecto.AutoMigrator
  require Logger

  @doc """
  Entry point

  Run DB migrations and try to ensure they succeed.
  Specifically we will delete all the DBs if migrations fail and try to re-run migrations from scratch
  """
  @impl true
  def migrate() do
    if run_migrations?() do
      load_app()

      try_migrations_1(repos())
    end

    :ok
  end

  # Run migrations, if they fail then blow away the DBs and retry the migrationss from scratch
  defp try_migrations_1(repos) do
    case try_migrations(repos) do
      :error ->
        Logger.critical("migration failure. Purging databases to attempt to continue")

        delete_databases(repos)

        # Retry from scratch and hope we can complete
        try_migrations_2(repos)

      :ok ->
        :ok
    end
  end

  defp try_migrations_2(repos) do
    case try_migrations(repos) do
      :error ->
        Logger.critical("migration retry failure. Continuing, but anticipate that app is unstable")
        :error

      :ok ->
        :ok
    end
  end

  # Delete all database files associated with all 'repos'
  # Currently assumes sqlite DBs
  defp delete_databases(repos) do
    for repo <- repos do
      repo.__adapter__.storage_down(repo.config)
      repo.__adapter__.storage_up(repo.config)
      # Purge all in use connections or we will still be using the old DB files
      repo.stop(5)
    end
  end

  # Try and run migrations, wrapping any exceptions and converting to :error/:ok result
  defp try_migrations(repos) do
    try do
      run_all_migrations(repos)
    rescue
      _ -> :error
    else
      _ -> :ok
    end
  end
end
```