ecto-release-migrations

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Ecto Release Migrations Setup

Ecto版本迁移设置

Overview

概述

In Elixir releases, Mix is not available. This skill creates a Release module that handles database creation, migration, and rollback via
bin/app_name eval
commands.
在Elixir版本发布中,Mix不可用。本技能会创建一个Release模块,通过
bin/app_name eval
命令处理数据库创建、迁移和回滚操作。

Workflow

工作流程

  1. Detect app name and repos
    • Check
      mix.exs
      for app name
    • Check
      config/config.exs
      or
      config/runtime.exs
      for Ecto repos
  2. Create Release module
    • Location:
      lib/<app_name>/release.ex
    • Must handle multiple repos if present
  3. Verify config
    • Ensure
      config/runtime.exs
      has production database config
    • Check for
      DATABASE_URL
      or explicit config
  1. 检测应用名称和仓库
    • 检查
      mix.exs
      获取应用名称
    • 检查
      config/config.exs
      config/runtime.exs
      获取Ecto仓库
  2. 创建Release模块
    • 位置:
      lib/<app_name>/release.ex
    • 若存在多个仓库,必须支持多仓库处理
  3. 验证配置
    • 确保
      config/runtime.exs
      包含生产环境数据库配置
    • 检查是否存在
      DATABASE_URL
      或显式配置

Implementation

实现

Release Module Template

Release模块模板

elixir
defmodule <AppName>.Release do
  @moduledoc """
  Release tasks for database management.
  
  Usage in production:
    bin/<app_name> eval "<AppName>.Release.migrate()"
    bin/<app_name> eval "<AppName>.Release.rollback(<AppName>.Repo, 20240101000000)"
  """

  @app :<app_name>

  def migrate do
    load_app()

    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 create do
    load_app()

    for repo <- repos() do
      case repo.__adapter__().storage_up(repo.config()) do
        :ok -> IO.puts("Database created for #{inspect(repo)}")
        {:error, :already_up} -> IO.puts("Database already exists for #{inspect(repo)}")
        {:error, term} -> raise "Failed to create database: #{inspect(term)}"
      end
    end
  end

  def seed do
    load_app()
    
    for repo <- repos() do
      {:ok, _, _} = Ecto.Migrator.with_repo(repo, fn _repo ->
        seed_file = Application.app_dir(@app, "priv/repo/seeds.exs")
        if File.exists?(seed_file) do
          Code.eval_file(seed_file)
        end
      end)
    end
  end

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

  defp load_app do
    Application.ensure_all_started(:ssl)
    Application.load(@app)
  end
end
elixir
defmodule <AppName>.Release do
  @moduledoc """
  Release tasks for database management.
  
  Usage in production:
    bin/<app_name> eval "<AppName>.Release.migrate()"
    bin/<app_name> eval "<AppName>.Release.rollback(<AppName>.Repo, 20240101000000)"
  """

  @app :<app_name>

  def migrate do
    load_app()

    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 create do
    load_app()

    for repo <- repos() do
      case repo.__adapter__().storage_up(repo.config()) do
        :ok -> IO.puts("Database created for #{inspect(repo)}")
        {:error, :already_up} -> IO.puts("Database already exists for #{inspect(repo)}")
        {:error, term} -> raise "Failed to create database: #{inspect(term)}"
      end
    end
  end

  def seed do
    load_app()
    
    for repo <- repos() do
      {:ok, _, _} = Ecto.Migrator.with_repo(repo, fn _repo ->
        seed_file = Application.app_dir(@app, "priv/repo/seeds.exs")
        if File.exists?(seed_file) do
          Code.eval_file(seed_file)
        end
      end)
    end
  end

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

  defp load_app do
    Application.ensure_all_started(:ssl)
    Application.load(@app)
  end
end

For Phoenix Projects with Multiple Repos

针对多仓库的Phoenix项目

If multiple repos exist (e.g.,
Repo
and
ReadRepo
), ensure
config.exs
has:
elixir
config :<app_name>, ecto_repos: [<AppName>.Repo, <AppName>.ReadRepo]
若存在多个仓库(例如
Repo
ReadRepo
),确保
config.exs
中包含:
elixir
config :<app_name>, ecto_repos: [<AppName>.Repo, <AppName>.ReadRepo]

Verification Steps

验证步骤

After creating the module:
  1. Compile:
    mix compile
  2. Test locally:
    mix run -e "<AppName>.Release.migrate()"
  3. Build release:
    MIX_ENV=prod mix release
  4. Test release:
    _build/prod/rel/<app_name>/bin/<app_name> eval "<AppName>.Release.migrate()"
创建模块后:
  1. 编译:
    mix compile
  2. 本地测试:
    mix run -e "<AppName>.Release.migrate()"
  3. 构建版本:
    MIX_ENV=prod mix release
  4. 测试版本:
    _build/prod/rel/<app_name>/bin/<app_name> eval "<AppName>.Release.migrate()"

Usage Documentation

使用文档

Add to project README or deployment docs:
bash
undefined
添加到项目README或部署文档中:
bash
undefined

Create database (first deploy only)

创建数据库(仅首次部署时使用)

bin/<app_name> eval "<AppName>.Release.migrate()"
bin/<app_name> eval "<AppName>.Release.migrate()"

Run migrations

运行迁移

bin/<app_name> eval "<AppName>.Release.migrate()"
bin/<app_name> eval "<AppName>.Release.migrate()"

Rollback to specific version

回滚到指定版本

bin/<app_name> eval "<AppName>.Release.rollback(<AppName>.Repo, 20240101000000)"
bin/<app_name> eval "<AppName>.Release.rollback(<AppName>.Repo, 20240101000000)"

Seed database

填充数据库初始数据

bin/<app_name> eval "<AppName>.Release.seed()"
undefined
bin/<app_name> eval "<AppName>.Release.seed()"
undefined

Common Issues

常见问题

  • SSL not started: Ensure
    Application.ensure_all_started(:ssl)
    is called before repo operations
  • App not loaded: Always call
    Application.load(@app)
    first
  • Missing ecto_repos config: Verify
    config :app_name, ecto_repos: [...]
    exists
  • SSL未启动:确保在仓库操作前调用
    Application.ensure_all_started(:ssl)
  • 应用未加载:始终先调用
    Application.load(@app)
  • 缺少ecto_repos配置:验证是否存在
    config :app_name, ecto_repos: [...]
    配置