Repository: phoenixframework/phoenix
Pull request #2144 (2017-03-02 23:13:02)

Port tests from phoenix.gen.{html,json,model} to phx.gen.{html,json,schema}
Ref: #2131 
I ported almost all tests from phoenix.gen.{html,json,model} to phx.gen.{html,json,schema}. There are only a few failing tests. Other ported tests are working just fine.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 37289323b..1de500d92 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,7 +15,6 @@ See these [`1.2.x` to `1.3.x` upgrade instructions](https://gist.github.com/chri
   * [Channel] Channels now distinguish from graceful exits and application restarts, allowing clients to enter error mode and reconnected after cold deploys.
   * [Router] document `match` support for matching on any http method with the special `:*` argument
   * [ConnTest] Add `redirected_params/1` to return the named params matched in the router for the redirected URL
-  * [Digester] Add `mix phx.digest.clean` to remove old versions of compiled assets
 
 * Deprecations
   * [Generator] All `phoenix.*` mix tasks have been deprecated in favor of new `phx.*` tasks
diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex
index bc60366ca..0df60f445 100644
--- a/installer/lib/mix/tasks/phx.new.ex
+++ b/installer/lib/mix/tasks/phx.new.ex
@@ -131,7 +131,7 @@ defmodule Mix.Tasks.Phx.New do
         Task.await(compile, :infinity)
 
         print_mix_info(project.project_path, extra)
-        if Project.ecto?(project), do: print_ecto_info(project)
+        if Project.ecto?(project), do: print_ecto_info()
         if not brunch?, do: print_brunch_info(project, starting_dir)
       end)
     end)
@@ -181,15 +181,9 @@ defmodule Mix.Tasks.Phx.New do
     nil
   end
 
-  defp print_ecto_info(%Project{app_path: nil}), do: nil
-  defp print_ecto_info(%Project{app_path: app_path} = project) do
-    config_path =
-      app_path
-      |> Path.join("config/dev.exs")
-      |> Path.relative_to(project.project_path)
-
+  defp print_ecto_info do
     Mix.shell.info """
-    Before moving on, configure your database in #{config_path} and run:
+    Before moving on, configure your database in config/dev.exs and run:
 
         $ mix ecto.create
     """
diff --git a/installer/templates/new/mix.exs b/installer/templates/new/mix.exs
index 26975ef87..3391064a9 100644
--- a/installer/templates/new/mix.exs
+++ b/installer/templates/new/mix.exs
@@ -35,7 +35,7 @@ defmodule <%= app_module %>.Mixfile do
   defp deps do
     [<%= phoenix_dep %>,
      {:phoenix_pubsub, "~> 1.0"},<%= if ecto do %>
-     {:phoenix_ecto, "~> 3.2"},
+     {:phoenix_ecto, "~> 3.1-rc"},
      {<%= inspect adapter_app %>, ">= 0.0.0"},<% end %><%= if html do %>
      {:phoenix_html, "~> 2.6"},
      {:phoenix_live_reload, "~> 1.0", only: :dev},<% end %>
diff --git a/installer/templates/phx_ecto/data_case.ex b/installer/templates/phx_ecto/data_case.ex
index c94bb26e0..1be01cb73 100644
--- a/installer/templates/phx_ecto/data_case.ex
+++ b/installer/templates/phx_ecto/data_case.ex
@@ -36,18 +36,18 @@ defmodule <%= app_module %>.DataCase do
   end
 
   @doc """
-  A helper that transform changeset errors to a map of messages.
+  A helper that converts the changeset error messages
+  for a given field into a list of strings for assertion:
 
-      changeset = Accounts.create_user(%{password: "short"})
-      assert "password is too short" in errors_on(changeset).password
-      assert %{password: ["password is too short"]} = errors_on(changeset)
+      changeset = Blog.create_user(%{password: "short"})
+      assert "password is too short" in errors_on(changeset, :password)
 
   """
-  def errors_on(changeset) do
-    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
+  def errors_on(changeset, field) do
+    for {message, opts} <- Keyword.get_values(changeset.errors, field) do
       Enum.reduce(opts, message, fn {key, value}, acc ->
         String.replace(acc, "%{#{key}}", to_string(value))
       end)
-    end)
+    end
   end
 end
diff --git a/installer/templates/phx_umbrella/apps/app_name/mix.exs b/installer/templates/phx_umbrella/apps/app_name/mix.exs
index f2a19add3..12cdc58fa 100644
--- a/installer/templates/phx_umbrella/apps/app_name/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name/mix.exs
@@ -33,7 +33,7 @@ defmodule <%= app_module %>.Mixfile do
   # Type `mix help deps` for examples and options.
   defp deps do
     [<%= if ecto do %>{:<%= adapter_app %>, ">= 0.0.0"},
-     {:ecto, "~> 2.1"}<% end %>]
+     {:ecto, "~> 2.1-rc"}<% end %>]
   end
 
   # Aliases are shortcuts or tasks specific to the current project.<%= if ecto do %>
diff --git a/lib/mix/phoenix/context.ex b/lib/mix/phoenix/context.ex
index fb40d45d2..53ce7f09b 100644
--- a/lib/mix/phoenix/context.ex
+++ b/lib/mix/phoenix/context.ex
@@ -1,6 +1,4 @@
 defmodule Mix.Phoenix.Context do
-  @moduledoc false
-
   alias Mix.Phoenix.{Context, Schema}
 
   defstruct name: nil,
@@ -13,7 +11,8 @@ defmodule Mix.Phoenix.Context do
             file: nil,
             dir: nil,
             opts: [],
-            pre_existing?: false
+            pre_existing?: false,
+            inputs: []
 
   def valid?(context) do
     context =~ ~r/^[A-Z]\w*(\.[A-Z]\w*)*$/
@@ -39,6 +38,7 @@ defmodule Mix.Phoenix.Context do
       file: file,
       dir: dir,
       opts: opts,
+      inputs: inputs(schema),
       pre_existing?: File.exists?(file)}
   end
 
@@ -48,4 +48,64 @@ defmodule Mix.Phoenix.Context do
       _ -> Module.concat(base, "Web")
     end
   end
