Repository: phoenixframework/phoenix
Pull request #3814 (2020-04-30 12:24:46)

Some suggested updates for clarity/context
👋 @mcrumm asked me to take a look through the new Telemetry docs and PR any suggestions. 

Looks great to me overall, but I added a few suggestions. These suggestions are mostly targeted at clarifying the relationship between `Telemetry.Metrics`, Telemetry reporters and the `:telemetry` library. How these components fit together to achieve instrumentation of your application can be a tough concept to grok in my opinion, but having that understanding will empower users to get the most out of Phoenix's new Telemetry offerings.

Maybe some of the suggestions here represent more detail than ya'll feel is appropriate for these docs, but I'd thought I'd put them forward and see what you think. 


Patch:
diff --git a/.travis.yml b/.travis.yml
index 23d389b71..3e07b4555 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,16 +1,12 @@
 language: elixir
 elixir:
-  - 1.9
+  - 1.10
 otp_release:
   - 22.0
 matrix:
   include:
-    - elixir: 1.7
-      otp_release: 21.0
-    - elixir: 1.8
-      otp_release: 21.0
     - elixir: 1.9
-      otp_release: 22.0
+      otp_release: 21.0
 sudo: false
 env:
   global:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbb509f9a..b67e3d920 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,15 @@
 # Changelog for v1.6
 
+Phoenix v1.6 requires Elixir v1.9+.
+
 ## 1.6.0-dev
 
 ### Enhancements
 
+### Bug fixes
+
+  * [mix phx.gen.live] Fix a bug where tests with `utc_datetime` fields did not pass out of the box
+
 ### Deprecations
 
   * [Endpoint] Phoenix now requires Cowboy v2.7+
