lib/mix/tasks/phx.gen.solid.value.ex

defmodule Mix.Tasks.Phx.Gen.Solid.Value do
  @shortdoc "Generates Value logic for a resource"

  @moduledoc """
  Generates Value logic for a resource.

      mix phx.gen.solid.value Accounts User users id name age

  The first argument is the context module followed by the schema module and its
  plural name.

  This creates a new Value in `MyApp.Accounts.Value.User`. By default the
  allowed fields for this value will be the arguments you passed into the
  generator, in this case, `@valid_fields [:id, :slug, :name]`.

  **Options**
  - `--helpers` - This will generate the Value helpers context in `MyApp.Value`.
     Module name can be overridden by `--value-context`.
  - `--value-context MODULE` - This will be the name used for the helpers alias
     and/or helper modue name when generated. Defaults to `MyApp.Value`.

  The generated Value relies on a few helper functions also generated by this
  task. By default it will be placed in your projects context folder.

  For more information about the generated Value, see the [Overview](overview.html).
  """

  use Mix.Task

  alias Mix.Phoenix.Context
  alias Mix.Tasks.Phx.Gen
  alias PhxGenSolid.Generator

  @switches [helpers: :boolean, value_context: :string]

  @impl true
  def run(args) do
    if Mix.Project.umbrella?() do
      Mix.raise("mix phx.gen.solid can only be run inside an application directory")
    end

    {opts, parsed} = OptionParser.parse!(args, strict: @switches)

    # Don't pass along the opts
    {context, schema} = Gen.Context.build(parsed, __MODULE__)

    binding = [
      context: context,
      opts: opts,
      schema: schema,
      web_app_name: Generator.web_app_name(context),
      value_context: build_value_context(context, opts),
      value_fields: build_value_fields(schema.attrs),
      value_module:
        Module.concat([
          context.base_module,
          "#{inspect(context.alias)}",
          "Value",
          "#{inspect(schema.alias)}"
        ])
    ]

    paths = Generator.paths()
    Generator.prompt_for_conflicts(context, &files_to_be_generated/2, opts)
    Generator.copy_new_files(context, binding, paths, &files_to_be_generated/2, opts)
  end

  def raise_with_help(msg), do: Generator.raise_with_help(msg)

  defp build_value_context(context, opts) do
    default_context = Module.concat([context.base_module, "Value"])

    case Keyword.has_key?(opts, :value_context) do
      true ->
        opts
        |> Keyword.get(:value_context)
        |> String.split(".")
        |> Module.concat()

      false ->
        default_context
    end
  end

  defp files_to_be_generated(%Context{schema: schema} = context, opts) do
    should_gen_helpers = Keyword.get(opts, :helpers, false)

    base_files = [
      {:eex, "value.ex", Path.join([context.dir, "values", "#{schema.singular}.ex"])}
    ]

    helpers = helper_files_to_be_generated(context)

    maybe_gen_helpers(base_files, helpers, should_gen_helpers)
  end

  defp helper_files_to_be_generated(%Context{context_app: context_app}) do
    app_path = Mix.Phoenix.context_lib_path(context_app, "")

    [
      {:eex, "value_context.ex", Path.join([app_path, "value.ex"])}
    ]
  end

  defp maybe_gen_helpers(base_files, helpers, true), do: Enum.concat(base_files, helpers)
  defp maybe_gen_helpers(base_files, _helpers, false), do: base_files

  defp build_value_fields(attrs) do
    Keyword.keys(attrs)
  end
end