+
+  def inject_schema_access(%Context{} = context, binding, paths) do
+    unless context.pre_existing? do
+      File.mkdir_p!(context.dir)
+      File.write!(context.file, Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.html/context.ex", binding))
+    end
+    schema_content = Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.html/schema_access.ex", binding)
+
+    context.file
+    |> File.read!()
+    |> String.trim_trailing()
+    |> String.trim_trailing("end")
+    |> EEx.eval_string(binding)
+    |> Kernel.<>(schema_content)
+    |> Kernel.<>("end\n")
+    |> write_context(context)
+
+    context
+  end
+
+  defp write_context(content, context) do
+    File.write!(context.file, content)
+  end
+
+  defp inputs(%Schema{} = schema) do
+    Enum.map(schema.attrs, fn
+      {_, {:array, _}} ->
+        {nil, nil, nil}
+      {_, {:references, _}} ->
+        {nil, nil, nil}
+      {key, :integer} ->
+        {label(key), ~s(<%= number_input f, #{inspect(key)}, class: "form-control" %>), error(key)}
+      {key, :float} ->
+        {label(key), ~s(<%= number_input f, #{inspect(key)}, step: "any", class: "form-control" %>), error(key)}
+      {key, :decimal} ->
+        {label(key), ~s(<%= number_input f, #{inspect(key)}, step: "any", class: "form-control" %>), error(key)}
+      {key, :boolean} ->
+        {label(key), ~s(<%= checkbox f, #{inspect(key)}, class: "checkbox" %>), error(key)}
+      {key, :text} ->
+        {label(key), ~s(<%= textarea f, #{inspect(key)}, class: "form-control" %>), error(key)}
+      {key, :date} ->
+        {label(key), ~s(<%= date_select f, #{inspect(key)}, class: "form-control" %>), error(key)}
+      {key, :time} ->
+        {label(key), ~s(<%= time_select f, #{inspect(key)}, class: "form-control" %>), error(key)}
+      {key, :utc_datetime} ->
+        {label(key), ~s(<%= datetime_select f, #{inspect(key)}, class: "form-control" %>), error(key)}
+      {key, :naive_datetime} ->
+        {label(key), ~s(<%= datetime_select f, #{inspect(key)}, class: "form-control" %>), error(key)}
+      {key, _}  ->
+        {label(key), ~s(<%= text_input f, #{inspect(key)}, class: "form-control" %>), error(key)}
+    end)
+  end
+
+  defp label(key) do
+    ~s(<%= label f, #{inspect(key)}, class: "control-label" %>)
+  end
+
+  defp error(field) do
+    ~s(<%= error_tag f, #{inspect(field)} %>)
+  end
 end
diff --git a/lib/mix/phoenix/schema.ex b/lib/mix/phoenix/schema.ex
index dac28ecdc..8906de7e2 100644
--- a/lib/mix/phoenix/schema.ex
+++ b/lib/mix/phoenix/schema.ex
@@ -1,6 +1,4 @@
 defmodule Mix.Phoenix.Schema do
-  @moduledoc false
-
   alias Mix.Phoenix.Schema
 
   defstruct module: nil,
@@ -107,6 +105,7 @@ defmodule Mix.Phoenix.Schema do
     |> Enum.map(& &1 |> String.split(":", parts: 2) |> hd |> String.to_atom)
   end
 
+
   @doc """
   Parses the attrs as received by generators.
   """
diff --git a/lib/mix/tasks/phoenix.digest.clean.ex b/lib/mix/tasks/phoenix.digest.clean.ex
new file mode 100644
index 000000000..b2b58a732
--- /dev/null
+++ b/lib/mix/tasks/phoenix.digest.clean.ex
@@ -0,0 +1,35 @@
+defmodule Mix.Tasks.Phoenix.Digest.Clean do
+  use Mix.Task
+
+  @shortdoc "Removes old versions of static assets."
+  @recursive true
+
+  @moduledoc """
+  Removes old versions of compiled assets.
+
+  By default, it will keep the latest version and
+  2 previous versions as well as any digest created
+  in the last hour.
+
+      mix phoenix.digest.clean
+      mix phoenix.digest.clean -o /www/public
+      mix phoenix.digest.clean --age 600 --keep 3
+
+  ## Options
+
+    * `-o, --output` - indicates the path to your compiled
+      assets directory. Defaults to `priv/static`.
+
+    * `--age` - specifies a maximum age (in seconds) for assets.
+      Files older than age that are not in the last `--keep` versions
+      will be removed. Defaults to 3600 (1 hour).
+
+    * `--keep` - specifies how many previous versions of assets to keep.
+      Defaults to 2 previous version.
+
+  """
+  def run(args) do
+    IO.puts :stderr, "mix phoenix.digest.clean is deprecated. Use phx.digest.clean instead."
+    Mix.Tasks.Phx.Digest.Clean.run(args)
+  end
+end
diff --git a/lib/mix/tasks/phx.gen.context.ex b/lib/mix/tasks/phx.gen.context.ex
deleted file mode 100644
index a7f0c8307..000000000
--- a/lib/mix/tasks/phx.gen.context.ex
+++ /dev/null
@@ -1,135 +0,0 @@
-defmodule Mix.Tasks.Phx.Gen.Context do
-  @shortdoc "Generates a context with functions around an Ecto schema"
-
-  @moduledoc """
-  Generates a context with functions around an Ecto schema.
-
-      mix phx.gen.context Accounts User users name:string age:integer
-
-  The first argument is the context name followed by the schema module
-  and its plural name (used for resources and schema).
-
-  The above generated resource will add the following files to lib/your_app:
-
-    * a context module in accounts.ex, serving as the API boundary to the resource
-    * a schema in accounts/user.ex, with an `accounts_users` table
-
-  As well as a migration file for the repository and test files for
-  generated context.
-
-  ## Schema options
-
-  By deault, the schema table name will be the plural name, namespaced by the
-  context name. You can customize this value by providing the `--table`
-  option to the generator.
-
-  Read the documentation for `phx.gen.schema` for more information on
-  attributes and supported options.
-  """
-
-  use Mix.Task
-
-  alias Mix.Phoenix.{Context, Schema}
-  alias Mix.Tasks.Phx.Gen
-
-  def run(args) do
-    if Mix.Project.umbrella? do
-      Mix.raise "mix phx.gen.context can only be run inside an application directory"
-    end
-
-    {context, schema} = build(args)
-    binding = [context: context, schema: schema]
-    paths = Mix.Phoenix.generator_paths()
-
-    context
-    |> copy_new_files(paths, binding)
-    |> print_shell_instructions()
-  end
-
-  def build(args) do
-    switches = [binary_id: :boolean, table: :string]
-    {opts, parsed, _} = OptionParser.parse(args, switches: switches)
-    [context_name, schema_name, plural | schema_args] = validate_args!(parsed)
-
-    table = Keyword.get(opts, :table, Phoenix.Naming.underscore(context_name) <> "_#{plural}")
-    schema_module = inspect(Module.concat(context_name, schema_name))
-
-    schema = Gen.Schema.build([schema_module, plural | schema_args] ++ ["--table", table], __MODULE__)
-    context = Context.new(context_name, schema, opts)
-    Mix.Phoenix.check_module_name_availability!(context.module)
-
-    {context, schema}
-  end
-
-  def copy_new_files(%Context{schema: schema} = context, paths, binding) do
-    Gen.Schema.copy_new_files(schema, paths, binding)
-    inject_schema_access(context, paths, binding)
-    Mix.Phoenix.copy_from paths, "priv/templates/phx.gen.context", "", binding, [
-      {:new_eex, "context_test.exs", "test/#{context.basename}_test.exs"},
-    ]
-    context
-  end
-
-  defp inject_schema_access(%Context{dir: dir, file: file} = context, paths, binding) do
-    unless context.pre_existing? do
-      File.mkdir_p!(dir)
-      File.write!(file, Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.context/context.ex", binding))
-    end
-
-    schema_content = Mix.Phoenix.eval_from(paths, "priv/templates/phx.gen.context/schema_access.ex", binding)
-
-    file
-    |> File.read!()
-    |> String.trim_trailing()
-    |> String.trim_trailing("end")
-    |> EEx.eval_string(binding)
-    |> Kernel.<>(schema_content)
-    |> Kernel.<>("end\n")
-    |> write_context(file)
-  end
-
-  defp write_context(content, file) do
-    File.write!(file, content)
-  end
-
-  def print_shell_instructions(%Context{schema: schema}) do
-    Gen.Schema.print_shell_instructions(schema)
-  end
-
-  defp validate_args!([context, schema, _plural | _] = args) do
-    cond do
-      not Context.valid?(context) ->
-        raise_with_help "Expected the context, #{inspect context}, to be a valid module name"
-      not Schema.valid?(schema) ->
-        raise_with_help "Expected the schema, #{inspect schema}, to be a valid module name"
-      context == schema ->
-        raise_with_help "The context and schema should have different names"
-      true ->
-        args
-    end
-  end
-
-  defp validate_args!(_) do
-    raise_with_help "Invalid arguments"
-  end
-
-  @spec raise_with_help(String.t) :: no_return()
-  def raise_with_help(msg) do
-    Mix.raise """
-    #{msg}
-
-    mix phx.gen.html, phx.gen.json and phx.gen.context expect a
-    context module name, followed by singular and plural names of
-    the generated resource, ending with any number of attributes.
-    For example:
-
-        mix phx.gen.html Accounts User users name:string
-        mix phx.gen.json Accounts User users name:string
-        mix phx.gen.context Accounts User users name:string
-
-    The context serves as the API boundary for the given resource.
-    Multiple resources may belong to a context and a resource may be
-    split over distinct contexts (such as Accounts.User and Blog.User).
-    """
-  end
-end
diff --git a/lib/mix/tasks/phx.gen.html.ex b/lib/mix/tasks/phx.gen.html.ex
index 71c4d1fe2..15c718ea4 100644
--- a/lib/mix/tasks/phx.gen.html.ex
+++ b/lib/mix/tasks/phx.gen.html.ex
@@ -21,14 +21,14 @@ defmodule Mix.Tasks.Phx.Gen.Html do
   As well as a migration file for the repository and test files for
   generated context and controller features.
 
-  ## Schema options
+  ## Schema table name
 
   By deault, the schema table name will be the plural name, namespaced by the
   context name. You can customize this value by providing the `--table`
   option to the generator.
 
-  Read the documentation for `phx.gen.schema` for more information on
-  attributes and supported options.
+  Read the documentation for `phx.gen.schema` for more information on attributes
+  and supported options.
   """
   use Mix.Task
 
@@ -36,19 +36,34 @@ defmodule Mix.Tasks.Phx.Gen.Html do
   alias Mix.Tasks.Phx.Gen
 
   def run(args) do
-    if Mix.Project.umbrella? do
-      Mix.raise "mix phx.gen.html can only be run inside an application directory"
-    end
-
-    {context, schema} = Gen.Context.build(args)
-    binding = [context: context, schema: schema, inputs: inputs(schema)]
+    {context, schema} = build(args)
+    binding = [context: context, schema: schema]
     paths = Mix.Phoenix.generator_paths()
 
     context
+    |> Context.inject_schema_access(binding, paths)
     |> copy_new_files(paths, binding)
     |> print_shell_instructions()
   end
 
+  def build(args) do
+    if Mix.Project.umbrella? do
+      Mix.raise "mix phx.gen.html and phx.gen.json can only be run inside an application directory"
+    end
+    switches = [binary_id: :boolean, table: :string]
+    {opts, parsed, _} = OptionParser.parse(args, switches: switches)
+    [context_name, schema_name, plural | schema_args] = validate_args!(parsed)
+
+    table = Keyword.get(opts, :table, Phoenix.Naming.underscore(context_name) <> "_#{plural}")
+    schema_module = inspect(Module.concat(context_name, schema_name))
+
+    schema = Gen.Schema.build([schema_module, plural | schema_args] ++ ["--table", table], __MODULE__)
+    context = Context.new(context_name, schema, opts)
+    Mix.Phoenix.check_module_name_availability!(context.module)
+
+    {context, schema}
+  end
+
   def copy_new_files(%Context{schema: schema} = context, paths, binding) do
     web_prefix = Mix.Phoenix.web_prefix()
     test_prefix = Mix.Phoenix.test_prefix()
@@ -62,56 +77,55 @@ defmodule Mix.Tasks.Phx.Gen.Html do
       {:eex, "show.html.eex",       Path.join(web_prefix, "templates/#{schema.singular}/show.html.eex")},
       {:eex, "view.ex",             Path.join(web_prefix, "views/#{schema.singular}_view.ex")},
       {:eex, "controller_test.exs", Path.join(test_prefix, "controllers/#{schema.singular}_controller_test.exs")},
+      {:new_eex, "context_test.exs", "test/#{context.basename}_test.exs"},
     ]
+    Gen.Schema.copy_new_files(schema, paths, binding)
 
-    Gen.Context.copy_new_files(context, paths, binding)
     context
   end
 
-  def print_shell_instructions(%Context{schema: schema} = context) do
+  def print_shell_instructions(%Context{schema: schema}) do
     Mix.shell.info """
 
     Add the resource to your browser scope in lib/#{Mix.Phoenix.otp_app()}/web/router.ex:
 
         resources "/#{schema.plural}", #{inspect schema.alias}Controller
     """
-    Gen.Context.print_shell_instructions(context)
+    Gen.Schema.print_shell_instructions(schema)
   end
 
-  defp inputs(%Schema{} = schema) do
-    Enum.map(schema.attrs, fn
-      {_, {:array, _}} ->
-        {nil, nil, nil}
-      {_, {:references, _}} ->
-        {nil, nil, nil}
-      {key, :integer} ->
-        {label(key), ~s(<%= number_input f, #{inspect(key)}, class: "form-control" %>), error(key)}
-      {key, :float} ->
-        {label(key), ~s(<%= number_input f, #{inspect(key)}, step: "any", class: "form-control" %>), error(key)}
-      {key, :decimal} ->
-        {label(key), ~s(<%= number_input f, #{inspect(key)}, step: "any", class: "form-control" %>), error(key)}
-      {key, :boolean} ->
-        {label(key), ~s(<%= checkbox f, #{inspect(key)}, class: "checkbox" %>), error(key)}
-      {key, :text} ->
-        {label(key), ~s(<%= textarea f, #{inspect(key)}, class: "form-control" %>), error(key)}
-      {key, :date} ->
-        {label(key), ~s(<%= date_select f, #{inspect(key)}, class: "form-control" %>), error(key)}
-      {key, :time} ->
-        {label(key), ~s(<%= time_select f, #{inspect(key)}, class: "form-control" %>), error(key)}
-      {key, :utc_datetime} ->
-        {label(key), ~s(<%= datetime_select f, #{inspect(key)}, class: "form-control" %>), error(key)}
-      {key, :naive_datetime} ->
-        {label(key), ~s(<%= datetime_select f, #{inspect(key)}, class: "form-control" %>), error(key)}
-      {key, _}  ->
-        {label(key), ~s(<%= text_input f, #{inspect(key)}, class: "form-control" %>), error(key)}
-    end)
+  defp validate_args!([context, schema, _plural | _] = args) do
+    cond do
+      not Context.valid?(context) ->
+        raise_with_help "Expected the context, #{inspect context}, to be a valid module name"
+      not Schema.valid?(schema) ->
+        raise_with_help "Expected the schema, #{inspect schema}, to be a valid module name"
+      context == schema ->
+        raise_with_help "The context and schema should have different names"
+      true ->
+        args
+    end
   end
 
-  defp label(key) do
-    ~s(<%= label f, #{inspect(key)}, class: "control-label" %>)
+  defp validate_args!(_) do
+    raise_with_help "Invalid arguments"
   end
 
-  defp error(field) do
-    ~s(<%= error_tag f, #{inspect(field)} %>)
+  @spec raise_with_help(String.t) :: no_return()
+  def raise_with_help(msg) do
+    Mix.raise """
+    #{msg}
+
+    mix phx.gen.html and mix phx.gen.json expect a context module name,
+    followed by singular and plural names of the generated resource, ending
+    with any number of attributes. For example:
+
+        mix phx.gen.html Accounts User users name:string
+        mix phx.gen.json Accounts User users name:string
+
+    The context serves as the API boundary for the given resource.
+    Multiple resources may belong to a context and a resource may be
+    split over distinct contexts (such as Accounts.User and Blog.User).
+    """
   end
 end
diff --git a/lib/mix/tasks/phx.gen.json.ex b/lib/mix/tasks/phx.gen.json.ex
index e7d9ebf08..9186be262 100644
--- a/lib/mix/tasks/phx.gen.json.ex
+++ b/lib/mix/tasks/phx.gen.json.ex
@@ -16,35 +16,32 @@ defmodule Mix.Tasks.Phx.Gen.Json do
     * a schema in accounts/user.ex, with an `accounts_users` table
     * a view in web/views/user_view.ex
     * a controller in web/controllers/user_controller.ex
+    * default CRUD templates in web/templates/user
 
   As well as a migration file for the repository and test files for
   generated context and controller features.
 
-  ## Schema options
+  ## Schema table name
 
   By deault, the schema table name will be the plural name, namespaced by the
   context name. You can customize this value by providing the `--table`
   option to the generator.
 
-  Read the documentation for `phx.gen.schema` for more information on
-  attributes and supported options.
+  Read the documentation for `phx.gen.schema` for more information on attributes
+  and supported options.
   """
-
   use Mix.Task
 
   alias Mix.Phoenix.Context
   alias Mix.Tasks.Phx.Gen
 
   def run(args) do
-    if Mix.Project.umbrella? do
-      Mix.raise "mix phx.gen.json can only be run inside an application directory"
-    end
-
-    {context, schema} = Gen.Context.build(args)
+    {context, schema} = Gen.Html.build(args)
     binding = [context: context, schema: schema]
     paths = Mix.Phoenix.generator_paths()
 
     context
+    |> Context.inject_schema_access(binding, paths)
     |> copy_new_files(paths, binding)
     |> print_shell_instructions()
   end
@@ -54,24 +51,28 @@ defmodule Mix.Tasks.Phx.Gen.Json do
     test_prefix = Mix.Phoenix.test_prefix()
 
     Mix.Phoenix.copy_from paths, "priv/templates/phx.gen.json", "", binding, [
-      {:eex,     "controller.ex",          Path.join(web_prefix, "controllers/#{schema.singular}_controller.ex")},
-      {:eex,     "view.ex",                Path.join(web_prefix, "views/#{schema.singular}_view.ex")},
-      {:eex,     "controller_test.exs",    Path.join(test_prefix, "controllers/#{schema.singular}_controller_test.exs")},
-      {:new_eex, "changeset_view.ex",      Path.join(web_prefix, "views/changeset_view.ex")},
+      {:eex, "controller.ex",       Path.join(web_prefix, "controllers/#{schema.singular}_controller.ex")},
+      {:eex, "view.ex",             Path.join(web_prefix, "views/#{schema.singular}_view.ex")},
+      {:eex, "controller_test.exs", Path.join(test_prefix, "controllers/#{schema.singular}_controller_test.exs")},
+      {:new_eex, "changeset_view.ex", Path.join(web_prefix, "views/changeset_view.ex")},
       {:new_eex, "fallback_controller.ex", Path.join(web_prefix, "controllers/fallback_controller.ex")},
     ]
 
-    Gen.Context.copy_new_files(context, paths, binding)
+    Mix.Phoenix.copy_from paths, "priv/templates/phx.gen.html", "", binding, [
+      {:new_eex, "context_test.exs", "test/#{context.basename}_test.exs"}
+    ]
+    Gen.Schema.copy_new_files(schema, paths, binding)
+
     context
   end
 
-  def print_shell_instructions(%Context{schema: schema} = context) do
+  def print_shell_instructions(%Context{schema: schema}) do
     Mix.shell.info """
 
     Add the resource to your api scope in lib/#{Mix.Phoenix.otp_app()}/web/router.ex:
 
         resources "/#{schema.plural}", #{inspect schema.alias}Controller, except: [:new, :edit]
     """
-    Gen.Context.print_shell_instructions(context)
+    Gen.Schema.print_shell_instructions(schema)
   end
 end
diff --git a/lib/mix/tasks/phx.gen.schema.ex b/lib/mix/tasks/phx.gen.schema.ex
index c2ca19598..e7961ae84 100644
--- a/lib/mix/tasks/phx.gen.schema.ex
+++ b/lib/mix/tasks/phx.gen.schema.ex
@@ -84,10 +84,6 @@ defmodule Mix.Tasks.Phx.Gen.Schema do
   @switches [migration: :boolean, binary_id: :boolean, table: :string]
 
   def run(args) do
-    if Mix.Project.umbrella? do
-      Mix.raise "mix phx.gen.schema can only be run inside an application directory"
-    end
-
     schema = build(args)
     paths = Mix.Phoenix.generator_paths()
 
@@ -97,6 +93,9 @@ defmodule Mix.Tasks.Phx.Gen.Schema do
   end
 
   def build(args, help \\ __MODULE__) do
+    if Mix.Project.umbrella? do
+      Mix.raise "mix phx.gen.schema can only be run inside an application directory"
+    end
     {opts, parsed, _} = OptionParser.parse(args, switches: @switches)
     [schema_name, plural | attrs] = validate_args!(parsed, help)
 
diff --git a/priv/templates/phx.gen.context/context.ex b/priv/templates/phx.gen.html/context.ex
similarity index 100%
rename from priv/templates/phx.gen.context/context.ex
rename to priv/templates/phx.gen.html/context.ex
diff --git a/priv/templates/phx.gen.context/context_test.exs b/priv/templates/phx.gen.html/context_test.exs
similarity index 100%
rename from priv/templates/phx.gen.context/context_test.exs
rename to priv/templates/phx.gen.html/context_test.exs
diff --git a/priv/templates/phx.gen.html/form.html.eex b/priv/templates/phx.gen.html/form.html.eex
index eabc93fef..5d57cd8ca 100644
--- a/priv/templates/phx.gen.html/form.html.eex
+++ b/priv/templates/phx.gen.html/form.html.eex
@@ -4,7 +4,7 @@
       <p>Oops, something went wrong! Please check the errors below.</p>
     </div>
   <%% end %>
-<%= for {label, input, error} <- inputs, input do %>
+<%= for {label, input, error} <- context.inputs, input do %>
   <div class="form-group">
     <%= label %>
     <%= input %>


Test patch:
diff --git a/installer/test/phoenix_new_test.exs b/installer/test/phoenix_new_test.exs
index f22821466..39b3b160a 100644
--- a/installer/test/phoenix_new_test.exs
+++ b/installer/test/phoenix_new_test.exs
@@ -112,7 +112,7 @@ defmodule Mix.Tasks.Phoenix.NewTest do
       assert msg =~ "$ cd photo_blog"
       assert msg =~ "$ mix phoenix.server"
 
-      assert_received {:mix_shell, :info, ["Before moving on, configure your database in config/dev.exs" <> _ = msg]}
+      assert_received {:mix_shell, :info, ["Before moving on," <> _ = msg]}
       assert msg =~ "$ mix ecto.create"
 
       # Channels
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index c02db6756..23c60321b 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -163,7 +163,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert msg =~ "$ cd phx_umb"
       assert msg =~ "$ mix phx.server"
 
-      assert_received {:mix_shell, :info, ["Before moving on, configure your database in apps/#{@app}/config/dev.exs" <> _ = msg]}
+      assert_received {:mix_shell, :info, ["Before moving on," <> _ = msg]}
       assert msg =~ "$ mix ecto.create"
 
       # Channels
@@ -509,8 +509,6 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
         assert msg =~ "$ cd another"
         assert msg =~ "$ mix phx.server"
 
-        refute_received {:mix_shell, :info, ["Before moving on, configure your database" <> _]}
-
         # Channels
         assert File.exists?("another/lib/another/channels")
         assert_file "another/lib/another/channels/user_socket.ex", ~r"defmodule Another.UserSocket"
diff --git a/priv/templates/phx.gen.context/schema_access.ex b/priv/templates/phx.gen.html/schema_access.ex
similarity index 100%
rename from priv/templates/phx.gen.context/schema_access.ex
rename to priv/templates/phx.gen.html/schema_access.ex
diff --git a/test/mix/tasks/phx.gen.context_test.exs b/test/mix/tasks/phx.gen.context_test.exs
deleted file mode 100644
index 22f5536cd..000000000
--- a/test/mix/tasks/phx.gen.context_test.exs
+++ /dev/null
@@ -1,137 +0,0 @@
-Code.require_file "../../../installer/test/mix_helper.exs", __DIR__
-
-defmodule Phoenix.DupContext do
-end
-
-defmodule Mix.Tasks.Phx.Gen.ContextTest do
-  use ExUnit.Case
-  import MixHelper
-  alias Mix.Tasks.Phx.Gen
-  alias Mix.Phoenix.{Context, Schema}
-
-  setup do
-    Mix.Task.clear()
-    :ok
-  end
-
-  test "new context", config do
-    in_tmp_project config.test, fn ->
-      schema = Schema.new("Blog.Post", "posts", [], [])
-      context = Context.new("Blog", schema, [])
-
-      assert %Context{
-        pre_existing?: false,
-        alias: Blog,
-        base_module: Phoenix,
-        basename: "blog",
-        dir: "lib/phoenix/blog",
-        file: "lib/phoenix/blog/blog.ex",
-        module: Phoenix.Blog,
-        web_module: Phoenix.Web,
-        schema: %Mix.Phoenix.Schema{
-          alias: Post,
-          file: "lib/phoenix/blog/post.ex",
-          human_plural: "Posts",
-          human_singular: "Post",
-          module: Phoenix.Blog.Post,
-          plural: "posts",
-          singular: "post"
-        }} = context
-    end
-  end
-
-  test "new existing context", config do
-    in_tmp_project config.test, fn ->
-      File.mkdir_p!("lib/phoenix/blog")
-      File.write!("lib/phoenix/blog/blog.ex", """
-      defmodule Phoenix.Blog do
-      end
-      """)
-
-      schema = Schema.new("Blog.Post", "posts", [], [])
-      assert %Context{pre_existing?: true} = Context.new("Blog", schema, [])
-    end
-  end
-
-  test "invalid mix arguments", config do
-    in_tmp_project config.test, fn ->
-      assert_raise Mix.Error, ~r/Expected the context, "blog", to be a valid module name/, fn ->
-        Gen.Context.run(~w(blog Post posts title:string))
-      end
-
-      assert_raise Mix.Error, ~r/Expected the schema, "posts", to be a valid module name/, fn ->
-        Gen.Context.run(~w(Post posts title:string))
-      end
-
-      assert_raise Mix.Error, ~r/The context and schema should have different names/, fn ->
-        Gen.Context.run(~w(Blog Blog blogs))
-      end
-
-      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->
-        Gen.Context.run(~w(Blog.Post posts))
-      end
-
-      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->
-        Gen.Context.run(~w(Blog Post))
-      end
-    end
-  end
-
-  test "name is already defined", config do
-    in_tmp_project config.test, fn ->
-      assert_raise Mix.Error, ~r/already taken/, fn ->
-        Gen.Context.run ~w(DupContext Post dups)
-      end
-    end
-  end
-
-  test "generates context and handles existing contexts", config do
-    in_tmp_project config.test, fn ->
-      Gen.Context.run(~w(Blog Post posts slug:unique title:string))
-
-      assert_file "lib/phoenix/blog/post.ex", fn file ->
-        assert file =~ "field :title, :string"
-      end
-
-      assert_file "lib/phoenix/blog/blog.ex", fn file ->
-        assert file =~ "def get_post!"
-        assert file =~ "def list_posts"
-        assert file =~ "def create_post"
-        assert file =~ "def update_post"
-        assert file =~ "def delete_post"
-        assert file =~ "def change_post"
-      end
-
-      assert_file "test/blog_test.exs", fn file ->
-        assert file =~ "use Phoenix.DataCase"
-      end
-
-      assert [path] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
-      assert_file path, fn file ->
-        assert file =~ "create table(:blog_posts)"
-        assert file =~ "add :title, :string"
-        assert file =~ "create unique_index(:blog_posts, [:slug])"
-      end
-
-      Gen.Context.run(~w(Blog Comment comments title:string))
-      assert_file "lib/phoenix/blog/comment.ex", fn file ->
-        assert file =~ "field :title, :string"
-      end
-
-      assert [path] = Path.wildcard("priv/repo/migrations/*_create_blog_comment.exs")
-      assert_file path, fn file ->
-        assert file =~ "create table(:blog_comments)"
-        assert file =~ "add :title, :string"
-      end
-
-      assert_file "lib/phoenix/blog/blog.ex", fn file ->
-        assert file =~ "def get_comment!"
-        assert file =~ "def list_comments"
-        assert file =~ "def create_comment"
-        assert file =~ "def update_comment"
-        assert file =~ "def delete_comment"
-        assert file =~ "def change_comment"
-      end
-    end
-  end
-end
diff --git a/test/mix/tasks/phx.gen.html_test.exs b/test/mix/tasks/phx.gen.html_test.exs
index 5b55d2935..40fdcf368 100644
--- a/test/mix/tasks/phx.gen.html_test.exs
+++ b/test/mix/tasks/phx.gen.html_test.exs
@@ -1,15 +1,58 @@
 Code.require_file "../../../installer/test/mix_helper.exs", __DIR__
 
+defmodule Phoenix.DupContext do
+end
+
 defmodule Mix.Tasks.Phx.Gen.HtmlTest do
   use ExUnit.Case
   import MixHelper
   alias Mix.Tasks.Phx.Gen
+  alias Mix.Phoenix.{Context, Schema}
 
   setup do
     Mix.Task.clear()
     :ok
   end
 
+  test "new context", config do
+    in_tmp_project config.test, fn ->
+      schema = Schema.new("Blog.Post", "posts", [], [])
+      context = Context.new("Blog", schema, [])
+
+      assert %Context{
+        pre_existing?: false,
+        alias: Blog,
+        base_module: Phoenix,
+        basename: "blog",
+        dir: "lib/phoenix/blog",
+        file: "lib/phoenix/blog/blog.ex",
+        module: Phoenix.Blog,
+        web_module: Phoenix.Web,
+        schema: %Mix.Phoenix.Schema{
+          alias: Post,
+          file: "lib/phoenix/blog/post.ex",
+          human_plural: "Posts",
+          human_singular: "Post",
+          module: Phoenix.Blog.Post,
+          plural: "posts",
+          singular: "post"
+        }} = context
+    end
+  end
+
+  test "new existing context", config do
+    in_tmp_project config.test, fn ->
+      File.mkdir_p!("lib/phoenix/blog")
+      File.write!("lib/phoenix/blog/blog.ex", """
+      defmodule Phoenix.Blog do
+      end
+      """)
+
+      schema = Schema.new("Blog.Post", "posts", [], [])
+      assert %Context{pre_existing?: true} = Context.new("Blog", schema, [])
+    end
+  end
+
   test "invalid mix arguments", config do
     in_tmp_project config.test, fn ->
       assert_raise Mix.Error, ~r/Expected the context, "blog", to be a valid module name/, fn ->
@@ -34,13 +77,26 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
     end
   end
 
-  test "generates html resource and handles existing contexts", config do
+  test "name is already defined", config do
+    in_tmp_project config.test, fn ->
+      assert_raise Mix.Error, ~r/already taken/, fn ->
+        Gen.Html.run ~w(DupContext Post dups)
+      end
+    end
+  end
+
+  test "generates html context and handles existing contexts", config do
     in_tmp_project config.test, fn ->
-      Gen.Html.run(~w(Blog Post posts slug:unique title:string))
+      Gen.Html.run(~w(Blog Post posts slug:unique title:string published_at:datetime))
 
-      assert_file "lib/phoenix/blog/post.ex"
+      assert_file "lib/phoenix/blog/post.ex", fn file ->
+        assert file =~ "field :published_at, :naive_datetime"
+      end
       assert_file "lib/phoenix/blog/blog.ex"
-      assert_file "test/blog_test.exs"
+
+      assert_file "test/blog_test.exs", fn file ->
+        assert file =~ "use Phoenix.DataCase"
+      end
 
       assert_file "test/web/controllers/post_controller_test.exs", fn file ->
         assert file =~ "defmodule Phoenix.Web.PostControllerTest"
@@ -49,7 +105,7 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
       assert [path] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
       assert_file path, fn file ->
         assert file =~ "create table(:blog_posts)"
-        assert file =~ "add :title, :string"
+        assert file =~ "add :published_at, :naive_datetime"
         assert file =~ "create unique_index(:blog_posts, [:slug])"
       end
 
@@ -64,8 +120,12 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
         assert file =~ "Blog.change_post"
       end
 
-      Gen.Html.run(~w(Blog Comment comments title:string))
-      assert_file "lib/phoenix/blog/comment.ex"
+
+      Gen.Html.run(~w(Blog Comment comments title:string published_at:naive_datetime edited_at:utc_datetime))
+      assert_file "lib/phoenix/blog/comment.ex", fn file ->
+        assert file =~ "field :published_at, :naive_datetime"
+        assert file =~ "field :edited_at, :utc_datetime"
+      end
 
       assert_file "test/web/controllers/comment_controller_test.exs", fn file ->
         assert file =~ "defmodule Phoenix.Web.CommentControllerTest"
@@ -74,7 +134,8 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
       assert [path] = Path.wildcard("priv/repo/migrations/*_create_blog_comment.exs")
       assert_file path, fn file ->
         assert file =~ "create table(:blog_comments)"
-        assert file =~ "add :title, :string"
+        assert file =~ "add :published_at, :naive_datetime"
+        assert file =~ "add :edited_at, :utc_datetime"
       end
 
       assert_file "lib/phoenix/web/controllers/comment_controller.ex", fn file ->
@@ -89,4 +150,40 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
       end
     end
   end
+
+  test "with binary_id properly generates controller test", config do
+    in_tmp_project config.test, fn ->
+      with_generator_env [binary_id: true, sample_binary_id: "abcd"], fn ->
+        Gen.Html.run(~w(Blog Post posts))
+
+        assert_file "test/web/controllers/post_controller_test.exs", fn file ->
+          assert file =~ ~S|post_path(conn, :show, "abcd")|
+        end
+      end
+
+      with_generator_env [binary_id: true], fn ->
+        Gen.Html.run(~w(Blog Post posts))
+
+        assert_file "test/web/controllers/post_controller_test.exs", fn file ->
+          assert file =~ ~S|post_path(conn, :show, "11111111-1111-1111-1111-111111111111")|
+        end
+      end
+    end
+  end
+
+  test "plural can't contain a colon" do
+    assert_raise Mix.Error, fn ->
+      Gen.Html.run(~w(Blog Post title:string))
+    end
+  end
+
+  test "plural can't have uppercased characters or camelized format" do
+    assert_raise Mix.Error, fn ->
+      Gen.Html.run(~w(Blog Post Posts title:string))
+    end
+
+    assert_raise Mix.Error, fn ->
+      Gen.Html.run(~w(Blog Post BlogPosts title:string))
+    end
+  end
 end
diff --git a/test/mix/tasks/phx.gen.json_test.exs b/test/mix/tasks/phx.gen.json_test.exs
index 969b43cce..7d5f86ef9 100644
--- a/test/mix/tasks/phx.gen.json_test.exs
+++ b/test/mix/tasks/phx.gen.json_test.exs
@@ -1,5 +1,8 @@
 Code.require_file "../../../installer/test/mix_helper.exs", __DIR__
 
+defmodule Phoenix.DupJsonContext do
+end
+
 defmodule Mix.Tasks.Phx.Gen.JsonTest do
   use ExUnit.Case
   import MixHelper
@@ -20,10 +23,6 @@ defmodule Mix.Tasks.Phx.Gen.JsonTest do
         Gen.Json.run(~w(Post posts title:string))
       end
 
-      assert_raise Mix.Error, ~r/The context and schema should have different names/, fn ->
-        Gen.Json.run(~w(Blog Blog blogs))
-      end
-
       assert_raise Mix.Error, ~r/Invalid arguments/, fn ->
         Gen.Json.run(~w(Blog.Post posts))
       end
@@ -34,7 +33,15 @@ defmodule Mix.Tasks.Phx.Gen.JsonTest do
     end
   end
 
-  test "generates json resource", config do
+  test "name is already defined", config do
+    in_tmp_project config.test, fn ->
+      assert_raise Mix.Error, ~r/already taken/, fn ->
+        Gen.Json.run ~w(DupJsonContext Post dups)
+      end
+    end
+  end
+
+  test "generates json context", config do
     in_tmp_project config.test, fn ->
       Gen.Json.run(["Blog", "Post", "posts", "title:string"])
 
@@ -66,4 +73,40 @@ defmodule Mix.Tasks.Phx.Gen.JsonTest do
       end
     end
   end
+
+  test "with binary_id properly generates controller test", config do
+    in_tmp_project config.test, fn ->
+      with_generator_env [binary_id: true, sample_binary_id: "abcd"], fn ->
+        Gen.Json.run(~w(Blog Post posts))
+
+        assert_file "test/web/controllers/post_controller_test.exs", fn file ->
+          assert file =~ ~S|post_path(conn, :show, "abcd")|
+        end
+      end
+
+      with_generator_env [binary_id: true], fn ->
+        Gen.Json.run(~w(Blog Post posts))
+
+        assert_file "test/web/controllers/post_controller_test.exs", fn file ->
+          assert file =~ ~S|post_path(conn, :show, "11111111-1111-1111-1111-111111111111")|
+        end
+      end
+    end
+  end
+
+  test "plural can't contain a colon" do
+    assert_raise Mix.Error, fn ->
+      Gen.Json.run(~w(Blog Post title:string))
+    end
+  end
+
+  test "plural can't have uppercased characters or camelized format" do
+    assert_raise Mix.Error, fn ->
+      Gen.Json.run(~w(Blog Post Posts title:string))
+    end
+
+    assert_raise Mix.Error, fn ->
+      Gen.Json.run(~w(Blog Post BlogPosts title:string))
+    end
+  end
 end
diff --git a/test/mix/tasks/phx.gen.schema_test.exs b/test/mix/tasks/phx.gen.schema_test.exs
index d28122286..d881312c5 100644
--- a/test/mix/tasks/phx.gen.schema_test.exs
+++ b/test/mix/tasks/phx.gen.schema_test.exs
@@ -36,6 +36,98 @@ defmodule Mix.Tasks.Phx.Gen.SchemaTest do
     end
   end
 
+  test "generates custom table name", config do
+    in_tmp_project config.test, fn ->
+      Gen.Schema.run(~w(Blog.Post posts --table cms_posts))
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
+
+      assert_file migration, fn file ->
+        assert file =~ "create table(:cms_posts) do"
+      end
+    end
+  end
+
+  test "generates nested context", config do
+    in_tmp_project config.test, fn ->
+      Gen.Schema.run(~w(Blog.Admin.User users name:string))
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_blog_admin_user.exs")
+
+      assert_file migration, fn file ->
+        assert file =~ "defmodule Phoenix.Repo.Migrations.CreatePhoenix.Blog.Admin.User do"
+        assert file =~ "create table(:users) do"
+      end
+
+      assert_file "lib/phoenix/blog/admin/user.ex", fn file ->
+        assert file =~ "defmodule Phoenix.Blog.Admin.User do"
+        assert file =~ "schema \"users\" do"
+      end
+    end
+  end
+
+  test "generates belongs_to associations with association table provided by user", config do
+    in_tmp_project config.test, fn ->
+      Gen.Schema.run(~w(Blog.Post posts title user_id:references:users))
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
+
+      assert_file migration, fn file ->
+        assert file =~ "add :user_id, {:references, :users}"
+      end
+
+      assert_file "lib/phoenix/blog/post.ex", fn file ->
+        assert file =~ "field :user_id, {:references, :users}"
+      end
+    end
+  end
+
+  test "generates unique_index" , config do
+    in_tmp_project config.test, fn ->
+      Gen.Schema.run(~w(Blog.Post posts title:unique unique_int:integer:unique))
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
+
+      assert_file migration, fn file ->
+        assert file =~ "defmodule Phoenix.Repo.Migrations.CreatePhoenix.Blog.Post do"
+        assert file =~ "create table(:posts) do"
+        assert file =~ "add :title, :string"
+        assert file =~ "add :unique_int, :integer"
+        assert file =~ "create unique_index(:posts, [:title])"
+        assert file =~ "create unique_index(:posts, [:unique_int])"
+      end
+
+      assert_file "lib/phoenix/blog/post.ex", fn file ->
+        assert file =~ "defmodule Phoenix.Blog.Post do"
+        assert file =~ "schema \"posts\" do"
+        assert file =~ "field :title, :string"
+        assert file =~ "field :unique_int, :integer"
+      end
+    end
+  end
+
+  test "generates indices on belongs_to associations", config do
+    in_tmp_project config.test, fn ->
+      Gen.Schema.run(~w(Blog.Post posts title user_id:references:users unique_post_id:references:posts:unique))
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
+
+      assert_file migration, fn file ->
+        assert file =~ "defmodule Phoenix.Repo.Migrations.CreatePhoenix.Blog.Post do"
+        assert file =~ "create table(:posts) do"
+        assert file =~ "add :user_id, {:references, :users}"
+        assert file =~ "add :unique_post_id, {:references, :posts}"
+        assert file =~ "create unique_index(:posts, [:unique_post_id])"
+      end
+
+      assert_file "lib/phoenix/blog/post.ex", fn file ->
+        assert file =~ "defmodule Phoenix.Blog.Post do"
+        assert file =~ "field :user_id, {:references, :users}"
+        assert file =~ "field :unique_post_id, {:references, :posts}"
+      end
+    end
+  end
+
   test "plural can't contain a colon", config do
     in_tmp_project config.test, fn ->
       assert_raise Mix.Error, ~r/plural/, fn ->
@@ -64,29 +156,78 @@ defmodule Mix.Tasks.Phx.Gen.SchemaTest do
     end
   end
 
+  test "table name missing from references", config do
+    in_tmp_project config.test, fn ->
+      assert_raise Mix.Error, ~r/missing/, fn ->
+        Gen.Schema.run(~w(Blog.Post posts user_id:references))
+      end
+    end
+  end
+
   test "generates schema", config do
     in_tmp_project config.test, fn ->
-      Gen.Schema.run(~w(Blog.Post posts title:string))
+      Gen.Schema.run(~w(Blog.Post blog_posts title:string))
+
       assert_file "lib/phoenix/blog/post.ex"
-      assert [_] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
+
+      assert_file migration, fn file ->
+        assert file =~ "create table(:blog_posts) do"
+      end
+    end
+  end
+
+  test "generates migration with binary_id", config do
+    in_tmp_project config.test, fn ->
+      Gen.Schema.run(~w(Blog.Post posts title user_id:references:users --binary-id))
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
+
+      assert_file migration, fn file ->
+        assert file =~ "create table(:posts, primary_key: false) do"
+        assert file =~ "add :id, :binary_id, primary_key: true"
+        assert file =~ "add :user_id, {:references, :users}"
+      end
+    end
+  end
+
+  test "skips migration with --no-migration option", config do
+    in_tmp_project config.test, fn ->
+      Gen.Schema.run(~w(Blog.Post posts --no-migration))
+
+      assert [] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
     end
   end
 
-  test "generates schema with proper datetime types" do
-    Gen.Schema.run(~w(Blog.Comment comments title:string drafted_at:datetime published_at:naive_datetime edited_at:utc_datetime))
+  test "table name omitted", config do
+    in_tmp_project config.test, fn ->
+      assert_raise Mix.Error, fn ->
+        Gen.Schema.run(~w(Blog.Post))
+      end
+    end
+  end
+
+  test "uses defaults from :generators configuration" do
+    in_tmp_project "uses defaults from generators configuration (migration)", fn ->
+      with_generator_env [migration: false], fn ->
+        Gen.Schema.run(~w(Blog.Post posts))
 
-    assert_file "lib/phoenix/blog/comment.ex", fn file ->
-      assert file =~ "field :drafted_at, :naive_datetime"
-      assert file =~ "field :published_at, :naive_datetime"
-      assert file =~ "field :edited_at, :utc_datetime"
+        assert [] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
+      end
     end
 
-    assert [path] = Path.wildcard("priv/repo/migrations/*_create_blog_comment.exs")
-    assert_file path, fn file ->
-      assert file =~ "create table(:comments)"
-      assert file =~ "add :drafted_at, :naive_datetime"
-      assert file =~ "add :published_at, :naive_datetime"
-      assert file =~ "add :edited_at, :utc_datetime"
+    in_tmp_project "uses defaults from generators configuration (binary_id)", fn ->
+      with_generator_env [binary_id: true], fn ->
+        Gen.Schema.run(~w(Blog.Post posts))
+
+        assert [migration] = Path.wildcard("priv/repo/migrations/*_create_blog_post.exs")
+
+        assert_file migration, fn file ->
+          assert file =~ "create table(:posts, primary_key: false) do"
+          assert file =~ "add :id, :binary_id, primary_key: true"
+        end
+      end
     end
   end
 end