diff --git a/assets/js/phoenix.js b/assets/js/phoenix.js
index 2ca1db3c1..0c2e579ad 100644
--- a/assets/js/phoenix.js
+++ b/assets/js/phoenix.js
@@ -955,7 +955,7 @@ export class Socket {
   }
 
   waitForBufferDone(callback, tries = 1) {
-    if (tries === 5 || !this.conn || (this.conn.bufferedAmount && this.conn.bufferedAmount === 0)) {
+    if (tries === 5 || !this.conn || !this.conn.bufferedAmount) {
       callback()
       return
     }
diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md
index 5121bbfb8..9eeba9634 100644
--- a/guides/deployment/heroku.md
+++ b/guides/deployment/heroku.md
@@ -97,11 +97,11 @@ The buildpack uses a predefined Elixir and Erlang version but to avoid surprises
 
 ```
 # Elixir version
-elixir_version=1.8.1
+elixir_version=1.10.3
 
 # Erlang version
 # available versions https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions
-erlang_version=21.2.5
+erlang_version=22.2.8
 ```
 
 ### Adding the Phoenix Server and Assets Buildpack
diff --git a/guides/directory_structure.md b/guides/directory_structure.md
index 4599e739c..cf34fe4a6 100644
--- a/guides/directory_structure.md
+++ b/guides/directory_structure.md
@@ -15,7 +15,7 @@ When we use `mix phx.new` to generates a new Phoenix application, it builds a to
 │   └── hello_web
 │   └── hello_web.ex
 ├── priv
-├── test
+└── test
 ```
 
 We will go over those directories one by one:
@@ -41,7 +41,7 @@ The `lib/hello` directory hosts all of your business domain. Since our project d
 ```console
 lib/hello
 ├── application.ex
-├── repo.ex
+└── repo.ex
 ```
 
 The `lib/hello/application.ex` file defines an Elixir application named `Hello.Application`. That's because at the end of the day Phoenix applications are simply Elixir applications. The `Hello.Application` module defines which services are part of our application:
@@ -50,6 +50,8 @@ The `lib/hello/application.ex` file defines an Elixir application named `Hello.A
 children = [
   # Start the Ecto repository
   Hello.Repo,
+  # Start the Telemetry supervisor
+  HelloWeb.Telemetry,
   # Start the PubSub system
   {Phoenix.PubSub, name: Hello.PubSub},
   # Start the Endpoint (http/https)
@@ -90,7 +92,7 @@ lib/hello_web
 │   │   └── app.html.eex
 │   └── page
 │       └── index.html.eex
-└── views
+├── views
 │   ├── error_helpers.ex
 │   ├── error_view.ex
 │   ├── layout_view.ex
@@ -98,6 +100,7 @@ lib/hello_web
 ├── endpoint.ex
 ├── gettext.ex
 ├── router.ex
+└── telemetry.ex
 ```
 
 All of the files which are currently in the `controllers`, `templates`, and `views` directories are there to create the "Welcome to Phoenix!" page we saw in the "Up and running" guide. 
@@ -105,6 +108,8 @@ The `channels` directory is where we will add code related to building real-time
 
 By looking at `templates` and `views` directories, we can see Phoenix provides features for handling layouts and error pages out of the box.
 
-Besides the directories mentioned, `lib/hello_web` has three files at its root. `lib/hello_web/endpoint.ex` is the entry-point for HTTP requests. Once the browser accesses `http://localhost:4000`, the endpoint starts processing the data, eventually leading to the router, which is defined in `lib/hello_web/router.ex`. The router defines the rules to dispatch requests to "controllers", which then uses "views" and "templates" to render HTML pages back to clients. We explore these layers in length in other guides, starting with the "Request life-cycle" guide coming next.
+Besides the directories mentioned, `lib/hello_web` has four files at its root. `lib/hello_web/endpoint.ex` is the entry-point for HTTP requests. Once the browser accesses `http://localhost:4000`, the endpoint starts processing the data, eventually leading to the router, which is defined in `lib/hello_web/router.ex`. The router defines the rules to dispatch requests to "controllers", which then uses "views" and "templates" to render HTML pages back to clients. We explore these layers in length in other guides, starting with the "Request life-cycle" guide coming next.
+
+Through _telemetry_, Phoenix is able to collect metrics and send monitoring events of your application. The `lib/hello_web/telemetry.ex` file defines the supervisor responsible for managing the telemetry processes. You can find more information on this topic in the [Telemetry guide](telemetry.html).
 
 Finally, there is a `lib/hello_web/gettext.ex` file which provides internationalization through [Gettext](https://hexdocs.pm/gettext/Gettext.html). If you are not worried about internationalization, you can safely skip this file and its contents.
diff --git a/guides/introduction/community.md b/guides/introduction/community.md
index 0757cc0a3..7c01a2698 100644
--- a/guides/introduction/community.md
+++ b/guides/introduction/community.md
@@ -32,6 +32,8 @@ There are a number of places to connect with community members at all experience
 
 ## Screencasts/Courses
 
+  * [Phoenix LiveView Free Course (The Pragmatic Studio - May 2020)](https://pragmaticstudio.com/courses/phoenix-liveview)
+
   * [The Complete Elixir and Phoenix Bootcamp Master Functional Programming Techniques with Elixir and Phoenix while Learning to Build Compelling Web Applications (Udemy - Jun 2017)](https://www.udemy.com/the-complete-elixir-and-phoenix-bootcamp-and-tutorial/)
 
   * [Discover Elixir & Phoenix (Mar 2017)](https://www.ludu.co/course/discover-elixir-phoenix)
diff --git a/guides/plug.md b/guides/plug.md
index 091b668bc..d044fc59f 100644
--- a/guides/plug.md
+++ b/guides/plug.md
@@ -38,7 +38,7 @@ Pretty simple, right? Let's see this function in action by adding it to our endp
 defmodule HelloWeb.Endpoint do
   ...
 
-  plug :instrospect
+  plug :introspect
   plug HelloWeb.Router
 
   def introspect(conn, _opts) do
@@ -134,7 +134,7 @@ Endpoints organize all the plugs common to every request, and apply them before
 defmodule HelloWeb.Endpoint do
   ...
 
-  plug :instrospect
+  plug :introspect
   plug HelloWeb.Router
 ```
 
diff --git a/guides/routing.md b/guides/routing.md
index 2db728c84..b418489a9 100644
--- a/guides/routing.md
+++ b/guides/routing.md
@@ -291,7 +291,7 @@ scope "/admin", HelloWeb.Admin do
 end
 ```
 
-We define a new scope where all routes a prefixed with "/admin" and all controllers are under the `HelloWeb.Admin` namespace.
+We define a new scope where all routes are prefixed with "/admin" and all controllers are under the `HelloWeb.Admin` namespace.
 
 Running `mix phx.routes` again, in addition to the previous set of routes we get the following:
 
diff --git a/guides/telemetry.md b/guides/telemetry.md
index 4386868d1..1c11b9236 100644
--- a/guides/telemetry.md
+++ b/guides/telemetry.md
@@ -13,6 +13,14 @@ and we will guide you through the steps to properly
 instrument your own functions using `:telemetry`. Let's take
 a closer look at how Telemetry works in your application.
 
+## Overview
+
+The `[:telemetry]` library allows you to emit events at various stages of an application's lifecycle. You can then respond to these events by, among other things, aggregating them as metrics and sending the metrics data to a reporting destination.
+
+Telemetry stores events by their name in an ETS table, along with the handler for each event. Then, when a given event is executed, Telemetry looks up its handler and invokes it.
+
+Phoenix's Telemetry tooling provides you with a supervisor that uses `Telemetry.Metrics` to define the list of Telemetry events to handle and how to handle those events, i.e. how to structure them as a certain type of metric. This supervisor works together with Telemetry reporters to respond to the specified Telemetry events by aggregating them as the appropriate metric and sending them to the correct reporting destination.
+
 ## The Telemetry supervisor
 
 Since v1.5, new Phoenix applications are generated with a
@@ -103,7 +111,7 @@ children = [
 ## Telemetry Events
 
 Many Elixir libraries (including Phoenix) are already using
-the `:telemetry` package (http://hexdocs.pm/telemetry) as a
+the [`:telemetry`](http://hexdocs.pm/telemetry) package as a
 way to give users more insight into the behavior of their
 applications, by emitting events at key moments in the
 application lifecycle.
@@ -124,7 +132,7 @@ A Telemetry event is made up of the following:
 Here is an example of an event from your endpoint:
 
 * `[:phoenix, :endpoint, :stop]` - dispatched by
-  `Plug.Telemetry` in your endpoint whenever the response is
+  `Plug.Telemetry`, one of the default plugs in your endpoint, whenever the response is
   sent
 
   * Measurement: `%{duration: native_time}`
@@ -148,11 +156,15 @@ took to get the response:
 > &#x2015; `Telemetry.Metrics`
 
 The Telemetry.Metrics package provides a common interface
-for defining metrics. What this means is that the package
-does not perform any aggregation of the measurements itself,
-but instead relies on reporters to do so. We will discuss
+for defining metrics. It exposes a set of [five metric type functions](https://hexdocs.pm/telemetry_metrics/Telemetry.Metrics.html#module-metrics) that are responsible for structuring a given Telemetry event as a particular measurement.
+
+The package does not perform any aggregation of the measurements itself. Instead, it provides a reporter with the Telemetry event-as-measurement definition and the reporter uses that definition to perform aggregations and report them.
+
+We will discuss
 reporters in the next section.
 
+Let's take a look at some examples.
+
 Using `Telemetry.Metrics`, you can define a counter metric,
 which counts how many HTTP requests were completed:
 
@@ -182,11 +194,30 @@ documentation.
 
 ### An Ecto Example
 
-Like Phoenix, Ecto ships with built-in telemetry events.
+Like Phoenix, Ecto ships with built-in Telemetry events.
 This means that you can gain introspection into your web
 and database layers using the same tools.
 
-For instance, you might want to graph query execution time:
+Here is an example of a Telemetry event executed by Ecto when an Ecto repository starts:
+
+* `[:ecto, :repo, :init]` - dispatched by
+  `Ecto.Repo.Supervisor`
+
+  * Measurement: `%{system_time: native_time}`
+
+  * Metadata: `%{repo: Ecto.Repo, opts: Keyword.t()}`
+
+This means that whenever the `Ecto.Repo.Supervisor` starts, it will emit an event, via `:telemetry`,
+with a measurement of the time at start-up.
+
+```elixir
+:telemetry.execute([:ecto, :repo, :init], %{system_time: System.system_time()}, %{repo: repo, opts: opts})
+```
+
+Additional Telemetry events are executed by Ecto adapters.
+
+One such adapter-specific event is the `[:my_app, :repo, :query]` event.
+For instance, if you want to graph query execution time, you can use the `Telemetry.Metrics.summary/2` function to instruct your reporter to calculate statistics of the `[:my_app, :repo, :query]` event, like maximum, mean, percentiles etc.:
 
 ```elixir
 Telemetry.Metrics.summary("my_app.repo.query.query_time",
@@ -194,7 +225,7 @@ Telemetry.Metrics.summary("my_app.repo.query.query_time",
 )
 ```
 
-or view how long queries spend queued:
+Or you could use the `Telemetry.Metrics.distribution/2` function to define a histogram for another adapter-specific event: `[:my_app, :repo, :query, :queue_time]`, thus visualizing how long queries spend queued:
 
 ```elixir
 Telemetry.Metrics.distribution("my_app.repo.query.queue_time",
@@ -220,6 +251,16 @@ interface provided by `Telemetry.Metrics`. They then
 aggregate the measurements (data) into metrics to provide
 meaningful information about your application.
 
+For example, if the following `Telemetry.Metrics.summary/2` call is added to the `metrics/0` function of your Telemetry supervisor:
+
+```elixir
+summary("phoenix.endpoint.stop.duration",
+  unit: {:native, :millisecond}
+)
+```
+
+Then the reporter will attach a listener for the `"phoenix.endpoint.stop.duration"` event and will respond to this event by calculating a summary metric with the given event metadata and reporting on that metric to the appropriate source.
+
 ### Phoenix.LiveDashboard
 
 For developers interested in real-time visualizations for
diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex
index 4ccdde4e4..2fe18e3e1 100644
--- a/installer/lib/mix/tasks/phx.new.ex
+++ b/installer/lib/mix/tasks/phx.new.ex
@@ -147,8 +147,8 @@ defmodule Mix.Tasks.Phx.New do
     project
   end
 
-  defp prompt_to_install_deps(%Project{} = project, generator, path) do
-    path = Map.fetch!(project, path)
+  defp prompt_to_install_deps(%Project{} = project, generator, path_key) do
+    path = Map.fetch!(project, path_key)
     install? = Mix.shell().yes?("\nFetch and install dependencies?")
     cd_step = ["$ cd #{relative_app_path(path)}"]
 
@@ -174,6 +174,17 @@ defmodule Mix.Tasks.Phx.New do
         print_ecto_info(generator)
       end
 
+      if path_key == :web_path do
+        Mix.shell().info("""
+        Your web app requires a PubSub server to be running.
+        The PubSub server is typically defined in a `mix phx.gen.ecto` app.
+        If you don't plan to define an Ecto app, you must explicitly start
+        the PubSub in your supervision tree as:
+
+            {Phoenix.PubSub, name: #{inspect(project.app_mod)}.PubSub}}
+        """)
+      end
+
       print_mix_info(generator)
     end)
   end
@@ -339,8 +350,8 @@ defmodule Mix.Tasks.Phx.New do
   end
 
   defp elixir_version_check! do
-    unless Version.match?(System.version(), "~> 1.7") do
-      Mix.raise "Phoenix v#{@version} requires at least Elixir v1.7.\n " <>
+    unless Version.match?(System.version(), "~> 1.9") do
+      Mix.raise "Phoenix v#{@version} requires at least Elixir v1.9.\n " <>
                 "You have #{System.version()}. Please update accordingly"
     end
   end
diff --git a/installer/mix.exs b/installer/mix.exs
index e42ce54f4..5061a7b02 100644
--- a/installer/mix.exs
+++ b/installer/mix.exs
@@ -10,7 +10,7 @@ defmodule Phx.New.MixProject do
       app: :phx_new,
       start_permanent: Mix.env() == :prod,
       version: @version,
-      elixir: "~> 1.7",
+      elixir: "~> 1.9",
       deps: deps(),
       package: [
         maintainers: [
diff --git a/installer/templates/phx_assets/app.js b/installer/templates/phx_assets/app.js
index 172261322..7467d54e3 100644
--- a/installer/templates/phx_assets/app.js
+++ b/installer/templates/phx_assets/app.js
@@ -17,16 +17,8 @@ import {Socket} from "phoenix"
 import NProgress from "nprogress"
 import {LiveSocket} from "phoenix_live_view"
 
-// The _csrf_token is necessary to load the current session into the LiveView.
-// The _cache_static_manifest_hash is used to detect whenever there is a new
-// deploy and trigger a reload of the assets. The "PHOENIX_CACHE_STATIC_MANIFEST_HASH"
-// will be automatically replaced by a hash by running `mix phx.digest` in prod.
-let params = {
-  _csrf_token: document.querySelector("meta[name='csrf-token']").getAttribute("content"),
-  _cache_static_manifest_hash: "PHOENIX_CACHE_STATIC_MANIFEST_HASH"
-}
-
-let liveSocket = new LiveSocket("/live", Socket, {params: params})
+let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
+let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
 
 // Show progress bar on live navigation and form submits
 window.addEventListener("phx:page-loading-start", info => NProgress.start())
diff --git a/installer/templates/phx_live/templates/layout/root.html.leex b/installer/templates/phx_live/templates/layout/root.html.leex
index adad0063e..ebef2187d 100644
--- a/installer/templates/phx_live/templates/layout/root.html.leex
+++ b/installer/templates/phx_live/templates/layout/root.html.leex
@@ -6,8 +6,8 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
     <%%= csrf_meta_tag() %>
     <%%= live_title_tag assigns[:page_title] || "<%= app_module %>", suffix: " · Phoenix Framework" %>
-    <link rel="stylesheet" href="<%%= Routes.static_path(@conn, "/css/app.css") %>"/>
-    <script defer type="text/javascript" src="<%%= Routes.static_path(@conn, "/js/app.js") %>"></script>
+    <link phx-track-static rel="stylesheet" href="<%%= Routes.static_path(@conn, "/css/app.css") %>"/>
+    <script defer phx-track-static type="text/javascript" src="<%%= Routes.static_path(@conn, "/js/app.js") %>"></script>
   </head>
   <body>
     <header>
diff --git a/installer/templates/phx_single/config/prod.secret.exs b/installer/templates/phx_single/config/prod.secret.exs
index 915a58906..854695a78 100644
--- a/installer/templates/phx_single/config/prod.secret.exs
+++ b/installer/templates/phx_single/config/prod.secret.exs
@@ -18,7 +18,7 @@ config :<%= app_name %>, <%= endpoint_module %>,
   ],
   secret_key_base: secret_key_base
 
-# ## Using releases (Elixir v1.9+)
+# ## Using releases
 #
 # If you are doing OTP releases, you need to instruct Phoenix
 # to start each relevant endpoint:
diff --git a/installer/templates/phx_single/mix.exs b/installer/templates/phx_single/mix.exs
index 7bc051dbe..e0d59020a 100644
--- a/installer/templates/phx_single/mix.exs
+++ b/installer/templates/phx_single/mix.exs
@@ -9,7 +9,7 @@ defmodule <%= app_module %>.MixProject do
       config_path: "../../config/config.exs",
       deps_path: "../../deps",
       lockfile: "../../mix.lock",<% end %>
-      elixir: "~> 1.7",
+      elixir: "~> 1.9",
       elixirc_paths: elixirc_paths(Mix.env()),
       compilers: [:phoenix<%= if gettext do %>, :gettext<% end %>] ++ Mix.compilers(),
       start_permanent: Mix.env() == :prod,
diff --git a/installer/templates/phx_umbrella/apps/app_name/mix.exs b/installer/templates/phx_umbrella/apps/app_name/mix.exs
index 3957bd1c8..dcdf89b83 100644
--- a/installer/templates/phx_umbrella/apps/app_name/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name/mix.exs
@@ -9,7 +9,7 @@ defmodule <%= app_module %>.MixProject do
       config_path: "../../config/config.exs",
       deps_path: "../../deps",
       lockfile: "../../mix.lock",
-      elixir: "~> 1.7",
+      elixir: "~> 1.9",
       elixirc_paths: elixirc_paths(Mix.env()),
       start_permanent: Mix.env() == :prod,
       aliases: aliases(),
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs
index 68f85d16e..83844c037 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs
@@ -12,7 +12,7 @@ config :<%= web_app_name %>, <%= endpoint_module %>,
   ],
   secret_key_base: secret_key_base
 
-# ## Using releases (Elixir v1.9+)
+# ## Using releases
 #
 # If you are doing OTP releases, you need to instruct Phoenix
 # to start each relevant endpoint:
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
index 369eb66f6..09666cdd0 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/mix.exs
@@ -9,7 +9,7 @@ defmodule <%= web_namespace %>.MixProject do
       config_path: "../../config/config.exs",
       deps_path: "../../deps",
       lockfile: "../../mix.lock",
-      elixir: "~> 1.7",
+      elixir: "~> 1.9",
       elixirc_paths: elixirc_paths(Mix.env()),
       compilers: [:phoenix<%= if gettext do %>, :gettext<% end %>] ++ Mix.compilers(),
       start_permanent: Mix.env() == :prod,
diff --git a/installer/templates/phx_web/telemetry.ex b/installer/templates/phx_web/telemetry.ex
index ec20cd3bf..ab776d989 100644
--- a/installer/templates/phx_web/telemetry.ex
+++ b/installer/templates/phx_web/telemetry.ex
@@ -9,6 +9,8 @@ defmodule <%= web_namespace %>.Telemetry do
   @impl true
   def init(_arg) do
     children = [
+      # Telemetry poller will execute the given period measurements
+      # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
       {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
       # Add reporters as children of your supervision tree.
       # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
diff --git a/lib/mix/phoenix/schema.ex b/lib/mix/phoenix/schema.ex
index d05544e89..85c0dd60b 100644
--- a/lib/mix/phoenix/schema.ex
+++ b/lib/mix/phoenix/schema.ex
@@ -6,6 +6,7 @@ defmodule Mix.Phoenix.Schema do
   defstruct module: nil,
             repo: nil,
             table: nil,
+            collection: nil,
             embedded?: false,
             generate?: true,
             opts: [],
@@ -83,6 +84,8 @@ defmodule Mix.Phoenix.Schema do
       |> Module.split()
       |> List.last()
       |> Phoenix.Naming.underscore()
+
+    collection = if schema_plural == singular, do: singular <> "_collection", else: schema_plural
     string_attr = string_attr(types)
     create_params = params(attrs, :create)
     default_params_key =
@@ -103,6 +106,7 @@ defmodule Mix.Phoenix.Schema do
       attrs: attrs,
       plural: schema_plural,
       singular: singular,
+      collection: collection,
       assocs: assocs,
       types: types,
       defaults: schema_defaults(attrs),
@@ -180,8 +184,6 @@ defmodule Mix.Phoenix.Schema do
     |> inspect_value(value)
   end
   defp inspect_value(:decimal, value), do: "Decimal.new(\"#{value}\")"
-  defp inspect_value(:utc_datetime, value), do: "DateTime.from_naive!(~N[#{value}], \"Etc/UTC\")"
-  defp inspect_value(:utc_datetime_usec, value), do: "DateTime.from_naive!(~N[#{value}], \"Etc/UTC\")"
   defp inspect_value(_type, value), do: inspect(value)
 
   defp drop_unique(info) do
@@ -207,12 +209,12 @@ defmodule Mix.Phoenix.Schema do
         :boolean        -> true
         :map            -> %{}
         :text           -> "some #{key}"
-        :date           -> %Date{year: 2010, month: 4, day: 17}
-        :time           -> %Time{hour: 14, minute: 0, second: 0}
-        :time_usec      -> %Time{hour: 14, minute: 0, second: 0, microsecond: {0, 6}}
+        :date           -> ~D[2010-04-17]
+        :time           -> ~T[14:00:00]
+        :time_usec      -> ~T[14:00:00.000000]
         :uuid           -> "7488a646-e31f-11e4-aace-600308960662"
-        :utc_datetime   -> "2010-04-17T14:00:00Z"
-        :utc_datetime_usec -> "2010-04-17T14:00:00.000000Z"
+        :utc_datetime   -> ~U[2010-04-17T14:00:00Z]
+        :utc_datetime_usec -> ~U[2010-04-17T14:00:00.000000Z]
         :naive_datetime -> ~N[2010-04-17 14:00:00]
         :naive_datetime_usec -> ~N[2010-04-17 14:00:00.000000]
         _               -> "some #{key}"
@@ -227,12 +229,12 @@ defmodule Mix.Phoenix.Schema do
         :boolean        -> false
         :map            -> %{}
         :text           -> "some updated #{key}"
-        :date           -> %Date{year: 2011, month: 5, day: 18}
-        :time           -> %Time{hour: 15, minute: 1, second: 1}
-        :time_usec      -> %Time{hour: 15, minute: 1, second: 1, microsecond: {0, 6}}
+        :date           -> ~D[2011-05-18]
+        :time           -> ~T[15:01:01]
+        :time_usec      -> ~T[15:01:01.000000]
         :uuid           -> "7488a646-e31f-11e4-aace-600308960668"
-        :utc_datetime   -> "2011-05-18T15:01:01Z"
-        :utc_datetime_usec   -> "2011-05-18T15:01:01.000000Z"
+        :utc_datetime   -> ~U[2011-05-18T15:01:01Z]
+        :utc_datetime_usec   -> ~U[2011-05-18T15:01:01.000000Z]
         :naive_datetime -> ~N[2011-05-18 15:01:01]
         :naive_datetime_usec -> ~N[2011-05-18 15:01:01.000000]
         _               -> "some updated #{key}"
diff --git a/lib/phoenix/channel.ex b/lib/phoenix/channel.ex
index bb9df984e..f99ca0498 100644
--- a/lib/phoenix/channel.ex
+++ b/lib/phoenix/channel.ex
@@ -339,7 +339,7 @@ defmodule Phoenix.Channel do
   @doc """
   Handle regular Elixir process messages.
 
-  See `GenServer.handle_info/2`.
+  See `c:GenServer.handle_info/2`.
   """
   @callback handle_info(msg :: term, socket :: Socket.t()) ::
               {:noreply, Socket.t()}
@@ -348,7 +348,7 @@ defmodule Phoenix.Channel do
   @doc """
   Handle regular GenServer call messages.
 
-  See `GenServer.handle_call/3`.
+  See `c:GenServer.handle_call/3`.
   """
   @callback handle_call(msg :: term, from :: {pid, tag :: term}, socket :: Socket.t()) ::
               {:reply, response :: term, Socket.t()}
@@ -358,7 +358,7 @@ defmodule Phoenix.Channel do
   @doc """
   Handle regular GenServer cast messages.
 
-  See `GenServer.handle_cast/2`.
+  See `c:GenServer.handle_cast/2`.
   """
   @callback handle_cast(msg :: term, socket :: Socket.t()) ::
               {:noreply, Socket.t()}
@@ -373,7 +373,7 @@ defmodule Phoenix.Channel do
   @doc """
   Invoked when the channel process is about to exit.
 
-  See `GenServer.terminate/2`.
+  See `c:GenServer.terminate/2`.
   """
   @callback terminate(
               reason :: :normal | :shutdown | {:shutdown, :left | :closed | term},
@@ -402,7 +402,7 @@ defmodule Phoenix.Channel do
       @phoenix_shutdown Keyword.get(opts, :shutdown, 5000)
 
       import unquote(__MODULE__)
-      import Phoenix.Socket, only: [assign: 3]
+      import Phoenix.Socket, only: [assign: 3, assign: 2]
 
       def child_spec(init_arg) do
         %{
diff --git a/lib/phoenix/channel/server.ex b/lib/phoenix/channel/server.ex
index cd402e234..a608f4b68 100644
--- a/lib/phoenix/channel/server.ex
+++ b/lib/phoenix/channel/server.ex
@@ -22,10 +22,11 @@ defmodule Phoenix.Channel.Server do
 
     ref = make_ref()
     from = {self(), ref}
-    child_spec = channel.child_spec({payload, from, socket})
+    child_spec = channel.child_spec({socket.endpoint, from})
 
     case PoolSupervisor.start_child(socket.endpoint, socket.handler, from, child_spec) do
       {:ok, pid} ->
+        send(pid, {Phoenix.Channel, payload, from, socket})
         mon_ref = Process.monitor(pid)
 
         receive do
@@ -254,10 +255,8 @@ defmodule Phoenix.Channel.Server do
   ## Callbacks
 
   @doc false
-  def init({auth_payload, from, socket}) do
-    # TODO: Use handle_continue when we support Erlang/OTP 21+.
-    send(self(), {:join, __MODULE__})
-    {:ok, {auth_payload, from, socket}}
+  def init({_endpoint, {pid, _}}) do
+    {:ok, Process.monitor(pid)}
   end
 
   @doc false
@@ -285,7 +284,8 @@ defmodule Phoenix.Channel.Server do
   end
 
   @doc false
-  def handle_info({:join, __MODULE__}, {auth_payload, {pid, _} = from, socket}) do
+  def handle_info({Phoenix.Channel, auth_payload, {pid, _} = from, socket}, ref) do
+    Process.demonitor(ref)
     %{channel: channel, topic: topic, private: private} = socket
     Process.put(:"$callers", [pid])
 
@@ -329,6 +329,10 @@ defmodule Phoenix.Channel.Server do
     |> handle_result(:handle_out)
   end
 
+  def handle_info({:DOWN, ref, _, _, reason}, ref) do
+    {:stop, reason, ref}
+  end
+
   def handle_info({:DOWN, _, _, transport_pid, reason}, %{transport_pid: transport_pid} = socket) do
     reason = if reason == :normal, do: {:shutdown, :closed}, else: reason
     {:stop, reason, socket}
diff --git a/lib/phoenix/controller/pipeline.ex b/lib/phoenix/controller/pipeline.ex
index 9d9b5ffbc..7e4161cfb 100644
--- a/lib/phoenix/controller/pipeline.ex
+++ b/lib/phoenix/controller/pipeline.ex
@@ -19,15 +19,12 @@ defmodule Phoenix.Controller.Pipeline do
 
       @doc false
       def call(conn, action) when is_atom(action) do
-        conn =
-          update_in(
-            conn.private,
-            &(&1
-              |> Map.put(:phoenix_controller, __MODULE__)
-              |> Map.put(:phoenix_action, action))
-          )
-
-        phoenix_controller_pipeline(conn, action)
+        conn
+        |> merge_private(
+          phoenix_controller: __MODULE__,
+          phoenix_action: action
+        )
+        |> phoenix_controller_pipeline(action)
       end
 
       @doc false
diff --git a/lib/phoenix/digester.ex b/lib/phoenix/digester.ex
index a7ec27507..2c90453aa 100644
--- a/lib/phoenix/digester.ex
+++ b/lib/phoenix/digester.ex
@@ -24,12 +24,10 @@ defmodule Phoenix.Digester do
 
       files = filter_files(input_path)
       latest = generate_latest(files)
-      hash = latest_hash(latest)
-
       digests = load_compile_digests(output_path)
-      digested_files = Enum.map(files, &digested_contents(&1, latest, hash))
+      digested_files = Enum.map(files, &digested_contents(&1, latest))
 
-      save_manifest(digested_files, latest, hash, digests, output_path)
+      save_manifest(digested_files, latest, digests, output_path)
       Enum.each(digested_files, &write_to_disk(&1, output_path))
     else
       {:error, :invalid_path}
@@ -75,35 +73,27 @@ defmodule Phoenix.Digester do
   defp migrate_manifest(%{"version" => @manifest_version} = manifest, _output_path), do: manifest
   defp migrate_manifest(_latest, _output_path), do: @empty_manifest
 
-  defp save_manifest(files, latest, hash, old_digests, output_path) do
+  defp save_manifest(files, latest, old_digests, output_path) do
     old_digests_that_still_exist =
       old_digests
       |> Enum.filter(fn {file, _} -> File.exists?(Path.join(output_path, file)) end)
       |> Map.new()
 
     digests = Map.merge(old_digests_that_still_exist, generate_digests(files))
-    write_manifest(latest, digests, hash, output_path)
+    write_manifest(latest, digests, output_path)
   end
 
-  defp write_manifest(latest, digests, hash, output_path) do
+  defp write_manifest(latest, digests, output_path) do
     json = %{
       "latest" => latest,
       "version" => @manifest_version,
-      "digests" => digests,
-      "hash" => hash
+      "digests" => digests
     }
 
     manifest_content = Phoenix.json_library().encode!(json)
     File.write!(Path.join(output_path, "cache_manifest.json"), manifest_content)
   end
 
-  defp latest_hash(latest) do
-    [@manifest_version, latest]
-    |> :erlang.term_to_binary()
-    |> :erlang.md5()
-    |> Base.url_encode64(padding: false)
-  end
-
   defp generate_digests(files) do
     Map.new(
       files,
@@ -182,15 +172,9 @@ defmodule Phoenix.Digester do
     Path.extname(file.filename) in Application.fetch_env!(:phoenix, :gzippable_exts)
   end
 
-  defp digested_contents(file, latest, hash) do
+  defp digested_contents(file, latest) do
     ext = Path.extname(file.filename)
 
-    file =
-      case ext do
-        ".js" -> %{file | content: digest_javascript_contents(file, hash)}
-        _ -> file
-      end
-
     digested_content =
       case ext do
         ".css" -> digest_stylesheet_asset_references(file, latest)
@@ -202,14 +186,6 @@ defmodule Phoenix.Digester do
     %{file | digested_content: digested_content}
   end
 
-  @javascript_cache_manifest_hash_regex ~r{["'](PHOENIX_CACHE_STATIC_MANIFEST_HASH)["']}
-
-  defp digest_javascript_contents(file, hash) do
-    Regex.replace(@javascript_cache_manifest_hash_regex, file.content, fn _, _ ->
-      "\"#{hash}\""
-    end)
-  end
-
   @stylesheet_url_regex ~r{(url\(\s*)(\S+?)(\s*\))}
   @quoted_text_regex ~r{\A(['"])(.+)\1\z}
 
@@ -305,7 +281,7 @@ defmodule Phoenix.Digester do
       %{"latest" => latest, "digests" => digests} = load_manifest(path)
       files = files_to_clean(latest, digests, now - age, keep)
       remove_files(files, path)
-      write_manifest(latest, Map.drop(digests, files), latest_hash(latest), path)
+      write_manifest(latest, Map.drop(digests, files), path)
       :ok
     else
       {:error, :invalid_path}
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index 5565af5ba..b1a79bae5 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -102,10 +102,11 @@ defmodule Phoenix.Endpoint do
       by `mix phx.digest`. It can be either: a string containing a file system path
       or a tuple containing the application name and the path within that application.
 
-    * `:cache_static_manifest_hash` - the hash of the cache static manifest.
-      This is automatically loaded from `cache_static_manifest` on boot.
-      However, if you have your own static handling mechanism, you may want to
-      set this value explicitly.
+    * `:cache_static_manifest_latest` - a map of the static files pointing to their
+      digest version. This is automatically loaded from `cache_static_manifest` on
+      boot. However, if you have your own static handling mechanism, you may want to
+      set this value explicitly. This is used by projects such as `LiveView` to
+      detect if the client is running on the latest version of all assets
 
     * `:check_origin` - configure the default `:check_origin` setting for
       transports. See `socket/3` for options. Defaults to `true`.
@@ -201,7 +202,7 @@ defmodule Phoenix.Endpoint do
 
     * `:drainer` - a drainer process that triggers when your application is
       shutting to wait for any on-going request to finish. It accepts all
-      options as defined by [`Plug.Cowboy`](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.Drainer.httml).
+      options as defined by [`Plug.Cowboy`](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.Drainer.html).
       Defaults to `[]` and can be disabled by setting it to false.
 
   ## Endpoint API
@@ -378,12 +379,10 @@ defmodule Phoenix.Endpoint do
 
   defp pubsub() do
     quote do
-      @deprecated "#{inspect(__MODULE__)}.subscribe/2 is deprecated, please call Phoenix.PubSub directly instead"
       def subscribe(topic, opts \\ []) when is_binary(topic) do
-        Phoenix.PubSub.subscribe(pubsub_server!(), topic, [])
+        Phoenix.PubSub.subscribe(pubsub_server!(), topic, opts)
       end
 
-      @deprecated "#{inspect(__MODULE__)}.unsubscribe/1 is deprecated, please call Phoenix.PubSub directly instead"
       def unsubscribe(topic) do
         Phoenix.PubSub.unsubscribe(pubsub_server!(), topic)
       end
@@ -768,8 +767,13 @@ defmodule Phoenix.Endpoint do
       The valid keys are:
 
         * `:peer_data` - the result of `Plug.Conn.get_peer_data/1`
+
         * `:x_headers` - all request headers that have an "x-" prefix
+
         * `:uri` - a `%URI{}` with information from the conn
+
+        * `:user_agent` - the value of the "user-agent" request header
+
         * `{:session, session_config}` - the session information from `Plug.Conn`.
           The `session_config` is an exact copy of the arguments given to `Plug.Session`.
           This requires the "_csrf_token" to be given as request parameter with
diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex
index acc59b2c2..f3a255c2f 100644
--- a/lib/phoenix/endpoint/supervisor.ex
+++ b/lib/phoenix/endpoint/supervisor.ex
@@ -384,8 +384,8 @@ defmodule Phoenix.Endpoint.Supervisor do
     endpoint.static_path("/")
   end
 
-  defp warmup_static(endpoint, %{"latest" => latest, "digests" => digests} = manifest) do
-    Phoenix.Config.put_new(endpoint, :cache_static_manifest_hash, manifest["hash"])
+  defp warmup_static(endpoint, %{"latest" => latest, "digests" => digests}) do
+    Phoenix.Config.put_new(endpoint, :cache_static_manifest_latest, latest)
 
     Enum.each(latest, fn {key, _} ->
       Phoenix.Config.cache(endpoint, {:__phoenix_static__, "/" <> key}, fn _ ->
diff --git a/lib/phoenix/logger.ex b/lib/phoenix/logger.ex
index 2d859d805..3460a55b1 100644
--- a/lib/phoenix/logger.ex
+++ b/lib/phoenix/logger.ex
@@ -69,7 +69,7 @@ defmodule Phoenix.Logger do
 
     * `[:phoenix, :channel_joined]` - dispatched at the end of a channel join
       * Measurement: `%{duration: native_time}`
-      * Metadata: `%{params: term, socket: Phoenix.Socket.t}`
+      * Metadata: `%{result: :ok | :error, params: term, socket: Phoenix.Socket.t}`
       * Disable logging: This event cannot be disabled
 
     * `[:phoenix, :channel_handled_in]` - dispatched at the end of a channel handle in
diff --git a/lib/phoenix/presence.ex b/lib/phoenix/presence.ex
index bac30014a..a5cf27dab 100644
--- a/lib/phoenix/presence.ex
+++ b/lib/phoenix/presence.ex
@@ -286,12 +286,6 @@ defmodule Phoenix.Presence do
         }
       end
 
-      # TODO: Remove this on the next Phoenix version as we require v1.6
-      # and this will only be called by outdated child specs.
-      def start_link(opts \\ []) do
-        Phoenix.Presence.start_link(__MODULE__, Keyword.merge(@opts, opts))
-      end
-
       # API
 
       def track(%Phoenix.Socket{} = socket, key, meta) do
diff --git a/lib/phoenix/router.ex b/lib/phoenix/router.ex
index 727dedd0c..ddd72b00b 100644
--- a/lib/phoenix/router.ex
+++ b/lib/phoenix/router.ex
@@ -450,10 +450,13 @@ defmodule Phoenix.Router do
       def __helpers__, do: __MODULE__.Helpers
 
       defp prepare(conn) do
-        update_in conn.private,
-          &(&1
-            |> Map.put(:phoenix_router, __MODULE__)
-            |> Map.put(__MODULE__, {conn.script_name, @phoenix_forwards}))
+        merge_private(
+          conn,
+          [
+            {:phoenix_router, __MODULE__},
+            {__MODULE__, {conn.script_name, @phoenix_forwards}}
+          ]
+        )
       end
 
       unquote(matches)
diff --git a/lib/phoenix/socket.ex b/lib/phoenix/socket.ex
index a5fff3d6a..74a0be896 100644
--- a/lib/phoenix/socket.ex
+++ b/lib/phoenix/socket.ex
@@ -129,9 +129,14 @@ defmodule Phoenix.Socket do
   ## Custom channels
 
   You can list any module as a channel as long as it implements
-  a `start_link/1` function that receives a tuple with three elements:
+  a `child_spec/1` function. The `child_spec/1` function receives
+  the caller as argument and it must return a child spec that
+  initializes a process.
 
-      {auth_payload, from, socket}
+  Once the process is initialized, it will receive the following
+  message:
+
+      {Phoenix.Channel, auth_payload, from, socket}
 
   A custom channel implementation MUST invoke
   `GenServer.reply(from, {:ok | :error, reply_payload})` during its
@@ -174,11 +179,6 @@ defmodule Phoenix.Socket do
   Custom channel implementations cannot be tested with `Phoenix.ChannelTest`
   and are currently considered experimental. The underlying API may be
   changed at any moment.
-
-  **Note:** in future Phoenix versions we will require custom channels
-  to provide a custom `child_spec/1` function instead of `start_link/1`.
-  Since the default behaviour of `child_spec/1` is to invoke `start_link/1`,
-  this behaviour should be backwards compatible in almost all cases.
   """
 
   require Logger
diff --git a/lib/phoenix/socket/transport.ex b/lib/phoenix/socket/transport.ex
index 14b835258..45a63e2f6 100644
--- a/lib/phoenix/socket/transport.ex
+++ b/lib/phoenix/socket/transport.ex
@@ -248,7 +248,7 @@ defmodule Phoenix.Socket.Transport do
 
     connect_info =
       Enum.map(connect_info, fn
-        key when key in [:peer_data, :uri, :x_headers] ->
+        key when key in [:peer_data, :uri, :user_agent, :x_headers] ->
           key
 
         {:session, session} ->
@@ -431,9 +431,13 @@ defmodule Phoenix.Socket.Transport do
   The supported keys are:
 
     * `:peer_data` - the result of `Plug.Conn.get_peer_data/1`
+
     * `:x_headers` - a list of all request headers that have an "x-" prefix
+
     * `:uri` - a `%URI{}` derived from the conn
 
+    * `:user_agent` - the value of the "user-agent" request header
+
   """
   def connect_info(conn, endpoint, keys) do
     for key <- keys, into: %{} do
@@ -447,6 +451,9 @@ defmodule Phoenix.Socket.Transport do
         :uri ->
           {:uri, fetch_uri(conn)}
 
+        :user_agent ->
+          {:user_agent, fetch_user_agent(conn)}
+
         {:session, session} ->
           {:session, connect_session(conn, endpoint, session)}
 
@@ -519,17 +526,23 @@ defmodule Phoenix.Socket.Transport do
         do: pair
   end
 
-  defp fetch_uri(%{host: host, scheme: scheme, query_string: query_string, port: port, request_path: request_path}) do
+  defp fetch_uri(conn) do
     %URI{
-      scheme: to_string(scheme),
-      query: query_string,
-      port: port,
-      host: host,
-      authority: host,
-      path: request_path,
+      scheme: to_string(conn.scheme),
+      query: conn.query_string,
+      port: conn.port,
+      host: conn.host,
+      authority: conn.host,
+      path: conn.request_path
     }
   end
 
+  defp fetch_user_agent(conn) do
+    with {_, value} <- List.keyfind(conn.req_headers, "user-agent", 0) do
+      value
+    end
+  end
+
   defp check_origin_config(handler, endpoint, opts) do
     Phoenix.Config.cache(endpoint, {:check_origin, handler}, fn _ ->
       check_origin =
diff --git a/mix.exs b/mix.exs
index 9cffaee76..aa779c889 100644
--- a/mix.exs
+++ b/mix.exs
@@ -6,7 +6,7 @@ defmodule Phoenix.MixProject do
   # If the elixir requirement is updated, we need to make the installer
   # use at least the minimum requirement used here. Although often the
   # installer is ahead of Phoenix itself.
-  @elixir_requirement "~> 1.7"
+  @elixir_requirement "~> 1.9"
 
   def project do
     [
diff --git a/priv/templates/phx.gen.json/fallback_controller.ex b/priv/templates/phx.gen.json/fallback_controller.ex
index a4b88bf7a..6ae62aa42 100644
--- a/priv/templates/phx.gen.json/fallback_controller.ex
+++ b/priv/templates/phx.gen.json/fallback_controller.ex
@@ -6,14 +6,16 @@ defmodule <%= inspect context.web_module %>.FallbackController do
   """
   use <%= inspect context.web_module %>, :controller
 
-  <%= if schema.generate? do %>def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
+  <%= if schema.generate? do %># This clause handles errors returned by Ecto's insert/update/delete.
+  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
     conn
     |> put_status(:unprocessable_entity)
     |> put_view(<%= inspect context.web_module %>.ChangesetView)
     |> render("error.json", changeset: changeset)
   end
 
-  <% end %>def call(conn, {:error, :not_found}) do
+  <% end %># This clause is an example of how to handle resources that cannot be found.
+  def call(conn, {:error, :not_found}) do
     conn
     |> put_status(:not_found)
     |> put_view(<%= inspect context.web_module %>.ErrorView)
diff --git a/priv/templates/phx.gen.live/index.ex b/priv/templates/phx.gen.live/index.ex
index d59915c7e..f46c53b77 100644
--- a/priv/templates/phx.gen.live/index.ex
+++ b/priv/templates/phx.gen.live/index.ex
@@ -6,7 +6,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
 
   @impl true
   def mount(_params, _session, socket) do
-    {:ok, assign(socket, :<%= schema.plural %>, fetch_<%= schema.plural %>())}
+    {:ok, assign(socket, :<%= schema.collection %>, list_<%= schema.plural %>())}
   end
 
   @impl true
@@ -37,10 +37,10 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
     <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(id)
     {:ok, _} = <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= schema.singular %>)
 
-    {:noreply, assign(socket, :<%= schema.plural %>, fetch_<%=schema.plural %>())}
+    {:noreply, assign(socket, :<%= schema.collection %>, list_<%=schema.plural %>())}
   end
 
-  defp fetch_<%= schema.plural %> do
+  defp list_<%= schema.plural %> do
     <%= inspect context.alias %>.list_<%= schema.plural %>()
   end
 end
diff --git a/priv/templates/phx.gen.live/index.html.leex b/priv/templates/phx.gen.live/index.html.leex
index 3e9672f92..1ea9f0ab7 100644
--- a/priv/templates/phx.gen.live/index.html.leex
+++ b/priv/templates/phx.gen.live/index.html.leex
@@ -18,7 +18,7 @@
     </tr>
   </thead>
   <tbody id="<%= schema.plural %>">
-    <%%= for <%= schema.singular %> <- @<%= schema.plural %> do %>
+    <%%= for <%= schema.singular %> <- @<%= schema.collection %> do %>
       <tr id="<%= schema.singular %>-<%%= <%= schema.singular %>.id %>">
 <%= for {k, _} <- schema.attrs do %>        <td><%%= <%= schema.singular %>.<%= k %> %></td>
 <% end %>


Test patch:
diff --git a/guides/testing/testing_controllers.md b/guides/testing/testing_controllers.md
index 965093f87..72a7b6a9d 100644
--- a/guides/testing/testing_controllers.md
+++ b/guides/testing/testing_controllers.md
@@ -47,11 +47,11 @@ def index(conn, _params) do
 end
 ```
 
-It gets all posts and renders the "index.html" template. The template can be found in "lib/hello_web/templates/page/index.html.eex".
+It gets all posts and renders the "index.html" template. The template can be found in `lib/hello_web/templates/page/index.html.eex`.
 
 The test looks like this:
 
-```elixi
+```elixir
 describe "index" do
   test "lists all posts", %{conn: conn} do
     conn = get(conn, Routes.post_path(conn, :index))
diff --git a/installer/test/phx_new_web_test.exs b/installer/test/phx_new_web_test.exs
index 4b828f89a..e9c5bffb1 100644
--- a/installer/test/phx_new_web_test.exs
+++ b/installer/test/phx_new_web_test.exs
@@ -58,6 +58,8 @@ defmodule Mix.Tasks.Phx.New.WebTest do
       assert msg =~ "$ cd phx_web"
       assert msg =~ "$ mix deps.get"
 
+      assert_received {:mix_shell, :info, ["Your web app requires a PubSub server to be running." <> _]}
+
       assert_received {:mix_shell, :info, ["Start your Phoenix app" <> _]}
     end
   end
diff --git a/lib/phoenix/test/channel_test.ex b/lib/phoenix/test/channel_test.ex
index 45a6fe444..1b47ab804 100644
--- a/lib/phoenix/test/channel_test.ex
+++ b/lib/phoenix/test/channel_test.ex
@@ -404,7 +404,7 @@ defmodule Phoenix.ChannelTest do
 
   """
   @spec push(Socket.t, String.t, map()) :: reference()
-  def push(socket, event, payload \\ %{}) do
+  def push(%Socket{} = socket, event, payload \\ %{}) do
     ref = make_ref()
     send(socket.channel_pid,
          %Message{event: event, topic: socket.topic, ref: ref, payload: __stringify__(payload)})
@@ -415,7 +415,7 @@ defmodule Phoenix.ChannelTest do
   Emulates the client leaving the channel.
   """
   @spec leave(Socket.t) :: reference()
-  def leave(socket) do
+  def leave(%Socket{} = socket) do
     push(socket, "phx_leave", %{})
   end
 
@@ -425,7 +425,7 @@ defmodule Phoenix.ChannelTest do
   Closing socket is synchronous and has a default timeout
   of 5000 milliseconds.
   """
-  def close(socket, timeout \\ 5000) do
+  def close(%Socket{} = socket, timeout \\ 5000) do
     Server.close(socket.channel_pid, timeout)
   end
 
@@ -441,7 +441,7 @@ defmodule Phoenix.ChannelTest do
       :ok
 
   """
-  def broadcast_from(socket, event, message) do
+  def broadcast_from(%Socket{} = socket, event, message) do
     %{pubsub_server: pubsub_server, topic: topic, transport_pid: transport_pid} = socket
     Server.broadcast_from pubsub_server, transport_pid, topic, event, message
   end
@@ -449,7 +449,7 @@ defmodule Phoenix.ChannelTest do
   @doc """
   Same as `broadcast_from/3`, but raises if broadcast fails.
   """
-  def broadcast_from!(socket, event, message) do
+  def broadcast_from!(%Socket{} = socket, event, message) do
     %{pubsub_server: pubsub_server, topic: topic, transport_pid: transport_pid} = socket
     Server.broadcast_from! pubsub_server, transport_pid, topic, event, message
   end
diff --git a/priv/templates/phx.gen.live/live_test.exs b/priv/templates/phx.gen.live/live_test.exs
index 8cc5f6b78..9fccb0c48 100644
--- a/priv/templates/phx.gen.live/live_test.exs
+++ b/priv/templates/phx.gen.live/live_test.exs
@@ -33,7 +33,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       {:ok, index_live, _html} = live(conn, Routes.<%= schema.route_helper %>_index_path(conn, :index))
 
       assert index_live |> element("a", "New <%= schema.human_singular %>") |> render_click() =~
-        "New <%= schema.human_singular %>"
+               "New <%= schema.human_singular %>"
 
       assert_patch(index_live, Routes.<%= schema.route_helper %>_index_path(conn, :new))
 
@@ -55,7 +55,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       {:ok, index_live, _html} = live(conn, Routes.<%= schema.route_helper %>_index_path(conn, :index))
 
       assert index_live |> element("#<%= schema.singular %>-#{<%= schema.singular %>.id} a", "Edit") |> render_click() =~
-        "Edit <%= schema.human_singular %>"
+               "Edit <%= schema.human_singular %>"
 
       assert_patch(index_live, Routes.<%= schema.route_helper %>_index_path(conn, :edit, <%= schema.singular %>))
 
@@ -95,7 +95,7 @@ defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web
       {:ok, show_live, _html} = live(conn, Routes.<%= schema.route_helper %>_show_path(conn, :show, <%= schema.singular %>))
 
       assert show_live |> element("a", "Edit") |> render_click() =~
-        "Edit <%= schema.human_singular %>"
+               "Edit <%= schema.human_singular %>"
 
       assert_patch(show_live, Routes.<%= schema.route_helper %>_show_path(conn, :edit, <%= schema.singular %>))
 
diff --git a/test/fixtures/digest/cleaner/cache_manifest.json b/test/fixtures/digest/cleaner/cache_manifest.json
index 68155e0cb..3c3c7b7b6 100644
--- a/test/fixtures/digest/cleaner/cache_manifest.json
+++ b/test/fixtures/digest/cleaner/cache_manifest.json
@@ -22,6 +22,5 @@
   "latest": {
     "app.css": "app-3.css"
   },
-  "version": 1,
-  "hash": "AxLYL9wnimiMdf3_NDnYvw"
+  "version": 1
 }
diff --git a/test/fixtures/digest/cleaner/latest_not_most_recent_cache_manifest.json b/test/fixtures/digest/cleaner/latest_not_most_recent_cache_manifest.json
index 60e7c08d9..78ace07d6 100644
--- a/test/fixtures/digest/cleaner/latest_not_most_recent_cache_manifest.json
+++ b/test/fixtures/digest/cleaner/latest_not_most_recent_cache_manifest.json
@@ -22,6 +22,5 @@
   "latest": {
     "app.css": "app-3.css"
   },
-  "version": 1,
-  "hash": "AxLYL9wnimiMdf3_NDnYvw"
+  "version": 1
 }
diff --git a/test/fixtures/digest/compile/cache_manifest.json b/test/fixtures/digest/compile/cache_manifest.json
index 140d1f487..dc286f9c6 100644
--- a/test/fixtures/digest/compile/cache_manifest.json
+++ b/test/fixtures/digest/compile/cache_manifest.json
@@ -10,6 +10,5 @@
   "latest": {
     "foo.css": "foo-d978852bea6530fcd197b5445ed008fd.css"
   },
-  "version": 1,
-  "hash": "cjkuB6uDZecddGSFz7D7kg"
+  "version": 1
 }
diff --git a/test/fixtures/digest/compile/cache_manifest_upgrade.json b/test/fixtures/digest/compile/cache_manifest_upgrade.json
index 0a32d8728..23bce0f2f 100644
--- a/test/fixtures/digest/compile/cache_manifest_upgrade.json
+++ b/test/fixtures/digest/compile/cache_manifest_upgrade.json
@@ -16,6 +16,5 @@
   "latest": {
     "foo.css": "foo-ghijkl.css"
   },
-  "version": 1,
-  "hash": "3xAzpSnmcgte3bMevWqJMA"
+  "version": 1
 }
diff --git a/test/fixtures/digest/priv/static/app.js b/test/fixtures/digest/priv/static/app.js
index 0fa8d1342..172e3a596 100644
--- a/test/fixtures/digest/priv/static/app.js
+++ b/test/fixtures/digest/priv/static/app.js
@@ -1,5 +1,4 @@
 (function() {
-  var digest = "PHOENIX_CACHE_STATIC_MANIFEST_HASH";
   console.log('Hello World!');
 })();
 //# sourceMappingURL=app.js.map
diff --git a/test/mix/phoenix_test.exs b/test/mix/phoenix_test.exs
index de746a676..3c22e66a1 100644
--- a/test/mix/phoenix_test.exs
+++ b/test/mix/phoenix_test.exs
@@ -76,12 +76,12 @@ defmodule Mix.PhoenixTest do
       admin: true,
       meta: %{},
       name: "some name",
-      date_of_birth: %Date{year: 2010, month: 4, day: 17},
-      happy_hour: %Time{hour: 14, minute: 0, second: 0},
-      happy_hour_usec: %Time{hour: 14, minute: 0, second: 0, microsecond: {0, 6}},
-      joined: %NaiveDateTime{year: 2010, month: 4, day: 17, hour: 14, minute: 0, second: 0},
-      joined_utc: "2010-04-17T14:00:00Z",
-      joined_utc_usec: "2010-04-17T14:00:00.000000Z",
+      date_of_birth: ~D[2010-04-17],
+      happy_hour: ~T[14:00:00],
+      happy_hour_usec: ~T[14:00:00.000000],
+      joined: ~N[2010-04-17 14:00:00],
+      joined_utc: ~U[2010-04-17 14:00:00Z],
+      joined_utc_usec: ~U[2010-04-17 14:00:00.000000Z],
       token: "7488a646-e31f-11e4-aace-600308960662",
       other: "some other"
     }
diff --git a/test/mix/tasks/phx.gen.html_test.exs b/test/mix/tasks/phx.gen.html_test.exs
index 803dc7ca0..e6127115f 100644
--- a/test/mix/tasks/phx.gen.html_test.exs
+++ b/test/mix/tasks/phx.gen.html_test.exs
@@ -56,15 +56,15 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
         assert file =~ "deleted_at: ~N[2010-04-17 14:00:00]"
         assert file =~ "deleted_at_usec: ~N[2010-04-17 14:00:00.000000]"
         assert file =~ "cost: \"120.5\""
-        assert file =~ "published_at: \"2010-04-17T14:00:00Z\""
-        assert file =~ "published_at_usec: \"2010-04-17T14:00:00.000000Z\""
+        assert file =~ "published_at: ~U[2010-04-17 14:00:00Z]"
+        assert file =~ "published_at_usec: ~U[2010-04-17 14:00:00.000000Z]"
         assert file =~ "weight: 120.5"
 
         assert file =~ "assert post.announcement_date == ~D[2011-05-18]"
         assert file =~ "assert post.deleted_at == ~N[2011-05-18 15:01:01]"
         assert file =~ "assert post.deleted_at_usec == ~N[2011-05-18 15:01:01.000000]"
-        assert file =~ "assert post.published_at == DateTime.from_naive!(~N[2011-05-18T15:01:01Z], \"Etc/UTC\")"
-        assert file =~ "assert post.published_at_usec == DateTime.from_naive!(~N[2011-05-18T15:01:01.000000Z], \"Etc/UTC\")"
+        assert file =~ "assert post.published_at == ~U[2011-05-18 15:01:01Z]"
+        assert file =~ "assert post.published_at_usec == ~U[2011-05-18 15:01:01.000000Z]"
         assert file =~ "assert post.alarm == ~T[15:01:01]"
         assert file =~ "assert post.alarm_usec == ~T[15:01:01.000000]"
         assert file =~ "assert post.cost == Decimal.new(\"120.5\")"
diff --git a/test/mix/tasks/phx.gen.live_test.exs b/test/mix/tasks/phx.gen.live_test.exs
index e08d1fded..5afd2625b 100644
--- a/test/mix/tasks/phx.gen.live_test.exs
+++ b/test/mix/tasks/phx.gen.live_test.exs
@@ -171,7 +171,7 @@ defmodule Mix.Tasks.Phx.Gen.LiveTest do
     end
   end
 
-  test "with --web namespace generates namedspaced web modules and directories", config do
+  test "with --web namespace generates namespaced web modules and directories", config do
     in_tmp_live_project config.test, fn ->
       Gen.Live.run(~w(Blog Post posts title:string --web Blog))
 
@@ -285,6 +285,32 @@ defmodule Mix.Tasks.Phx.Gen.LiveTest do
     end
   end
 
+
+  test "with same singular and plural", config do
+    in_tmp_live_project config.test, fn ->
+      Gen.Live.run(~w(Tracker Series series value:integer))
+
+      assert_file "lib/phoenix/tracker.ex"
+      assert_file "lib/phoenix/tracker/series.ex"
+
+      assert_file "lib/phoenix_web/live/series_live/index.ex", fn file ->
+        assert file =~ "assign(socket, :series_collection, list_series())"
+      end
+
+      assert_file "lib/phoenix_web/live/series_live/show.ex"
+      assert_file "lib/phoenix_web/live/series_live/form_component.ex"
+      assert_file "lib/phoenix_web/live/modal_component.ex"
+
+      assert_file "lib/phoenix_web/live/series_live/index.html.leex", fn file ->
+        assert file =~ "for series <- @series_collection do"
+      end
+
+      assert_file "lib/phoenix_web/live/series_live/show.html.leex"
+      assert_file "lib/phoenix_web/live/series_live/form_component.html.leex"
+      assert_file "test/phoenix_web/live/series_live_test.exs"
+    end
+  end
+
   describe "inside umbrella" do
     test "without context_app generators config uses web dir", config do
       in_tmp_live_umbrella_project config.test, fn ->
diff --git a/test/mix/tasks/phx.new_test.exs b/test/mix/tasks/phx.new_test.exs
index c789b5abe..f55d35e84 100644
--- a/test/mix/tasks/phx.new_test.exs
+++ b/test/mix/tasks/phx.new_test.exs
@@ -77,11 +77,9 @@ defmodule Mix.Tasks.Phx.NewTest do
             end)
           end) =~ ~r"3 tests, 0 failures"
 
-          if Version.match?(System.version(), ">= 1.7.0") do
-            File.mkdir_p!("deps/phoenix")
-            File.cp_r!(Path.join(root, ".formatter.exs"), "deps/phoenix/.formatter.exs")
-            Mix.Task.run("format", ["--check-formatted"])
-          end
+          File.mkdir_p!("deps/phoenix")
+          File.cp_r!(Path.join(root, ".formatter.exs"), "deps/phoenix/.formatter.exs")
+          Mix.Task.run("format", ["--check-formatted"])
         end
       after
         Code.delete_path Path.join(project_path, "_build/test/consolidated")
diff --git a/test/phoenix/digester_test.exs b/test/phoenix/digester_test.exs
index 6eef0f5d0..03b25c2ab 100644
--- a/test/phoenix/digester_test.exs
+++ b/test/phoenix/digester_test.exs
@@ -35,7 +35,6 @@ defmodule Phoenix.DigesterTest do
       json = Path.join(@output_path, "cache_manifest.json") |> json_read!()
       assert json["latest"]["phoenix.png"] =~ ~r"phoenix-#{@hash_regex}.png"
       assert json["version"] == 1
-      assert byte_size(json["hash"]) == 22
     end
 
     test "includes existing digests in new cache manifest" do
@@ -131,7 +130,6 @@ defmodule Phoenix.DigesterTest do
 
       json = Path.join(input_path, "cache_manifest.json") |> json_read!()
       assert json["digests"] == %{}
-      assert byte_size(json["hash"]) == 22
     end
 
     test "digests and compress nested files" do
@@ -169,8 +167,6 @@ defmodule Phoenix.DigesterTest do
 
       json2 = Path.join(@output_path, "cache_manifest.json") |> json_read!()
       assert Enum.count(json2["digests"]) == 2
-
-      assert json1["hash"] != json2["hash"]
     end
 
     test "doesn't duplicate files when digesting and compressing twice" do
@@ -182,11 +178,8 @@ defmodule Phoenix.DigesterTest do
       File.write!(input_file, "console.log('test');")
 
       assert :ok = Phoenix.Digester.compile(input_path, input_path)
-      json1 = Path.join(input_path, "cache_manifest.json") |> json_read!()
       assert :ok = Phoenix.Digester.compile(input_path, input_path)
-      json2 = Path.join(input_path, "cache_manifest.json") |> json_read!()
 
-      assert json1["hash"] == json2["hash"]
       output_files = assets_files(input_path)
       refute "file.js.gz.gz" in output_files
       refute "cache_manifest.json.gz" in output_files
@@ -239,7 +232,6 @@ defmodule Phoenix.DigesterTest do
     test "digests sourceMappingURL asset paths found within javascript source files" do
       input_path = "test/fixtures/digest/priv/static/"
       assert :ok = Phoenix.Digester.compile(input_path, @output_path)
-      json = Path.join(@output_path, "cache_manifest.json") |> json_read!()
 
       digested_js_map_filename =
         assets_files(@output_path)
@@ -252,12 +244,10 @@ defmodule Phoenix.DigesterTest do
       digested_js = Path.join(@output_path, digested_js_filename) |> File.read!()
       refute digested_js =~ "app.js.map"
       assert digested_js =~ "#{digested_js_map_filename}"
-      assert digested_js =~ "var digest = \"#{json["hash"]}\";"
 
       js = Path.join(@output_path, "app.js") |> File.read!()
       assert js =~ "app.js.map"
       refute js =~ "#{digested_js_map_filename}"
-      assert js =~ "var digest = \"#{json["hash"]}\";"
     end
 
     test "digests file url paths found within javascript mapping files" do
diff --git a/test/phoenix/endpoint/endpoint_test.exs b/test/phoenix/endpoint/endpoint_test.exs
index 79fe3c0fd..26d384685 100644
--- a/test/phoenix/endpoint/endpoint_test.exs
+++ b/test/phoenix/endpoint/endpoint_test.exs
@@ -95,14 +95,16 @@ defmodule Phoenix.Endpoint.EndpointTest do
   end
 
   test "warms up caches on load and config change" do
-    assert Endpoint.config(:cache_static_manifest_hash) == "cjkuB6uDZecddGSFz7D7kg"
+    assert Endpoint.config(:cache_static_manifest_latest) ==
+             %{"foo.css" => "foo-d978852bea6530fcd197b5445ed008fd.css"}
+
     assert Endpoint.static_path("/foo.css") == "/foo-d978852bea6530fcd197b5445ed008fd.css?vsn=d"
 
     # Trigger a config change and the cache should be warmed up again
     config = put_in(@config[:cache_static_manifest], "../../../../test/fixtures/digest/compile/cache_manifest_upgrade.json")
 
     assert Endpoint.config_change([{Endpoint, config}], []) == :ok
-    assert Endpoint.config(:cache_static_manifest_hash) == "3xAzpSnmcgte3bMevWqJMA"
+    assert Endpoint.config(:cache_static_manifest_latest) == %{"foo.css" => "foo-ghijkl.css"}
     assert Endpoint.static_path("/foo.css") == "/foo-ghijkl.css?vsn=d"
   end
 
@@ -144,7 +146,7 @@ defmodule Phoenix.Endpoint.EndpointTest do
   end
 
   test "injects pubsub broadcast with configured server" do
-    Phoenix.PubSub.subscribe(:endpoint_pub, "sometopic")
+    Endpoint.subscribe("sometopic")
     some = spawn fn -> :ok end
 
     Endpoint.broadcast_from(some, "sometopic", "event1", %{key: :val})
diff --git a/test/phoenix/integration/websocket_channels_test.exs b/test/phoenix/integration/websocket_channels_test.exs
index add5f9aa2..fdc0699a4 100644
--- a/test/phoenix/integration/websocket_channels_test.exs
+++ b/test/phoenix/integration/websocket_channels_test.exs
@@ -64,20 +64,24 @@ defmodule Phoenix.Integration.WebSocketChannelsTest do
   defmodule CustomChannel do
     use GenServer, restart: :temporary
 
-    def start_link(triplet) do
-      GenServer.start_link(__MODULE__, triplet)
+    def start_link(from) do
+      GenServer.start_link(__MODULE__, from)
     end
 
-    def init({payload, from, socket}) do
+    def init({_, _}) do
+      {:ok, :init}
+    end
+
+    def handle_info({Phoenix.Channel, payload, from, socket}, :init) do
       case payload["action"] do
         "ok" ->
           GenServer.reply(from, {:ok, %{"action" => "ok"}})
-          {:ok, socket}
+          {:noreply, socket}
 
         "ignore" ->
           GenServer.reply(from, {:error, %{"action" => "ignore"}})
           send(self(), :stop)
-          {:ok, socket}
+          {:noreply, socket}
 
         "error" ->
           raise "oops"
@@ -165,7 +169,14 @@ defmodule Phoenix.Integration.WebSocketChannelsTest do
       websocket: [
         check_origin: ["//example.com"],
         timeout: 200,
-        connect_info: [:x_headers, :peer_data, :uri, session: @session_config, signing_salt: "salt"]
+        connect_info: [
+          :x_headers,
+          :peer_data,
+          :uri,
+          :user_agent,
+          session: @session_config,
+          signing_salt: "salt"
+        ]
       ]
 
     plug Plug.Session, @session_config
@@ -296,6 +307,28 @@ defmodule Phoenix.Integration.WebSocketChannelsTest do
                                       "port" => 80}}}}
       end
 
