ash_postgres/documentation/topics/migrations_and_tasks.md
2023-10-24 12:33:17 -04:00

5.2 KiB

Migrations

Migration Generator Primer

Tasks

The available tasks are:

  • mix ash_postgres.generate_migrations
  • mix ash_postgres.create
  • mix ash_postgres.drop
  • mix ash_postgres.migrate (use mix ash_postgres.migrate --tenants to run tenant migrations)

AshPostgres is built on top of ecto, so much of its behavior is pass-through/orchestration of that tooling.

Basic Workflow

  • Make resource changes
  • Run mix ash_postgres.generate_migrations to generate migrations and resource snapshots
  • Run mix ash_postgres.migrate to run those migrations
  • Run mix ash_postgres.migrate --tenants as well if you have multi-tenant resources.

For more information on generating migrations, see the module documentation here: Mix.Tasks.AshPostgres.GenerateMigrations, or run mix help ash_postgres.generate_migrations

For running your migrations, there is a mix task that will find all of the repos configured in your apis and run their migrations. It is a thin wrapper around mix ecto.migrate. Ours is called mix ash_postgres.migrate

If you want to run or rollback individual migrations, use the corresponding

For tenant migrations (see the multitenancy guides for more) generated by multitenant resources, make sure you are using mix ash_postgres.generate_migrations. It is not sufficient to run mix ash_postgres.migrate --migrations_path tenant_migrations_path. You will also need to define a list_tenants/0 function in your repo module. See AshPostgres.Repo for more.

Regenerating Migrations

Often, you will run into a situation where you want to make a slight change to a resource after you've already generated and run migrations. If you are using git and would like to undo those changes, then regenerate the migrations, this script may prove useful:

#!/bin/bash

# Get count of untracked migrations
N_MIGRATIONS=$(git ls-files --others priv/repo/migrations | wc -l)

# Rollback untracked migrations
mix ecto.rollback -n $N_MIGRATIONS

# Delete untracked migrations and snapshots
git ls-files --others priv/repo/migrations | xargs rm
git ls-files --others priv/resource_snapshots | xargs rm

# Regenerate migrations
mix ash_postgres.generate_migrations

# Run migrations if flag
if echo $* | grep -e "-m" -q
then
  mix ecto.migrate
fi

After saving this file to something like regen.sh, make it executable with chmod +x regen.sh. Now you can run it with ./regen.sh. If you would like the migrations to automatically run after regeneration, add the -m flag: ./regen.sh -m.

Multiple Repos

If you are using multiple repos, you will likely need to use mix ecto.migrate and manage it separately for each repo, as the options would be applied to both repo, which wouldn't make sense.

Running Migrations in Production

Define a module similar to the following:

defmodule MyApp.Release do
  @moduledoc """
Tasks that need to be executed in the released application (because mix is not present in releases).
  """
  @app :my_app
  def migrate do
    load_app()

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

  # only needed if you are using postgres multitenancy
  def migrate_tenants do
    load_app()

    for repo <- repos() do
      repo_name = repo |> Module.split() |> List.last() |> Macro.underscore()

      path =
        "priv/"
        |> Path.join(repo_name)
        |> Path.join("tenant_migrations")
        # This may be different for you if you are not using the default tenant migrations

      {:ok, _, _} =
        Ecto.Migrator.with_repo(
          repo,
          fn repo ->
            for tenant <- repo.all_tenants() do
              Ecto.Migrator.run(repo, path, :up, all: true, prefix: tenant)
            end
          end
        )
    end
  end

  # only needed if you are using postgres multitenancy
  def migrate_all do
    load_app()
    migrate()
    migrate_tenants()
  end

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

  # only needed if you are using postgres multitenancy
  def rollback_tenants(repo, version) do
    load_app()
    repo_name = repo |> Module.split() |> List.last() |> Macro.underscore()

    path =
      "priv/"
      |> Path.join(repo_name)
      |> Path.join("tenant_migrations")
      # This may be different for you if you are not using the default tenant migrations

    for tenant <- repo.all_tenants() do
      {:ok, _, _} =
        Ecto.Migrator.with_repo(
          repo,
          &Ecto.Migrator.run(&1, path, :down,
            to: version,
            prefix: tenant
          )
        )
    end
  end

  defp repos do
    apis()
    |> Enum.flat_map(fn api ->
      api
      |> Ash.Api.Info.resources()
      |> Enum.map(&AshPostgres.DataLayer.Info.repo/1)
    end)
    |> Enum.uniq()
  end

  defp apis do
    Application.fetch_env!(@app, :ash_apis)
  end

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