Packages

A comprehensive library for working with both static and dynamic enum structures in Elixir.

Current section

Files

Jump to
enumex lib mix tasks enumex.dynamic.gen.migration.ex
Raw

lib/mix/tasks/enumex.dynamic.gen.migration.ex

defmodule Mix.Tasks.Enumex.Dynamic.Gen.Migration do
@shortdoc "Generates an enum table migration."
args = [
%{help: "A table for storing enum values", required: true, value_name: "TABLE"},
%{
help: "Enum field names mapper in format 'original:modified'",
value_name: "OPTION_1 OPTION_2 … OPTION_N"
}
]
task = Enumex.Generator.Task.new("enumex.dynamic.gen.migration", args, [])
if Mix.env() == :test do
try do
:meck.new(:io, [:unstick, :passthrough])
rescue
_exception -> :ok
end
:meck.expect(:io, :columns, fn -> {:ok, 80} end)
end
help = Enumex.Generator.Task.help(task, @shortdoc, true)
@moduledoc Enumex.Generator.Task.moduledoc(help)
use Mix.Task
alias Enumex.Generator.Task
alias Mix.Generator
alias Mix.Tasks.Ecto.Gen.Migration
require Generator
@help help
@opts [
aliases: Task.aliases(),
return_separator: true,
strict: Task.options()
]
@task_name task.name
@version Task.version(task)
@impl Mix.Task
def run(args) do
Task.run(@task_name, args, @help, @version, @opts, fn
[table | args], [] ->
case Enum.split_while(args, &(&1 != "--")) do
{enum_bindings, ["--" | ecto_args]} when ecto_args != [] ->
run(table, enum_bindings, ecto_args)
{_enum_bindings, _ecto_args} ->
Task.missing_args(@task_name, ~w[ECTO_GEN_MIGRATION_ARGS])
end
end)
end
@spec run(String.t(), OptionParser.argv(), OptionParser.argv()) :: any()
defp run(table, enum_bindings, ecto_args) do
bindings = prepare_bindings(enum_bindings)
bindings
|> generate_unique_indexes()
|> Enum.map_join("", &unique_index_template(fields: &1, table: table))
|> then(&[indexes: &1, opts: prepare_enum_opts(bindings), table: table])
|> changes_template()
|> String.trim_trailing()
# credo:disable-for-next-line Credo.Check.Refactor.Apply
|> then(&apply(Migration, :run, [["--change", &1 | ecto_args]]))
end
@spec prepare_bindings(OptionParser.argv()) :: keyword(atom())
defp prepare_bindings(bindings) do
bindings
|> Enum.reduce([], fn option, acc ->
option
|> String.split(":")
|> prepare_bindings(acc)
end)
|> Enum.sort()
end
@spec prepare_bindings(OptionParser.argv(), acc) :: acc when acc: keyword(atom())
defp prepare_bindings([original, modified], acc)
when original not in ~w[enum_name id index] or modified == original do
acc
end
defp prepare_bindings([original, modified], acc) do
# credo:disable-for-next-line Credo.Check.Warning.UnsafeToAtom
[{String.to_existing_atom(original), String.to_atom(modified)} | acc]
end
defp prepare_bindings(_list, acc), do: acc
@spec generate_unique_indexes(keyword(atom())) :: [[atom()]]
defp generate_unique_indexes(opts) do
enum_name = opts[:enum_name] || :enum_name
id = opts[:id] || :id
index = opts[:index] || :index
Enum.sort([[enum_name, id], [enum_name, id, index], [enum_name, index]])
end
@spec prepare_enum_opts(keyword(atom())) :: String.t()
defp prepare_enum_opts(bindings) do
Enum.map_join(bindings, ", ", fn {key, value} -> "#{key}: #{inspect(value)}" end)
end
@spec changes_template(indexes: String.t(), opts: String.t(), table: String.t()) :: String.t()
Generator.embed_template(:changes, """
import Enumex.Dynamic.Migration
create table(<%= inspect(@table) %>) do
enum_fields(<%= @opts %>)
end
<%= @indexes %>
""")
@spec unique_index_template(fields: [[atom()]], table: String.t()) :: String.t()
Generator.embed_template(:unique_index, """
create unique_index(<%= inspect(@table) %>, <%= inspect(@fields) %>)
""")
end