lib/auto_migrator.ex

defmodule Ecto.AutoMigrator do
  @callback migrate :: :ok
  @callback rollback(atom, any) :: {:ok, any, any}
  @callback load_app :: :ok | {:error, any}
  @callback repos :: list(atom())
  @callback run_migrations? :: boolean

  defmacro __using__(_opts) do
    quote do
      @behaviour Ecto.AutoMigrator

      if Module.get_attribute(__MODULE__, :doc) == nil do
        @doc """
        Automatic migrations module which can be added to the supervision tree
        """
      end

      @app Mix.Project.config()[:app]
      use GenServer

      def start_link(args) do
        GenServer.start_link(__MODULE__, args, [])
      end

      @impl true
      @spec init(any) :: :ignore
      def init(_args) do
        migrate()

        :ignore
      end

      def migrate() do
        if run_migrations?() do
          load_app()

          run_all_migrations(repos())
        end

        :ok
      end

      def run_all_migrations(repos) do
        for repo <- repos do
          {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
        end
      end

      def rollback(repo, version) do
        load_app()
        {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
      end

      def load_app() do
        Application.load(@app)
      end

      def repos() do
        Application.fetch_env!(@app, :ecto_repos)
      end

      def run_migrations?() do
        case Application.fetch_env(@app, :run_migrations) do
          {:ok, val} -> !!val
          :error -> false
        end
      end

      defoverridable Ecto.AutoMigrator
    end
  end
end