220 lines
6.5 KiB
Elixir
220 lines
6.5 KiB
Elixir
|
defmodule Mix.Tasks.Phx.Gen.Schema do
|
||
|
@shortdoc "Generates an Ecto schema and migration file"
|
||
|
|
||
|
@moduledoc """
|
||
|
Generates an Ecto schema and migration.
|
||
|
|
||
|
$ mix phx.gen.schema Blog.Post blog_posts title:string views:integer
|
||
|
|
||
|
The first argument is the schema module followed by its plural
|
||
|
name (used as the table name).
|
||
|
|
||
|
The generated schema above will contain:
|
||
|
|
||
|
* a schema file in `lib/my_app/blog/post.ex`, with a `blog_posts` table
|
||
|
* a migration file for the repository
|
||
|
|
||
|
The generated migration can be skipped with `--no-migration`.
|
||
|
|
||
|
## Contexts
|
||
|
|
||
|
Your schemas can be generated and added to a separate OTP app.
|
||
|
Make sure your configuration is properly setup or manually
|
||
|
specify the context app with the `--context-app` option with
|
||
|
the CLI.
|
||
|
|
||
|
Via config:
|
||
|
|
||
|
config :marketing_web, :generators, context_app: :marketing
|
||
|
|
||
|
Via CLI:
|
||
|
|
||
|
$ mix phx.gen.schema Blog.Post blog_posts title:string views:integer --context-app marketing
|
||
|
|
||
|
## Attributes
|
||
|
|
||
|
The resource fields are given using `name:type` syntax
|
||
|
where type are the types supported by Ecto. Omitting
|
||
|
the type makes it default to `:string`:
|
||
|
|
||
|
$ mix phx.gen.schema Blog.Post blog_posts title views:integer
|
||
|
|
||
|
The following types are supported:
|
||
|
|
||
|
#{for attr <- Mix.Phoenix.Schema.valid_types(), do: " * `#{inspect attr}`\n"}
|
||
|
* `:datetime` - An alias for `:naive_datetime`
|
||
|
|
||
|
The generator also supports references, which we will properly
|
||
|
associate the given column to the primary key column of the
|
||
|
referenced table:
|
||
|
|
||
|
$ mix phx.gen.schema Blog.Post blog_posts title user_id:references:users
|
||
|
|
||
|
This will result in a migration with an `:integer` column
|
||
|
of `:user_id` and create an index.
|
||
|
|
||
|
Furthermore an array type can also be given if it is
|
||
|
supported by your database, although it requires the
|
||
|
type of the underlying array element to be given too:
|
||
|
|
||
|
$ mix phx.gen.schema Blog.Post blog_posts tags:array:string
|
||
|
|
||
|
Unique columns can be automatically generated by using:
|
||
|
|
||
|
$ mix phx.gen.schema Blog.Post blog_posts title:unique unique_int:integer:unique
|
||
|
|
||
|
Redact columns can be automatically generated by using:
|
||
|
|
||
|
$ mix phx.gen.schema Accounts.Superhero superheroes secret_identity:redact password:string:redact
|
||
|
|
||
|
Ecto.Enum fields can be generated by using:
|
||
|
|
||
|
$ mix phx.gen.schema Blog.Post blog_posts title status:enum:unpublished:published:deleted
|
||
|
|
||
|
If no data type is given, it defaults to a string.
|
||
|
|
||
|
## table
|
||
|
|
||
|
By default, the table name for the migration and schema will be
|
||
|
the plural name provided for the resource. To customize this value,
|
||
|
a `--table` option may be provided. For example:
|
||
|
|
||
|
$ mix phx.gen.schema Blog.Post posts --table cms_posts
|
||
|
|
||
|
## binary_id
|
||
|
|
||
|
Generated migration can use `binary_id` for schema's primary key
|
||
|
and its references with option `--binary-id`.
|
||
|
|
||
|
## Default options
|
||
|
|
||
|
This generator uses default options provided in the `:generators`
|
||
|
configuration of your application. These are the defaults:
|
||
|
|
||
|
config :your_app, :generators,
|
||
|
migration: true,
|
||
|
binary_id: false,
|
||
|
sample_binary_id: "11111111-1111-1111-1111-111111111111"
|
||
|
|
||
|
You can override those options per invocation by providing corresponding
|
||
|
switches, e.g. `--no-binary-id` to use normal ids despite the default
|
||
|
configuration or `--migration` to force generation of the migration.
|
||
|
"""
|
||
|
use Mix.Task
|
||
|
|
||
|
alias Mix.Phoenix.Schema
|
||
|
|
||
|
@switches [migration: :boolean, binary_id: :boolean, table: :string,
|
||
|
web: :string, context_app: :string, prefix: :string]
|
||
|
|
||
|
@doc false
|
||
|
def run(args) do
|
||
|
if Mix.Project.umbrella?() do
|
||
|
Mix.raise "mix phx.gen.schema must be invoked from within your *_web application root directory"
|
||
|
end
|
||
|
|
||
|
schema = build(args, [])
|
||
|
paths = Mix.Phoenix.generator_paths()
|
||
|
|
||
|
prompt_for_conflicts(schema)
|
||
|
|
||
|
schema
|
||
|
|> copy_new_files(paths, schema: schema)
|
||
|
|> print_shell_instructions()
|
||
|
end
|
||
|
|
||
|
defp prompt_for_conflicts(schema) do
|
||
|
schema
|
||
|
|> files_to_be_generated()
|
||
|
|> Mix.Phoenix.prompt_for_conflicts()
|
||
|
end
|
||
|
|
||
|
@doc false
|
||
|
def build(args, parent_opts, help \\ __MODULE__) do
|
||
|
{schema_opts, parsed, _} = OptionParser.parse(args, switches: @switches)
|
||
|
[schema_name, plural | attrs] = validate_args!(parsed, help)
|
||
|
|
||
|
opts =
|
||
|
parent_opts
|
||
|
|> Keyword.merge(schema_opts)
|
||
|
|> put_context_app(schema_opts[:context_app])
|
||
|
|
||
|
schema = Schema.new(schema_name, plural, attrs, opts)
|
||
|
|
||
|
schema
|
||
|
end
|
||
|
|
||
|
defp put_context_app(opts, nil), do: opts
|
||
|
defp put_context_app(opts, string) do
|
||
|
Keyword.put(opts, :context_app, String.to_atom(string))
|
||
|
end
|
||
|
|
||
|
@doc false
|
||
|
def files_to_be_generated(%Schema{} = schema) do
|
||
|
[{:eex, "schema.ex", schema.file}]
|
||
|
end
|
||
|
|
||
|
@doc false
|
||
|
def copy_new_files(%Schema{context_app: ctx_app} = schema, paths, binding) do
|
||
|
files = files_to_be_generated(schema)
|
||
|
Mix.Phoenix.copy_from(paths, "priv/templates/phx.gen.schema", binding, files)
|
||
|
|
||
|
if schema.migration? do
|
||
|
migration_path = Mix.Phoenix.context_app_path(ctx_app, "priv/repo/migrations/#{timestamp()}_create_#{schema.table}.exs")
|
||
|
Mix.Phoenix.copy_from paths, "priv/templates/phx.gen.schema", binding, [
|
||
|
{:eex, "migration.exs", migration_path},
|
||
|
]
|
||
|
end
|
||
|
|
||
|
schema
|
||
|
end
|
||
|
|
||
|
@doc false
|
||
|
def print_shell_instructions(%Schema{} = schema) do
|
||
|
if schema.migration? do
|
||
|
Mix.shell().info """
|
||
|
|
||
|
Remember to update your repository by running migrations:
|
||
|
|
||
|
$ mix ecto.migrate
|
||
|
"""
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@doc false
|
||
|
def validate_args!([schema, plural | _] = args, help) do
|
||
|
cond do
|
||
|
not Schema.valid?(schema) ->
|
||
|
help.raise_with_help "Expected the schema argument, #{inspect schema}, to be a valid module name"
|
||
|
String.contains?(plural, ":") or plural != Phoenix.Naming.underscore(plural) ->
|
||
|
help.raise_with_help "Expected the plural argument, #{inspect plural}, to be all lowercase using snake_case convention"
|
||
|
true ->
|
||
|
args
|
||
|
end
|
||
|
end
|
||
|
def validate_args!(_, help) do
|
||
|
help.raise_with_help "Invalid arguments"
|
||
|
end
|
||
|
|
||
|
@doc false
|
||
|
@spec raise_with_help(String.t) :: no_return()
|
||
|
def raise_with_help(msg) do
|
||
|
Mix.raise """
|
||
|
#{msg}
|
||
|
|
||
|
mix phx.gen.schema expects both a module name and
|
||
|
the plural of the generated resource followed by
|
||
|
any number of attributes:
|
||
|
|
||
|
mix phx.gen.schema Blog.Post blog_posts title:string
|
||
|
"""
|
||
|
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)
|
||
|
end
|