+      test "transport user agent is extracted to the socket connect_info" do
+        extra_headers = [{"user-agent", "foo/1.0"}]
+
+        {:ok, sock} =
+          WebsocketClient.start_link(
+            self(),
+            "ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}",
+            @serializer,
+            extra_headers
+          )
+        WebsocketClient.join(sock, lobby(), %{})
+
+        assert_receive %Message{
+          event: "joined",
+          payload: %{
+            "connect_info" => %{
+              "user_agent" => "foo/1.0"
+            }
+          }
+        }
+      end
+
       test "transport session is extracted to the socket connect_info" do
         import Phoenix.ConnTest
         path = "ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}"
diff --git a/test/phoenix/presence_test.exs b/test/phoenix/presence_test.exs
index 06fcdfbc6..71e073cef 100644
--- a/test/phoenix/presence_test.exs
+++ b/test/phoenix/presence_test.exs
@@ -20,7 +20,7 @@ defmodule Phoenix.PresenceTest do
 
   setup_all do
     start_supervised! {Phoenix.PubSub, name: PresPub, pool_size: 1}
-    assert {:ok, _pid} = MyPresence.start_link([])
+    start_supervised! MyPresence
     {:ok, pubsub: PresPub}
   end
 
@@ -76,7 +76,7 @@ defmodule Phoenix.PresenceTest do
 
     pid = spawn(fn -> :timer.sleep(:infinity) end)
     Phoenix.PubSub.subscribe(config.pubsub, topic)
-    {:ok, _pid} = DefaultPresence.start_link(pubsub_server: config.pubsub)
+    start_supervised! {DefaultPresence, pubsub_server: config.pubsub}
     DefaultPresence.track(pid, topic, "u1", %{name: "u1"})
 
     assert_receive %Broadcast{topic: ^topic, event: "presence_diff", payload: %{