ecto.gen.migration.ex (3818B)
1 defmodule Mix.Tasks.Ecto.Gen.Migration do 2 use Mix.Task 3 4 import Macro, only: [camelize: 1, underscore: 1] 5 import Mix.Generator 6 import Mix.Ecto 7 import Mix.EctoSQL 8 9 @shortdoc "Generates a new migration for the repo" 10 11 @aliases [ 12 r: :repo 13 ] 14 15 @switches [ 16 change: :string, 17 repo: [:string, :keep], 18 no_compile: :boolean, 19 no_deps_check: :boolean, 20 migrations_path: :string 21 ] 22 23 @moduledoc """ 24 Generates a migration. 25 26 The repository must be set under `:ecto_repos` in the 27 current app configuration or given via the `-r` option. 28 29 ## Examples 30 31 $ mix ecto.gen.migration add_posts_table 32 $ mix ecto.gen.migration add_posts_table -r Custom.Repo 33 34 The generated migration filename will be prefixed with the current 35 timestamp in UTC which is used for versioning and ordering. 36 37 By default, the migration will be generated to the 38 "priv/YOUR_REPO/migrations" directory of the current application 39 but it can be configured to be any subdirectory of `priv` by 40 specifying the `:priv` key under the repository configuration. 41 42 This generator will automatically open the generated file if 43 you have `ECTO_EDITOR` set in your environment variable. 44 45 ## Command line options 46 47 * `-r`, `--repo` - the repo to generate migration for 48 * `--no-compile` - does not compile applications before running 49 * `--no-deps-check` - does not check dependencies before running 50 * `--migrations-path` - the path to run the migrations from, defaults to `priv/repo/migrations` 51 52 ## Configuration 53 54 If the current app configuration specifies a custom migration module 55 the generated migration code will use that rather than the default 56 `Ecto.Migration`: 57 58 config :ecto_sql, migration_module: MyApplication.CustomMigrationModule 59 60 """ 61 62 @impl true 63 def run(args) do 64 repos = parse_repo(args) 65 66 Enum.map repos, fn repo -> 67 case OptionParser.parse!(args, strict: @switches, aliases: @aliases) do 68 {opts, [name]} -> 69 ensure_repo(repo, args) 70 path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations") 71 base_name = "#{underscore(name)}.exs" 72 file = Path.join(path, "#{timestamp()}_#{base_name}") 73 unless File.dir?(path), do: create_directory path 74 75 fuzzy_path = Path.join(path, "*_#{base_name}") 76 if Path.wildcard(fuzzy_path) != [] do 77 Mix.raise "migration can't be created, there is already a migration file with name #{name}." 78 end 79 80 # The :change option may be used by other tasks but not the CLI 81 assigns = [mod: Module.concat([repo, Migrations, camelize(name)]), change: opts[:change]] 82 create_file file, migration_template(assigns) 83 84 if open?(file) and Mix.shell().yes?("Do you want to run this migration?") do 85 Mix.Task.run "ecto.migrate", ["-r", inspect(repo), "--migrations-path", path] 86 end 87 88 file 89 90 {_, _} -> 91 Mix.raise "expected ecto.gen.migration to receive the migration file name, " <> 92 "got: #{inspect Enum.join(args, " ")}" 93 end 94 end 95 end 96 97 defp timestamp do 98 {{y, m, d}, {hh, mm, ss}} = :calendar.universal_time() 99 "#{y}#{pad(m)}#{pad(d)}#{pad(hh)}#{pad(mm)}#{pad(ss)}" 100 end 101 102 defp pad(i) when i < 10, do: <<?0, ?0 + i>> 103 defp pad(i), do: to_string(i) 104 105 defp migration_module do 106 case Application.get_env(:ecto_sql, :migration_module, Ecto.Migration) do 107 migration_module when is_atom(migration_module) -> migration_module 108 other -> Mix.raise "Expected :migration_module to be a module, got: #{inspect(other)}" 109 end 110 end 111 112 embed_template :migration, """ 113 defmodule <%= inspect @mod %> do 114 use <%= inspect migration_module() %> 115 116 def change do 117 <%= @change %> 118 end 119 end 120 """ 121 end