131 lines
3.8 KiB
Elixir
131 lines
3.8 KiB
Elixir
defmodule Mix.Tasks.Ecto.Gen.Migration do
|
|
use Mix.Task
|
|
|
|
import Macro, only: [camelize: 1, underscore: 1]
|
|
import Mix.Generator
|
|
import Mix.Ecto
|
|
import Mix.EctoSQL
|
|
|
|
@shortdoc "Generates a new migration for the repo"
|
|
|
|
@aliases [
|
|
r: :repo
|
|
]
|
|
|
|
@switches [
|
|
change: :string,
|
|
repo: [:string, :keep],
|
|
no_compile: :boolean,
|
|
no_deps_check: :boolean,
|
|
migrations_path: :string
|
|
]
|
|
|
|
@moduledoc """
|
|
Generates a migration.
|
|
|
|
The repository must be set under `:ecto_repos` in the
|
|
current app configuration or given via the `-r` option.
|
|
|
|
## Examples
|
|
|
|
$ mix ecto.gen.migration add_posts_table
|
|
$ mix ecto.gen.migration add_posts_table -r Custom.Repo
|
|
|
|
The generated migration filename will be prefixed with the current
|
|
timestamp in UTC which is used for versioning and ordering.
|
|
|
|
By default, the migration will be generated to the
|
|
"priv/YOUR_REPO/migrations" directory of the current application
|
|
but it can be configured to be any subdirectory of `priv` by
|
|
specifying the `:priv` key under the repository configuration.
|
|
|
|
This generator will automatically open the generated file if
|
|
you have `ECTO_EDITOR` set in your environment variable.
|
|
|
|
## Command line options
|
|
|
|
* `-r`, `--repo` - the repo to generate migration for
|
|
* `--no-compile` - does not compile applications before running
|
|
* `--no-deps-check` - does not check dependencies before running
|
|
* `--migrations-path` - the path to run the migrations from, defaults to `priv/repo/migrations`
|
|
|
|
## Configuration
|
|
|
|
If the current app configuration specifies a custom migration module
|
|
the generated migration code will use that rather than the default
|
|
`Ecto.Migration`:
|
|
|
|
config :ecto_sql, migration_module: MyApplication.CustomMigrationModule
|
|
|
|
"""
|
|
|
|
@impl true
|
|
def run(args) do
|
|
repos = parse_repo(args)
|
|
|
|
Enum.map(repos, fn repo ->
|
|
case OptionParser.parse!(args, strict: @switches, aliases: @aliases) do
|
|
{opts, [name]} ->
|
|
ensure_repo(repo, args)
|
|
path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations")
|
|
base_name = "#{underscore(name)}.exs"
|
|
file = Path.join(path, "#{timestamp()}_#{base_name}")
|
|
unless File.dir?(path), do: create_directory(path)
|
|
|
|
fuzzy_path = Path.join(path, "*_#{base_name}")
|
|
|
|
if Path.wildcard(fuzzy_path) != [] do
|
|
Mix.raise(
|
|
"migration can't be created, there is already a migration file with name #{name}."
|
|
)
|
|
end
|
|
|
|
# The :change option may be used by other tasks but not the CLI
|
|
assigns = [
|
|
mod: Module.concat([repo, Migrations, camelize(name)]),
|
|
change: opts[:change]
|
|
]
|
|
|
|
create_file(file, migration_template(assigns))
|
|
|
|
if open?(file) and Mix.shell().yes?("Do you want to run this migration?") do
|
|
Mix.Task.run("ecto.migrate", ["-r", inspect(repo), "--migrations-path", path])
|
|
end
|
|
|
|
file
|
|
|
|
{_, _} ->
|
|
Mix.raise(
|
|
"expected ecto.gen.migration to receive the migration file name, " <>
|
|
"got: #{inspect(Enum.join(args, " "))}"
|
|
)
|
|
end
|
|
end)
|
|
end
|
|
|
|
defp timestamp do
|
|
{{y, m, d}, {hh, mm, ss}} = :calendar.universal_time()
|
|
"#{y}#{pad(m)}#{pad(d)}#{pad(hh)}#{pad(mm)}#{pad(ss)}"
|
|
end
|
|
|
|
defp pad(i) when i < 10, do: <<?0, ?0 + i>>
|
|
defp pad(i), do: to_string(i)
|
|
|
|
defp migration_module do
|
|
case Application.get_env(:ecto_sql, :migration_module, Ecto.Migration) do
|
|
migration_module when is_atom(migration_module) -> migration_module
|
|
other -> Mix.raise("Expected :migration_module to be a module, got: #{inspect(other)}")
|
|
end
|
|
end
|
|
|
|
embed_template(:migration, """
|
|
defmodule <%= inspect @mod %> do
|
|
use <%= inspect migration_module() %>
|
|
|
|
def change do
|
|
<%= @change %>
|
|
end
|
|
end
|
|
""")
|
|
end
|