Repository: phoenixframework/phoenix (elixir)

Issue:
Small typo in Phoenix Channels hexdocs
<!--
### Precheck

* For general discussions and support, use Stack Overflow or the Elixir Forum
* For proposing and discussing new features, start a thread on the Phoenix Core mailing list
* For bugs, do a quick search and make sure the bug has not yet been reported
* Ensure that this issue is related to the Phoenix library and not one of the dependencies listed in mix.exs (Ecto, Plug, etc.)
* All checked? Be nice and have fun!
-->

### Environment

* Elixir version (elixir -v): N/A
* Phoenix version (mix deps): N/A
* NodeJS version (node -v): N/A
* NPM version (npm -v): N/A
* Operating system: N/A

### Expected behavior

There's a small typo in the Phoenix Channels hexdocs page.
https://hexdocs.pm/phoenix/channels.html#using-token-authentication

> Step 3 - Pass the Token to the Socket Constructor and Verify
> ...
> In our JavaScript, we can use the token set previously ~when to pass the token~ when constructing the Socket:


Pull request:
Fix typo in Phoenix.Channels docs (#4084)
Closes #4084

Patch:
diff --git a/guides/ecto.md b/guides/ecto.md
index a5e9c852a..957b5c073 100644
--- a/guides/ecto.md
+++ b/guides/ecto.md
@@ -470,7 +470,7 @@ defmodule HelloPhoenix.MixProject do
     [
       {:phoenix, "~> 1.4.0"},
       {:phoenix_ecto, "~> 4.0"},
-      {:ecto_sql, "~> 3.4.4"},
+      {:ecto_sql, "~> 3.5"},
       {:myxql, ">= 0.0.0"},
       {:phoenix_html, "~> 2.11"},
       {:phoenix_live_reload, "~> 1.2", only: :dev},
diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs
index df5851a12..d1628fad1 100644
--- a/installer/templates/phx_single/mix.exs
+++ b/installer/templates/phx_single/mix.exs
@@ -39,7 +39,7 @@ defmodule <%= @app_module %>.MixProject do
     [
       <%= @phoenix_dep %>,<%= if @ecto do %>
       {:phoenix_ecto, "~> 4.1"},
-      {:ecto_sql, "~> 3.4.4"},
+      {:ecto_sql, "~> 3.5"},
       {<%= inspect @adapter_app %>, ">= 0.0.0"},<% end %><%= if @html do %><%= if @live do %>
       {:phoenix_live_view, "~> 0.15.0"},
       {:floki, ">= 0.27.0", only: :test},<% end %>
diff --git a/installer/templates/phx_umbrella/apps/app_name/mix.exs b/installer/templates/phx_umbrella/apps/app_name/mix.exs
index c572601b4..b3baa9e2a 100644
--- a/installer/templates/phx_umbrella/apps/app_name/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name/mix.exs
@@ -37,7 +37,7 @@ defmodule <%= @app_module %>.MixProject do
   defp deps do
     [
       {:phoenix_pubsub, "~> 2.0"}<%= if @ecto do %>,
-      {:ecto_sql, "~> 3.4.4"},
+      {:ecto_sql, "~> 3.5"},
       {:<%= @adapter_app %>, ">= 0.0.0"},
       {:jason, "~> 1.0"}<% end %>
     ]
diff --git a/lib/mix/phoenix/schema.ex b/lib/mix/phoenix/schema.ex
index a2782edcf..cfcfae341 100644
--- a/lib/mix/phoenix/schema.ex
+++ b/lib/mix/phoenix/schema.ex
@@ -52,7 +52,8 @@ defmodule Mix.Phoenix.Schema do
     :utc_datetime,
     :utc_datetime_usec,
     :uuid,
-    :binary
+    :binary,
+    :enum
   ]
 
   def valid_types, do: @valid_types
@@ -232,6 +233,12 @@ defmodule Mix.Phoenix.Schema do
     end
   end
 
+  def type_for_migration({:enum, _}), do: :string
+  def type_for_migration(other), do: other
+
+  def type_and_opts_for_schema({:enum, opts}), do: ~s|Ecto.Enum, values: #{inspect Keyword.get(opts, :values)}|
+  def type_and_opts_for_schema(other), do: inspect other
+
   defp date_value?(%{day: _day, month: _month, year: _year}), do: true
   defp date_value?(_value), do: false
 
@@ -265,6 +272,7 @@ defmodule Mix.Phoenix.Schema do
   defp type_to_default(key, t, :create) do
     case t do
         {:array, _}     -> []
+        {:enum, values} -> values |> translate_enum_vals() |> hd()
         :integer        -> 42
         :float          -> 120.5
         :decimal        -> "120.5"
@@ -285,6 +293,7 @@ defmodule Mix.Phoenix.Schema do
   defp type_to_default(key, t, :update) do
     case t do
         {:array, _}     -> []
+        {:enum, values} -> values |> translate_enum_vals() |> tl() |> hd()
         :integer        -> 43
         :float          -> 456.7
         :decimal        -> "456.7"
@@ -315,6 +324,14 @@ defmodule Mix.Phoenix.Schema do
   defp build_utc_naive_datetime,
     do: NaiveDateTime.truncate(build_utc_naive_datetime_usec(), :second)
 
+
+  @enum_missing_values_error """
+  Enum type requires at least two values
+  For example:
+
+      mix phx.gen.schema Comment comments body:text status:enum:published:unpublished
+  """
+
   defp validate_attr!({name, :datetime}), do: validate_attr!({name, :naive_datetime})
   defp validate_attr!({name, :array}) do
     Mix.raise """
@@ -324,13 +341,25 @@ defmodule Mix.Phoenix.Schema do
         mix phx.gen.schema Post posts settings:array:string
     """
   end
+  defp validate_attr!({_name, :enum}), do: Mix.raise @enum_missing_values_error
   defp validate_attr!({_name, type} = attr) when type in @valid_types, do: attr
+  defp validate_attr!({_name, {:enum, vals}} = attr), do: validate_enum_attr!(vals, attr)
   defp validate_attr!({_name, {type, _}} = attr) when type in @valid_types, do: attr
   defp validate_attr!({_, type}) do
     Mix.raise "Unknown type `#{inspect type}` given to generator. " <>
               "The supported types are: #{@valid_types |> Enum.sort() |> Enum.join(", ")}"
   end
 
+  defp validate_enum_attr!(vals, attr) do
+     vals
+    |> Atom.to_string()
+    |> String.split(":")
+    |> case do
+      [_] -> Mix.raise @enum_missing_values_error
+      _ -> attr
+    end
+  end
+
   defp partition_attrs_and_assocs(schema_module, attrs) do
     {assocs, attrs} =
       Enum.split_with(attrs, fn
@@ -373,11 +402,19 @@ defmodule Mix.Phoenix.Schema do
 
   defp types(attrs) do
     Enum.into(attrs, %{}, fn
+      {key, {:enum, vals}} -> {key, {:enum, values: translate_enum_vals(vals)}}
       {key, {root, val}} -> {key, {root, schema_type(val)}}
       {key, val} -> {key, schema_type(val)}
     end)
   end
 
+  def translate_enum_vals(vals) do
+    vals
+    |> Atom.to_string()
+    |> String.split(":")
+    |> Enum.map(&String.to_atom/1)
+  end
+
   defp schema_type(:text), do: :string
   defp schema_type(:uuid), do: Ecto.UUID
   defp schema_type(val) do
diff --git a/lib/mix/tasks/phx.gen.html.ex b/lib/mix/tasks/phx.gen.html.ex
index 1619d97e2..36a887526 100644
--- a/lib/mix/tasks/phx.gen.html.ex
+++ b/lib/mix/tasks/phx.gen.html.ex
@@ -187,6 +187,8 @@ defmodule Mix.Tasks.Phx.Gen.Html do
         {label(key), ~s(<%= multiple_select f, #{inspect(key)}, ["1": 1, "2": 2] %>), error(key)}
       {key, {:array, _}} ->
         {label(key), ~s(<%= multiple_select f, #{inspect(key)}, ["Option 1": "option1", "Option 2": "option2"] %>), error(key)}
+      {key, {:enum, _}}  ->
+        {label(key), ~s|<%= select f, #{inspect(key)}, Ecto.Enum.values(#{inspect(schema.module)}, #{inspect(key)}), prompt: "Choose a value" %>|, error(key)}
       {key, _}  ->
         {label(key), ~s(<%= text_input f, #{inspect(key)} %>), error(key)}
     end)
diff --git a/lib/mix/tasks/phx.gen.schema.ex b/lib/mix/tasks/phx.gen.schema.ex
index d4c7f2f74..fc2919358 100644
--- a/lib/mix/tasks/phx.gen.schema.ex
+++ b/lib/mix/tasks/phx.gen.schema.ex
@@ -61,6 +61,10 @@ defmodule Mix.Tasks.Phx.Gen.Schema do
 
       mix phx.gen.schema Blog.Post blog_posts title:unique unique_int:integer:unique
 
+  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
diff --git a/priv/templates/phx.gen.schema/migration.exs b/priv/templates/phx.gen.schema/migration.exs
index 4611e8bd3..8cdb2630a 100644
--- a/priv/templates/phx.gen.schema/migration.exs
+++ b/priv/templates/phx.gen.schema/migration.exs
@@ -4,7 +4,7 @@ defmodule <%= inspect schema.repo %>.Migrations.Create<%= Macro.camelize(schema.
   def change do
     create table(:<%= schema.table %><%= if schema.binary_id do %>, primary_key: false<% end %>) do
 <%= if schema.binary_id do %>      add :id, :binary_id, primary_key: true
-<% end %><%= for {k, v} <- schema.attrs do %>      add <%= inspect k %>, <%= inspect v %><%= schema.migration_defaults[k] %>
+<% end %><%= for {k, v} <- schema.attrs do %>      add <%= inspect k %>, <%= inspect Mix.Phoenix.Schema.type_for_migration(v) %><%= schema.migration_defaults[k] %>
 <% end %><%= for {_, i, _, s} <- schema.assocs do %>      add <%= inspect(i) %>, references(<%= inspect(s) %>, on_delete: :nothing<%= if schema.binary_id do %>, type: :binary_id<% end %>)
 <% end %>
       timestamps()
diff --git a/priv/templates/phx.gen.schema/schema.ex b/priv/templates/phx.gen.schema/schema.ex
index 6ef1af53d..1f9a79b6a 100644
--- a/priv/templates/phx.gen.schema/schema.ex
+++ b/priv/templates/phx.gen.schema/schema.ex
@@ -5,7 +5,7 @@ defmodule <%= inspect schema.module %> do
   @primary_key {:id, :binary_id, autogenerate: true}
   @foreign_key_type :binary_id<% end %>
   schema <%= inspect schema.table %> do
-<%= for {k, v} <- schema.types do %>    field <%= inspect k %>, <%= inspect v %><%= schema.defaults[k] %>
+<%= for {k, v} <- schema.types do %>    field <%= inspect k %>, <%= Mix.Phoenix.Schema.type_and_opts_for_schema(v) %><%= schema.defaults[k] %>
 <% end %><%= for {_, k, _, _} <- schema.assocs do %>    field <%= inspect k %>, <%= if schema.binary_id do %>:binary_id<% else %>:id<% end %>
 <% end %>
     timestamps()


Test patch:
diff --git a/integration_test/mix.exs b/integration_test/mix.exs
index 6a9152739..655a14e5c 100644
--- a/integration_test/mix.exs
+++ b/integration_test/mix.exs
@@ -29,7 +29,7 @@ defmodule Phoenix.Integration.MixProject do
       {:phx_new, path: "../installer"},
       {:phoenix, path: "../", override: true},
       {:phoenix_ecto, "~> 4.1"},
-      {:ecto_sql, "~> 3.4.4"},
+      {:ecto_sql, "~> 3.5"},
       {:postgrex, ">= 0.0.0"},
       {:myxql, ">= 0.0.0"},
       {:tds, ">= 0.0.0"},
diff --git a/integration_test/mix.lock b/integration_test/mix.lock
index 5672ecc97..f16d3d857 100644
--- a/integration_test/mix.lock
+++ b/integration_test/mix.lock
@@ -5,10 +5,10 @@
   "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
   "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
   "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
-  "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
+  "db_connection": {:hex, :db_connection, "2.3.0", "d56ef906956a37959bcb385704fc04035f4f43c0f560dd23e00740daf8028c49", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "dcc082b8f723de9a630451b49fdbd7a59b065c4b38176fb147aaf773574d4520"},
   "decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"},
-  "ecto": {:hex, :ecto, "3.4.6", "08f7afad3257d6eb8613309af31037e16c36808dfda5a3cd0cb4e9738db030e4", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6f13a9e2a62e75c2dcfc7207bfc65645ab387af8360db4c89fee8b5a4bf3f70b"},
-  "ecto_sql": {:hex, :ecto_sql, "3.4.5", "30161f81b167d561a9a2df4329c10ae05ff36eca7ccc84628f2c8b9fa1e43323", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31990c6a3579b36a3c0841d34a94c275e727de8b84f58509da5f1b2032c98ac2"},
+  "ecto": {:hex, :ecto, "3.5.4", "73ee115deb10769c73fd2d27e19e36bc4af7c56711ad063616a86aec44f80f6f", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7f13f9c9c071bd2ca04652373ff3edd1d686364de573255096872a4abc471807"},
+  "ecto_sql": {:hex, :ecto_sql, "3.5.3", "1964df0305538364b97cc4661a2bd2b6c89d803e66e5655e4e55ff1571943efd", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2f53592432ce17d3978feb8f43e8dc0705e288b0890caf06d449785f018061c"},
   "elixir_make": {:hex, :elixir_make, "0.6.1", "8faa29a5597faba999aeeb72bbb9c91694ef8068f0131192fb199f98d32994ef", [:mix], [], "hexpm", "35d33270680f8d839a4003c3e9f43afb595310a592405a00afc12de4c7f55a18"},
   "file_system": {:hex, :file_system, "0.2.9", "545b9c9d502e8bfa71a5315fac2a923bd060fd9acb797fe6595f54b0f975fd32", [:mix], [], "hexpm", "3cf87a377fe1d93043adeec4889feacf594957226b4f19d5897096d6f61345d8"},
   "floki": {:hex, :floki, "0.29.0", "b1710d8c93a2f860dc2d7adc390dd808dc2fb8f78ee562304457b75f4c640881", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "008585ce64b9f74c07d32958ec9866f4b8a124bf4da1e2941b28e41384edaaad"},
@@ -16,7 +16,7 @@
   "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
   "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
   "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"},
-  "myxql": {:hex, :myxql, "0.4.3", "9ad738fcfdf91a807b968f8994ccd4701786e572a3ce3a939c8e0f9001e38486", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.3", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "744e4c314d9c9bcc55066c20997f11f91c7e458dc94990ab9ed45caf94ee01ed"},
+  "myxql": {:hex, :myxql, "0.4.4", "b60707bc4ea73db8d6591c5e8016251b35fdc15bc2bc5a51f9212c160f97b9e9", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:geo, "~> 3.3", [hex: :geo, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "028c44611efc193e809fa711d16b2ff34921fae9ff97c0d5b8eeaa9a3701e919"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
   "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"},
   "phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
@@ -27,7 +27,7 @@
   "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"},
   "plug_cowboy": {:hex, :plug_cowboy, "2.3.0", "149a50e05cb73c12aad6506a371cd75750c0b19a32f81866e1a323dda9e0e99d", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bc595a1870cef13f9c1e03df56d96804db7f702175e4ccacdb8fc75c02a7b97e"},
   "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"},
-  "postgrex": {:hex, :postgrex, "0.15.6", "a464c72010a56e3214fe2b99c1a76faab4c2bb0255cabdef30dea763a3569aa2", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "f99268325ac8f66ffd6c4964faab9e70fbf721234ab2ad238c00f9530b8cdd55"},
+  "postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"},
   "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
   "tds": {:hex, :tds, "2.1.1", "b6163ea716d74ed90a3a83668db2e7c74c1e722fd3538ef5758e0a084fde8d60", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "6b28e1f06a57867eb6b1a957ae5d872b09214ba771ef08cf5ca9d52d6d372876"},
   "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
diff --git a/integration_test/test/code_generation/app_with_defaults_test.exs b/integration_test/test/code_generation/app_with_defaults_test.exs
index d32c6aa14..5a997f4ee 100644
--- a/integration_test/test/code_generation/app_with_defaults_test.exs
+++ b/integration_test/test/code_generation/app_with_defaults_test.exs
@@ -27,7 +27,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithDefaultsTest do
       with_installer_tmp("app_with_defaults", fn tmp_dir ->
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "phx_blog")
 
-        mix_run!(~w(phx.gen.html Blog Post posts title:unique body:string), app_root_path)
+        mix_run!(~w(phx.gen.html Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/phx_blog_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -50,7 +50,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithDefaultsTest do
       with_installer_tmp("app_with_defaults", fn tmp_dir ->
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "phx_blog")
 
-        mix_run!(~w(phx.gen.html Blog Post posts title body:string), app_root_path)
+        mix_run!(~w(phx.gen.html Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/phx_blog_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -74,7 +74,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithDefaultsTest do
       with_installer_tmp("app_with_defaults", fn tmp_dir ->
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "phx_blog")
 
-        mix_run!(~w(phx.gen.json Blog Post posts title:unique body:string), app_root_path)
+        mix_run!(~w(phx.gen.json Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), app_root_path)
 
         assert_no_compilation_warnings(app_root_path)
         assert_passes_formatter_check(app_root_path)
@@ -86,7 +86,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithDefaultsTest do
       with_installer_tmp("app_with_defaults", fn tmp_dir ->
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "phx_blog")
 
-        mix_run!(~w(phx.gen.json Blog Post posts title body:string), app_root_path)
+        mix_run!(~w(phx.gen.json Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/phx_blog_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -110,7 +110,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithDefaultsTest do
       with_installer_tmp("app_with_defaults", fn tmp_dir ->
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "phx_blog", ["--live"])
 
-        mix_run!(~w(phx.gen.live Blog Post posts title:unique body:string public:boolean), app_root_path)
+        mix_run!(~w(phx.gen.live Blog Post posts title:unique body:string p:boolean s:enum:a:b:c), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/phx_blog_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -138,7 +138,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithDefaultsTest do
       with_installer_tmp("app_with_defaults", fn tmp_dir ->
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "phx_blog", ["--live"])
 
-        mix_run!(~w(phx.gen.live Blog Post posts title body:string public:boolean), app_root_path)
+        mix_run!(~w(phx.gen.live Blog Post posts title body:string public:boolean status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/phx_blog_web/router.ex"), fn file ->
           inject_before_final_end(file, """
diff --git a/integration_test/test/code_generation/app_with_mssql_adapter_test.exs b/integration_test/test/code_generation/app_with_mssql_adapter_test.exs
index cc2875f24..7c65cfda2 100644
--- a/integration_test/test/code_generation/app_with_mssql_adapter_test.exs
+++ b/integration_test/test/code_generation/app_with_mssql_adapter_test.exs
@@ -8,7 +8,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithMSSQLAdapterTest do
         {app_root_path, _} =
           generate_phoenix_app(tmp_dir, "default_mssql_app", ["--database", "mssql"])
 
-        mix_run!(~w(phx.gen.html Blog Post posts title body:string), app_root_path)
+        mix_run!(~w(phx.gen.html Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/default_mssql_app_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -34,7 +34,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithMSSQLAdapterTest do
         {app_root_path, _} =
           generate_phoenix_app(tmp_dir, "default_mssql_app", ["--database", "mssql"])
 
-        mix_run!(~w(phx.gen.json Blog Post posts title body:string), app_root_path)
+        mix_run!(~w(phx.gen.json Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/default_mssql_app_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -60,7 +60,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithMSSQLAdapterTest do
         {app_root_path, _} =
           generate_phoenix_app(tmp_dir, "default_mssql_app", ["--database", "mssql", "--live"])
 
-        mix_run!(~w(phx.gen.live Blog Post posts title body:string), app_root_path)
+        mix_run!(~w(phx.gen.live Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/default_mssql_app_web/router.ex"), fn file ->
           inject_before_final_end(file, """
diff --git a/integration_test/test/code_generation/app_with_mysql_adapter_test.exs b/integration_test/test/code_generation/app_with_mysql_adapter_test.exs
index dc8518cb1..03835ff08 100644
--- a/integration_test/test/code_generation/app_with_mysql_adapter_test.exs
+++ b/integration_test/test/code_generation/app_with_mysql_adapter_test.exs
@@ -8,7 +8,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithMySqlAdapterTest do
         {app_root_path, _} =
           generate_phoenix_app(tmp_dir, "default_mysql_app", ["--database", "mysql"])
 
-        mix_run!(~w(phx.gen.html Blog Post posts title body:string), app_root_path)
+        mix_run!(~w(phx.gen.html Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/default_mysql_app_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -34,7 +34,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithMySqlAdapterTest do
         {app_root_path, _} =
           generate_phoenix_app(tmp_dir, "default_mysql_app", ["--database", "mysql"])
 
-        mix_run!(~w(phx.gen.json Blog Post posts title body:string), app_root_path)
+        mix_run!(~w(phx.gen.json Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/default_mysql_app_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -60,7 +60,7 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithMySqlAdapterTest do
         {app_root_path, _} =
           generate_phoenix_app(tmp_dir, "default_mysql_app", ["--database", "mysql", "--live"])
 
-        mix_run!(~w(phx.gen.live Blog Post posts title body:string), app_root_path)
+        mix_run!(~w(phx.gen.live Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)
 
         modify_file(Path.join(app_root_path, "lib/default_mysql_app_web/router.ex"), fn file ->
           inject_before_final_end(file, """
diff --git a/integration_test/test/code_generation/umbrella_app_with_defaults_test.exs b/integration_test/test/code_generation/umbrella_app_with_defaults_test.exs
index 9b68b7478..250212c04 100644
--- a/integration_test/test/code_generation/umbrella_app_with_defaults_test.exs
+++ b/integration_test/test/code_generation/umbrella_app_with_defaults_test.exs
@@ -29,7 +29,7 @@ defmodule Phoenix.Integration.CodeGeneration.UmbrellaAppWithDefaultsTest do
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "rainy_day", ["--umbrella"])
         web_root_path = Path.join(app_root_path, "apps/rainy_day_web")
 
-        mix_run!(~w(phx.gen.html Blog Post posts title:unique body:string), web_root_path)
+        mix_run!(~w(phx.gen.html Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), web_root_path)
 
         modify_file(Path.join(web_root_path, "lib/rainy_day_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -53,7 +53,7 @@ defmodule Phoenix.Integration.CodeGeneration.UmbrellaAppWithDefaultsTest do
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "rainy_day", ["--umbrella"])
         web_root_path = Path.join(app_root_path, "apps/rainy_day_web")
 
-        mix_run!(~w(phx.gen.html Blog Post posts title body:string), web_root_path)
+        mix_run!(~w(phx.gen.html Blog Post posts title body:string status:enum:unpublished:published:deleted), web_root_path)
 
         modify_file(Path.join(web_root_path, "lib/rainy_day_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -78,7 +78,7 @@ defmodule Phoenix.Integration.CodeGeneration.UmbrellaAppWithDefaultsTest do
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "rainy_day", ["--umbrella"])
         web_root_path = Path.join(app_root_path, "apps/rainy_day_web")
 
-        mix_run!(~w(phx.gen.json Blog Post posts title:unique body:string), web_root_path)
+        mix_run!(~w(phx.gen.json Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), web_root_path)
 
         assert_no_compilation_warnings(app_root_path)
         assert_passes_formatter_check(app_root_path)
@@ -91,7 +91,7 @@ defmodule Phoenix.Integration.CodeGeneration.UmbrellaAppWithDefaultsTest do
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "rainy_day", ["--umbrella"])
         web_root_path = Path.join(app_root_path, "apps/rainy_day_web")
 
-        mix_run!(~w(phx.gen.json Blog Post posts title body:string), web_root_path)
+        mix_run!(~w(phx.gen.json Blog Post posts title body:string status:enum:unpublished:published:deleted), web_root_path)
 
         modify_file(Path.join(web_root_path, "lib/rainy_day_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -116,7 +116,7 @@ defmodule Phoenix.Integration.CodeGeneration.UmbrellaAppWithDefaultsTest do
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "rainy_day", ["--umbrella", "--live"])
         web_root_path = Path.join(app_root_path, "apps/rainy_day_web")
 
-        mix_run!(~w(phx.gen.live Blog Post posts title:unique body:string), web_root_path)
+        mix_run!(~w(phx.gen.live Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), web_root_path)
 
         modify_file(Path.join(web_root_path, "lib/rainy_day_web/router.ex"), fn file ->
           inject_before_final_end(file, """
@@ -145,7 +145,7 @@ defmodule Phoenix.Integration.CodeGeneration.UmbrellaAppWithDefaultsTest do
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "rainy_day", ["--umbrella", "--live"])
         web_root_path = Path.join(app_root_path, "apps/rainy_day_web")
 
-        mix_run!(~w(phx.gen.live Blog Post posts title body:string), web_root_path)
+        mix_run!(~w(phx.gen.live Blog Post posts title body:string status:enum:unpublished:published:deleted), web_root_path)
 
         modify_file(Path.join(web_root_path, "lib/rainy_day_web/router.ex"), fn file ->
           inject_before_final_end(file, """
diff --git a/test/mix/tasks/phx.gen.html_test.exs b/test/mix/tasks/phx.gen.html_test.exs
index e67ccdc30..1ad0e6c7b 100644
--- a/test/mix/tasks/phx.gen.html_test.exs
+++ b/test/mix/tasks/phx.gen.html_test.exs
@@ -31,17 +31,26 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
       assert_raise Mix.Error, ~r/Invalid arguments/, fn ->
         Gen.Html.run(~w(Blog Post))
       end
+
+      assert_raise Mix.Error, ~r/Enum type requires at least two values/, fn ->
+        Gen.Html.run(~w(Blog Post posts status:enum))
+      end
+
+      assert_raise Mix.Error, ~r/Enum type requires at least two values/, fn ->
+        Gen.Html.run(~w(Blog Post posts status:enum:new))
+      end
     end
   end
 
   test "generates html resource and handles existing contexts", config do
     one_day_in_seconds = 24 * 3600
-    naive_datetime = %{NaiveDateTime.utc_now() | second: 0, microsecond: {0, 6}} 
+    naive_datetime = %{NaiveDateTime.utc_now() | second: 0, microsecond: {0, 6}}
     datetime = %{DateTime.utc_now() | second: 0, microsecond: {0, 6}}
 
     in_tmp_project config.test, fn ->
       Gen.Html.run(~w(Blog Post posts title slug:unique votes:integer cost:decimal
                       tags:array:text popular:boolean drafted_at:datetime
+                      status:enum:unpublished:published:deleted
                       published_at:utc_datetime
                       published_at_usec:utc_datetime_usec
                       deleted_at:naive_datetime
@@ -63,6 +72,7 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
         assert file =~ "published_at: #{datetime |> DateTime.add(-one_day_in_seconds) |> DateTime.truncate(:second) |> inspect()}"
         assert file =~ "published_at_usec: #{datetime |> DateTime.add(-one_day_in_seconds) |> inspect()}"
         assert file =~ "weight: 120.5"
+        assert file =~ "status: :published"
 
         assert file =~ "assert post.announcement_date == #{inspect(Date.utc_today())}"
         assert file =~ "assert post.deleted_at == #{naive_datetime |> NaiveDateTime.truncate(:second) |> inspect()}"
@@ -73,6 +83,7 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
         assert file =~ "assert post.alarm_usec == ~T[15:01:01.000000]"
         assert file =~ "assert post.cost == Decimal.new(\"120.5\")"
         assert file =~ "assert post.weight == 120.5"
+        assert file =~ "assert post.status == :published"
       end
 
       assert_file "test/phoenix_web/controllers/post_controller_test.exs", fn file ->
@@ -84,6 +95,7 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
       assert_file path, fn file ->
         assert file =~ "create table(:posts)"
         assert file =~ "add :title, :string"
+        assert file =~ "add :status, :string"
         assert file =~ "create unique_index(:posts, [:slug])"
       end
 
@@ -131,6 +143,7 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
         assert file =~ ~s(<%= date_select f, :announcement_date %>)
         assert file =~ ~s(<%= time_select f, :alarm %>)
         assert file =~ ~s(<%= text_input f, :secret %>)
+        assert file =~ ~s|<%= select f, :status, Ecto.Enum.values(Phoenix.Blog.Post, :status), prompt: "Choose a value" %>|
 
         assert file =~ ~s(<%= label f, :title %>)
         assert file =~ ~s(<%= label f, :votes %>)
@@ -143,6 +156,7 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
         assert file =~ ~s(<%= label f, :announcement_date %>)
         assert file =~ ~s(<%= label f, :alarm %>)
         assert file =~ ~s(<%= label f, :secret %>)
+        assert file =~ ~s(<%= label f, :status %>)
 
         refute file =~ ~s(<%= label f, :user_id)
         refute file =~ ~s(<%= number_input f, :user_id)
diff --git a/test/mix/tasks/phx.gen.live_test.exs b/test/mix/tasks/phx.gen.live_test.exs
index 07e363bbd..aebf40db3 100644
--- a/test/mix/tasks/phx.gen.live_test.exs
+++ b/test/mix/tasks/phx.gen.live_test.exs
@@ -57,6 +57,7 @@ defmodule Mix.Tasks.Phx.Gen.LiveTest do
     in_tmp_live_project config.test, fn ->
       Gen.Live.run(~w(Blog Post posts title slug:unique votes:integer cost:decimal
                       tags:array:text popular:boolean drafted_at:datetime
+                      status:enum:unpublished:published:deleted
                       published_at:utc_datetime
                       published_at_usec:utc_datetime_usec
                       deleted_at:naive_datetime
@@ -108,6 +109,7 @@ defmodule Mix.Tasks.Phx.Gen.LiveTest do
         assert file =~ ~s(<%= multiple_select f, :tags, ["Option 1": "option1", "Option 2": "option2"] %>)
         assert file =~ ~s(<%= checkbox f, :popular %>)
         assert file =~ ~s(<%= datetime_select f, :drafted_at %>)
+        assert file =~ ~s|<%= select f, :status, Ecto.Enum.values(Phoenix.Blog.Post, :status), prompt: "Choose a value" %>|
         assert file =~ ~s(<%= datetime_select f, :published_at %>)
         assert file =~ ~s(<%= datetime_select f, :deleted_at %>)
         assert file =~ ~s(<%= date_select f, :announcement_date %>)
diff --git a/test/mix/tasks/phx.gen.schema_test.exs b/test/mix/tasks/phx.gen.schema_test.exs
index a4cfc18f3..c7bd21a72 100644
--- a/test/mix/tasks/phx.gen.schema_test.exs
+++ b/test/mix/tasks/phx.gen.schema_test.exs
@@ -223,6 +223,22 @@ defmodule Mix.Tasks.Phx.Gen.SchemaTest do
     end
   end
 
+  test "generates schema with enum", config do
+    in_tmp_project config.test, fn ->
+      Gen.Schema.run(~w(Blog.Comment comments title:string status:enum:unpublished:published:deleted))
+
+      assert_file "lib/phoenix/blog/comment.ex", fn file ->
+        assert file =~ "field :status, Ecto.Enum, values: [:unpublished, :published, :deleted]"
+      end
+
+      assert [path] = Path.wildcard("priv/repo/migrations/*_create_comments.exs")
+      assert_file path, fn file ->
+        assert file =~ "create table(:comments)"
+        assert file =~ "add :status, :string"
+      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))
@@ -274,11 +290,11 @@ defmodule Mix.Tasks.Phx.Gen.SchemaTest do
     in_tmp_project config.test, fn ->
       try do
         Application.put_env(:ecto_sql, :migration_module, MyCustomApp.MigrationModule)
-  
-        Gen.Schema.run(~w(Blog.Post posts))        
-  
+
+        Gen.Schema.run(~w(Blog.Post posts))
+
         assert [migration] = Path.wildcard("priv/repo/migrations/*_create_posts.exs")
-  
+
         assert_file migration, fn file ->
           assert file =~ "use MyCustomApp.MigrationModule"
           assert file =~ "create table(:posts) do"