Repository: phoenixframework/phoenix
Pull request #3629 (2019-12-05 16:33:09)

Add documentation for channel.off function
Just added a description of what it does, as I had to go into the source code to figure out how it worked.


Patch:
diff --git a/assets/js/phoenix.js b/assets/js/phoenix.js
index 76bff1f80..a5a78f876 100644
--- a/assets/js/phoenix.js
+++ b/assets/js/phoenix.js
@@ -474,6 +474,20 @@ export class Channel {
   }
 
   /**
+   * Unsubscribes off of channel events
+   *
+   * Use the ref returned from a channel.on() to unsubscribe one 
+   * handler, or pass nothing for the ref to unsubscribe all 
+   * handlers for the given event.
+   *
+   * @example
+   * // Unsubscribe the do_stuff handler
+   * const ref1 = channel.on("event", do_stuff)
+   * channel.off("event", ref1)
+   * 
+   * // Unsubscribe all handlers from event
+   * channel.off("event")
+   *
    * @param {string} event
    * @param {integer} ref
    */
diff --git a/guides/deployment/heroku.md b/guides/deployment/heroku.md
index c522d56d9..d6236749d 100644
--- a/guides/deployment/heroku.md
+++ b/guides/deployment/heroku.md
@@ -69,15 +69,14 @@ Once we have signed up, we can download the correct version of the Heroku Toolbe
 The Heroku CLI, part of the Toolbelt, is useful to create Heroku applications, list currently running dynos for an existing application, tail logs or run one-off commands (mix tasks for instance).
 
 ## Create and Setup Heroku Application
-There are two different ways to deploy a Phoenix app on Heroku. We could use Heroku buildpacks or their container stack. The difference between these two approaches is in how we tell Heroku to treat our build. In buildpack case, we need to update our apps configuration on Heroku to use Phoenix/Elixir specific buildpacks instead of trying to automatically detect it. On container approach, we have more control on how we want to setup our app and we can define our container image using `Dockerfile` and `heroku.yml`. We describe both approaches below.
 
-### Using Heroku Buildpacks
+There are two different ways to deploy a Phoenix app on Heroku. We could use Heroku buildpacks or their container stack. The difference between these two approaches is in how we tell Heroku to treat our build. In buildpack case, we need to update our apps configuration on Heroku to use Phoenix/Elixir specific buildpacks. On container approach, we have more control on how we want to setup our app and we can define our container image using `Dockerfile` and `heroku.yml`. This section will explore the buildpack approach. In order to use Dockerfile, it is often recommended to convert our app to use releases, which we will describe later on.
 
-A [buildpack](https://devcenter.heroku.com/articles/buildpacks) is a convenient way of packaging framework and/or runtime support. Phoenix requires 2 buildpacks to run on Heroku, the first adds basic Elixir support and the second adds Phoenix specific commands. We will see how to install both in the next sections.
+### Create Application
 
-#### Create Application
+A [buildpack](https://devcenter.heroku.com/articles/buildpacks) is a convenient way of packaging framework and/or runtime support. Phoenix requires 2 buildpacks to run on Heroku, the first adds basic Elixir support and the second adds Phoenix specific commands.
 
-Now that we have the Toolbelt installed, let's create the Heroku application. We will do so using the latest available version of the [Elixir buildpack](https://github.com/HashNuke/heroku-buildpack-elixir):
+With the Toolbelt installed, let's create the Heroku application. We will do so using the latest available version of the [Elixir buildpack](https://github.com/HashNuke/heroku-buildpack-elixir):
 
 ```console
 $ heroku create --buildpack hashnuke/elixir
@@ -105,7 +104,7 @@ elixir_version=1.8.1
 erlang_version=21.2.5
 ```
 
-#### Adding the Phoenix Server and Assets Buildpack
+### Adding the Phoenix Server and Assets Buildpack
 
 To successfully run Phoenix in production, we need to compile assets and start the Phoenix server. The [Phoenix Static buildpack](https://github.com/gjaldon/heroku-buildpack-phoenix-static) can take care of that for us, so let's add it now.
 
@@ -128,34 +127,6 @@ Heroku will recognize this file and use the command to start your application, e
 
 Finally, note that since we are using multiple buildpacks, you might run into an issue where the sequence is out of order (the Elixir buildpack needs to run before the Phoenix Static buildpack). [Heroku's docs](https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app) explain this better, but you will need to make sure the Phoenix Static buildpack comes last.
 
-### Using Container Stack
-#### Create Heroku application
-Set the stack of your app to `container`, this allows us to use `Dockerfile` to define our app setup.
-```console
-$ heroku create
-Creating app... done, ⬢ mysterious-meadow-6277
-$ heroku stack:set container
-```
-Add a new `heroku.yml` file to your root folder. In this file you can define addons used by your app, how to build the image and what configs are passed to the image. You can learn more about Heroku's `heroku.yml` options [here](https://devcenter.heroku.com/articles/build-docker-images-heroku-yml). Here is a sample:
-```yaml
-setup:
-  addons:
-    - plan: heroku-postgresql
-      as: DATABASE
-build:
-  docker:
-    web: Dockerfile
-  config:
-    MIX_ENV: prod
-    SECRET_KEY_BASE: $SECRET_KEY_BASE
-    DATABASE_URL: $DATABASE_URL
-```
-
-#### Setup Your app with Dockerfile
-Add `Dockerfile` to your root folder. You can follow [container release docs](./releases.md#containers).
-
-Once you have the image definition setup, you can push your app to heroku and you can see it starts building the image and deploy it.
-
 ## Making our Project ready for Heroku
 
 Every new Phoenix project ships with a config file `config/prod.secret.exs` which loads configuration and secrets from [environment variables](https://devcenter.heroku.com/articles/config-vars). This aligns well with Heroku best practices, so most the only work left for us to do is to configure URLs and SSL.
@@ -345,19 +316,39 @@ $ heroku run "POOL_SIZE=2 mix ecto.migrate"
 
 And that's it!
 
-## Connecting to your dyno
+## Deploying to Heroku using the container stack
 
-Heroku gives you the ability to connect to your dyno with an IEx shell which allows running Elixir code such as database queries.
+### Create Heroku application
 
-- Modify the `web` process in your Procfile to run a named node:
-  ```
-  web: elixir --sname server -S mix phx.server
-  ```
-- Redeploy to Heroku
-- Connect to the dyno with `heroku ps:exec` (if you have several applications on the same repository you will need to specify the app name or the remote name with `--app APP_NAME` or `--remote REMOTE_NAME`)
-- Launch an iex session with `iex --sname console --remsh server`
+Set the stack of your app to `container`, this allows us to use `Dockerfile` to define our app setup.
 
-You have an iex session into your dyno!
+```console
+$ heroku create
+Creating app... done, ⬢ mysterious-meadow-6277
+$ heroku stack:set container
+```
+
+Add a new `heroku.yml` file to your root folder. In this file you can define addons used by your app, how to build the image and what configs are passed to the image. You can learn more about Heroku's `heroku.yml` options [here](https://devcenter.heroku.com/articles/build-docker-images-heroku-yml). Here is a sample:
+
+```yaml
+setup:
+  addons:
+    - plan: heroku-postgresql
+      as: DATABASE
+build:
+  docker:
+    web: Dockerfile
+  config:
+    MIX_ENV: prod
+    SECRET_KEY_BASE: $SECRET_KEY_BASE
+    DATABASE_URL: $DATABASE_URL
+```
+
+### Setup releases and Dockerfile
+
+Now we need to define a `Dockerfile` at the root folder of your project that contains your application. We recommend to use releases when doing so, as the release will allow us to build a container with only the parts of Erlang and Elixir we actually use. Follow [the releases docs](/releases.html). At the end of the guide, there is a sample Dockerfile file you can use.
+
+Once you have the image definition setup, you can push your app to heroku and you can see it starts building the image and deploy it.
 
 ## Useful Heroku Commands
 
@@ -379,6 +370,20 @@ In fact, we can run anything using the `heroku run` command, like the Ecto migra
 $ heroku run "POOL_SIZE=2 mix ecto.migrate"
 ```
 
+## Connecting to your dyno
+
+Heroku gives you the ability to connect to your dyno with an IEx shell which allows running Elixir code such as database queries.
+
+- Modify the `web` process in your Procfile to run a named node:
+  ```
+  web: elixir --sname server -S mix phx.server
+  ```
+- Redeploy to Heroku
+- Connect to the dyno with `heroku ps:exec` (if you have several applications on the same repository you will need to specify the app name or the remote name with `--app APP_NAME` or `--remote REMOTE_NAME`)
+- Launch an iex session with `iex --sname console --remsh server`
+
+You have an iex session into your dyno!
+
 ## Troubleshooting
 
 ### Compilation Error
diff --git a/guides/ecto.md b/guides/ecto.md
index 3846e1242..cd3f3d9aa 100644
--- a/guides/ecto.md
+++ b/guides/ecto.md
@@ -122,7 +122,7 @@ Our `Hello.Repo` module is the foundation we need to work with databases in a Ph
 defmodule Hello.Repo do
   use Ecto.Repo,
     otp_app: :hello,
-    adapter: Ecto.Adapter.Postgres
+    adapter: Ecto.Adapters.Postgres
 end
 ```
 
diff --git a/installer/templates/phx_ecto/data_case.ex b/installer/templates/phx_ecto/data_case.ex
index 4d6c9e6ca..9860f88c7 100644
--- a/installer/templates/phx_ecto/data_case.ex
+++ b/installer/templates/phx_ecto/data_case.ex
@@ -10,8 +10,8 @@ defmodule <%= app_module %>.DataCase do
   we enable the SQL sandbox, so changes done to the database
   are reverted at the end of every test. If you are using
   PostgreSQL, you can even run database tests asynchronously
-  by setting `use <%= web_namespace %>.DataCase, async: true`, although
-  this option is not recommendded for other databases.
+  by setting `use <%= app_module %>.DataCase, async: true`, although
+  this option is not recommended for other databases.
   """
 
   use ExUnit.CaseTemplate
diff --git a/installer/templates/phx_single/README.md b/installer/templates/phx_single/README.md
index 2f263fda0..4c7dcff6c 100644
--- a/installer/templates/phx_single/README.md
+++ b/installer/templates/phx_single/README.md
@@ -16,5 +16,5 @@ Ready to run in production? Please [check our deployment guides](https://hexdocs
   * Official website: https://www.phoenixframework.org/
   * Guides: https://hexdocs.pm/phoenix/overview.html
   * Docs: https://hexdocs.pm/phoenix
-  * Mailing list: http://groups.google.com/group/phoenix-talk
+  * Forum: https://elixirforum.com/c/phoenix-forum
   * Source: https://github.com/phoenixframework/phoenix
diff --git a/installer/templates/phx_static/app.js b/installer/templates/phx_static/app.js
index 69bf58f0b..c0b39de9d 100644
--- a/installer/templates/phx_static/app.js
+++ b/installer/templates/phx_static/app.js
@@ -1,3 +1,3 @@
 // for phoenix_html support, including form and button helpers
 // copy the following scripts into your javascript bundle:
-// * https://raw.githubusercontent.com/phoenixframework/phoenix_html/master/priv/static/phoenix_html.js
+// * deps/phoenix_html/priv/static/phoenix_html.js
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/README.md b/installer/templates/phx_umbrella/apps/app_name_web/README.md
index 87cba417c..3f02bbe88 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/README.md
+++ b/installer/templates/phx_umbrella/apps/app_name_web/README.md
@@ -16,5 +16,5 @@ Ready to run in production? Please [check our deployment guides](https://hexdocs
   * Official website: https://www.phoenixframework.org/
   * Guides: https://hexdocs.pm/phoenix/overview.html
   * Docs: https://hexdocs.pm/phoenix
-  * Mailing list: http://groups.google.com/group/phoenix-talk
+  * Forum: https://elixirforum.com/c/phoenix-forum
   * Source: https://github.com/phoenixframework/phoenix
diff --git a/lib/phoenix/code_reloader.ex b/lib/phoenix/code_reloader.ex
index 8de52c8aa..51a8e0ebb 100644
--- a/lib/phoenix/code_reloader.ex
+++ b/lib/phoenix/code_reloader.ex
@@ -2,9 +2,17 @@ defmodule Phoenix.CodeReloader do
   @moduledoc """
   A plug and module to handle automatic code reloading.
 
-  For each request, Phoenix checks if any of the modules previously
-  compiled requires recompilation via `__phoenix_recompile__?/0` and then
-  calls `mix compile` for sources exclusive to the `web` directory.
+  For each request, Phoenix goes through all modules and checks if any of
+  them implement a `__phoenix_recompile__?/0` function. If they do and
+  it returns true, the module source file is touched, forcing it to be
+  recompiled. For this functionality to work, Phoenix requires you to add
+  the `:phoenix` compiler to your list of compilers:
+
+      compilers: [:phoenix] ++ Mix.compilers()
+
+  This is useful, for example, to recompile modules that depend on external
+  systems, such as directories, databases, etc. Note if you simply depend on
+  external files, `@external_resource` annotation should be used.
 
   To avoid race conditions, all code reloads are funneled through a
   sequential call operation.
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index 6d01c5065..d1c544c86 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -100,7 +100,9 @@ defmodule Phoenix.Endpoint do
     * `:cache_static_manifest` - a path to a json manifest file that contains
       static files and their digested version. This is typically set to
       "priv/static/cache_manifest.json" which is the file automatically generated
-      by `mix phx.digest`
+      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.
 
     * `:check_origin` - configure the default `:check_origin` setting for
       transports. See `socket/3` for options. Defaults to `true`.
@@ -839,7 +841,7 @@ defmodule Phoenix.Endpoint do
     * `:max_frame_size` - the maximum allowed frame size in bytes.
       Supported from Cowboy 2.3 onwards, defaults to "infinity"
 
-    * `:compress` - whether to enable per message compresssion on
+    * `:compress` - whether to enable per message compression on
       all data frames, defaults to false
 
     * `:subprotocols` - a list of supported websocket subprotocols.
diff --git a/lib/phoenix/endpoint/cowboy2_handler.ex b/lib/phoenix/endpoint/cowboy2_handler.ex
index c478c75c4..4cd34eb20 100644
--- a/lib/phoenix/endpoint/cowboy2_handler.ex
+++ b/lib/phoenix/endpoint/cowboy2_handler.ex
@@ -2,7 +2,7 @@ defmodule Phoenix.Endpoint.Cowboy2Handler do
   @moduledoc false
 
   if Code.ensure_loaded?(:cowboy_websocket) and
-    function_exported?(:cowboy_websocket, :behaviour_info, 1) do
+       function_exported?(:cowboy_websocket, :behaviour_info, 1) do
     @behaviour :cowboy_websocket
   end
 
@@ -45,26 +45,17 @@ defmodule Phoenix.Endpoint.Cowboy2Handler do
         {:ok, req, {handler, opts}}
     end
   catch
-    :error, value ->
+    kind, reason ->
       case System.stacktrace() do
         # Maybe the handler is not available because the code is being recompiled.
         # Sync with the code reloader and retry once.
-        [{^endpoint, :__handler__, _, _} | _] when value == :undef and retry? ->
+        [{^endpoint, :__handler__, _, _} | _] when reason == :undef and retry? ->
           Phoenix.CodeReloader.Server.sync()
           init(conn, endpoint, opts, false)
 
-        _ ->
-          stack = System.stacktrace()
-          exception = Exception.normalize(:error, value, stack)
-          exit({{exception, stack}, {endpoint, :call, [conn, opts]}})
+        stacktrace ->
+          exit_on_error(kind, reason, stacktrace, {endpoint, :call, [conn, opts]})
       end
-
-    :throw, value ->
-      stack = System.stacktrace()
-      exit({{{:nocatch, value}, stack}, {endpoint, :call, [conn, opts]}})
-
-    :exit, value ->
-      exit({value, {endpoint, :call, [conn, opts]}})
   after
     receive do
       @already_sent -> :ok
@@ -82,6 +73,42 @@ defmodule Phoenix.Endpoint.Cowboy2Handler do
             inspect(other)
   end
 
+  defp exit_on_error(
+         :error,
+         %Plug.Conn.WrapperError{kind: kind, reason: reason, stack: stack},
+         _stack,
+         call
+       ) do
+    exit_on_error(kind, reason, stack, call)
+  end
+
+  defp exit_on_error(:error, value, stack, call) do
+    exception = Exception.normalize(:error, value, stack)
+    exit({{exception, stack}, call})
+  end
+
+  defp exit_on_error(:throw, value, stack, call) do
+    exit({{{:nocatch, value}, stack}, call})
+  end
+
+  defp exit_on_error(:exit, value, _stack, call) do
+    exit({value, call})
+  end
+
+  defp copy_resp_headers(%Plug.Conn{} = conn, req) do
+    Enum.reduce(conn.resp_headers, req, fn {key, val}, acc ->
+      :cowboy_req.set_resp_header(key, val, acc)
+    end)
+  end
+
+  defp handle_reply(handler, {:ok, state}), do: {:ok, [handler | state]}
+  defp handle_reply(handler, {:push, data, state}), do: {:reply, data, [handler | state]}
+
+  defp handle_reply(handler, {:reply, _status, data, state}),
+    do: {:reply, data, [handler | state]}
+
+  defp handle_reply(handler, {:stop, _reason, state}), do: {:stop, [handler | state]}
+
   ## Websocket callbacks
 
   def websocket_init([handler | state]) do
@@ -125,15 +152,4 @@ defmodule Phoenix.Endpoint.Cowboy2Handler do
   def terminate(reason, _req, [handler | state]) do
     handler.terminate(reason, state)
   end
-
-  defp copy_resp_headers(%Plug.Conn{} = conn, req) do
-    Enum.reduce(conn.resp_headers, req, fn {key, val}, acc ->
-      :cowboy_req.set_resp_header(key, val, acc)
-    end)
-  end
-
-  defp handle_reply(handler, {:ok, state}), do: {:ok, [handler | state]}
-  defp handle_reply(handler, {:push, data, state}), do: {:reply, data, [handler | state]}
-  defp handle_reply(handler, {:reply, _status, data, state}), do: {:reply, data, [handler | state]}
-  defp handle_reply(handler, {:stop, _reason, state}), do: {:stop, [handler | state]}
 end
diff --git a/lib/phoenix/endpoint/render_errors.ex b/lib/phoenix/endpoint/render_errors.ex
index 2f97f3c55..9b596c855 100644
--- a/lib/phoenix/endpoint/render_errors.ex
+++ b/lib/phoenix/endpoint/render_errors.ex
@@ -107,7 +107,7 @@ defmodule Phoenix.Endpoint.RenderErrors do
     reason = Exception.normalize(kind, reason, stack)
     format = Controller.get_format(conn)
     template = "#{conn.status}.#{format}"
-    assigns = %{kind: kind, reason: reason, stack: stack}
+    assigns = %{kind: kind, reason: reason, stack: stack, status: conn.status}
 
     conn
     |> Controller.put_view(view)
diff --git a/lib/phoenix/endpoint/supervisor.ex b/lib/phoenix/endpoint/supervisor.ex
index a09e5f165..59b5d8649 100644
--- a/lib/phoenix/endpoint/supervisor.ex
+++ b/lib/phoenix/endpoint/supervisor.ex
@@ -452,7 +452,14 @@ defmodule Phoenix.Endpoint.Supervisor do
 
   defp cache_static_manifest(endpoint) do
     if inner = endpoint.config(:cache_static_manifest) do
-      outer = Application.app_dir(endpoint.config(:otp_app), inner)
+      {app, inner} = 
+        case inner do
+          {_, _} = inner -> inner
+          inner when is_binary(inner) -> {endpoint.config(:otp_app), inner}
+          _ -> raise ArgumentError, ":cache_static_manifest must be a binary or a tuple"
+        end
+
+      outer = Application.app_dir(app, inner)
 
       if File.exists?(outer) do
         outer |> File.read!() |> Phoenix.json_library().decode!()
diff --git a/lib/phoenix/router.ex b/lib/phoenix/router.ex
index 6c9db4b44..e93964670 100644
--- a/lib/phoenix/router.ex
+++ b/lib/phoenix/router.ex
@@ -15,6 +15,13 @@ defmodule Phoenix.Router do
     end
   end
 
+  defmodule MalformedURIError do
+    @moduledoc """
+    Exception raised when the URI is malformed on matching.
+    """
+    defexception [:message, plug_status: 400]
+  end
+
   @moduledoc """
   Defines a Phoenix router.
 
@@ -375,7 +382,15 @@ defmodule Phoenix.Router do
       def call(conn, _opts) do
         %{method: method, path_info: path_info, host: host} = conn = prepare(conn)
 
-        case __match_route__(method, Enum.map(path_info, &URI.decode/1), host) do
+        decoded =
+          try do
+            Enum.map(path_info, &URI.decode/1)
+          rescue
+            ArgumentError ->
+              raise MalformedURIError, "malformed URI path: #{inspect conn.request_path}"
+          end
+
+        case __match_route__(method, decoded, host) do
           :error -> raise NoRouteError, conn: conn, router: __MODULE__
           match -> Phoenix.Router.__call__(conn, match)
         end
diff --git a/lib/phoenix/router/helpers.ex b/lib/phoenix/router/helpers.ex
index fd305e824..70b43f4b6 100644
--- a/lib/phoenix/router/helpers.ex
+++ b/lib/phoenix/router/helpers.ex
@@ -327,16 +327,22 @@ defmodule Phoenix.Router.Helpers do
   @doc """
   Callback for generate router catch alls.
   """
-  def raise_route_error(mod, fun, arity, action, routes, params)
-      when is_list(params) or is_map(params) do
+  def raise_route_error(mod, fun, arity, action, routes, params) do
+    cond do
+      not Keyword.has_key?(routes, action) ->
+        "no action #{inspect action} for #{inspect mod}.#{fun}/#{arity}"
+        |> invalid_route_error(fun, routes)
 
-    prelude =
-      if Keyword.has_key?(routes, action) do
-        "no action #{inspect action} for helper #{inspect mod}.#{fun}/#{arity}"
-      else
+      is_list(params) or is_map(params) ->
         "no function clause for #{inspect mod}.#{fun}/#{arity} and action #{inspect action}"
-      end
+        |> invalid_route_error(fun, routes)
 
+      true ->
+        invalid_param_error(mod, fun, arity, action, routes)
+    end
+  end
+
+  defp invalid_route_error(prelude, fun, routes) do
     suggestions =
       for {action, bindings} <- routes do
         bindings = Enum.join([inspect(action) | bindings], ", ")
@@ -345,7 +351,8 @@ defmodule Phoenix.Router.Helpers do
 
     raise ArgumentError, "#{prelude}. The following actions/clauses are supported:\n#{suggestions}"
   end
-  def raise_route_error(mod, fun, arity, action, routes, _params) do
+
+  defp invalid_param_error(mod, fun, arity, action, routes) do
     call_vars = Keyword.fetch!(routes, action)
 
     raise ArgumentError, """
@@ -353,7 +360,7 @@ defmodule Phoenix.Router.Helpers do
     The last argument to this function should be a keyword list or a map.
     For example:
 
-    #{fun}(#{Enum.join(["conn", ":#{action}" | call_vars], ", ")}, page: 5, per_page: 10)
+        #{fun}(#{Enum.join(["conn", ":#{action}" | call_vars], ", ")}, page: 5, per_page: 10)
 
     It is possible you have called this function without defining the proper
     number of path segments in your router.
diff --git a/lib/phoenix/template.ex b/lib/phoenix/template.ex
index ef3c38c8f..5473104c3 100644
--- a/lib/phoenix/template.ex
+++ b/lib/phoenix/template.ex
@@ -106,6 +106,7 @@ defmodule Phoenix.Template do
   ]
 
   @default_pattern "*"
+  @private_assigns [:__phx_template_not_found__]
 
   defmodule UndefinedError do
     @moduledoc """
@@ -334,7 +335,7 @@ defmodule Phoenix.Template do
   def raise_template_not_found(view_module, template, assigns) do
     {root, pattern, names} = view_module.__templates__()
     raise UndefinedError,
-      assigns: assigns,
+      assigns: Map.drop(assigns, @private_assigns),
       available: names,
       template: template,
       root: root,
diff --git a/priv/templates/phx.gen.context/schema_access.ex b/priv/templates/phx.gen.context/schema_access.ex
index 769889156..38a3db8f6 100644
--- a/priv/templates/phx.gen.context/schema_access.ex
+++ b/priv/templates/phx.gen.context/schema_access.ex
@@ -67,7 +67,7 @@
   end
 
   @doc """
-  Deletes a <%= inspect schema.alias %>.
+  Deletes a <%= schema.singular %>.
 
   ## Examples
 


Test patch:
diff --git a/installer/templates/phx_test/support/channel_case.ex b/installer/templates/phx_test/support/channel_case.ex
index 732675003..85928c26f 100644
--- a/installer/templates/phx_test/support/channel_case.ex
+++ b/installer/templates/phx_test/support/channel_case.ex
@@ -8,9 +8,11 @@ defmodule <%= web_namespace %>.ChannelCase do
   to build common data structures and query the data layer.
 
   Finally, if the test case interacts with the database,
-  it cannot be async. For this reason, every test runs
-  inside a transaction which is reset at the beginning
-  of the test unless the test case is marked as async.
+  we enable the SQL sandbox, so changes done to the database
+  are reverted at the end of every test. If you are using
+  PostgreSQL, you can even run database tests asynchronously
+  by setting `use <%= web_namespace %>.ChannelCase, async: true`, although
+  this option is not recommended for other databases.
   """
 
   use ExUnit.CaseTemplate
diff --git a/installer/templates/phx_test/support/conn_case.ex b/installer/templates/phx_test/support/conn_case.ex
index be9fcb2f2..6ce8e47de 100644
--- a/installer/templates/phx_test/support/conn_case.ex
+++ b/installer/templates/phx_test/support/conn_case.ex
@@ -12,7 +12,7 @@ defmodule <%= web_namespace %>.ConnCase do
   are reverted at the end of every test. If you are using
   PostgreSQL, you can even run database tests asynchronously
   by setting `use <%= web_namespace %>.ConnCase, async: true`, although
-  this option is not recommendded for other databases.
+  this option is not recommended for other databases.
   """
 
   use ExUnit.CaseTemplate
diff --git a/test/phoenix/endpoint/endpoint_test.exs b/test/phoenix/endpoint/endpoint_test.exs
index ac83e94d1..93767256f 100644
--- a/test/phoenix/endpoint/endpoint_test.exs
+++ b/test/phoenix/endpoint/endpoint_test.exs
@@ -170,6 +170,13 @@ defmodule Phoenix.Endpoint.EndpointTest do
       event: "event3", payload: %{key: :val}, topic: "sometopic"}
   end
 
+  test "loads cache manifest from specified application" do
+    config = put_in(@config[:cache_static_manifest], {:phoenix, "../../../../test/fixtures/cache_manifest.json"})
+    
+    assert Endpoint.config_change([{Endpoint, config}], []) == :ok
+    assert Endpoint.static_path("/foo.css") == "/foo-d978852bea6530fcd197b5445ed008fd.css?vsn=d"
+  end
+
   test "server?/2 returns true for explicitly true server", config do
     endpoint = Module.concat(__MODULE__, config.test)
     Application.put_env(:phoenix, endpoint, server: true)
diff --git a/test/phoenix/endpoint/render_errors_test.exs b/test/phoenix/endpoint/render_errors_test.exs
index 61bf2a91d..b9d3e687c 100644
--- a/test/phoenix/endpoint/render_errors_test.exs
+++ b/test/phoenix/endpoint/render_errors_test.exs
@@ -11,19 +11,19 @@ defmodule Phoenix.Endpoint.RenderErrorsTest do
     "Layout: " <> render(view_template, assigns)
   end
 
-  def render("404.html", %{kind: kind, reason: _reason, stack: _stack, conn: conn}) do
+  def render("404.html", %{kind: kind, reason: _reason, stack: _stack, status: 404, conn: conn}) do
     "Got 404 from #{kind} with #{conn.method}"
   end
 
-  def render("404.json", %{kind: kind, reason: _reason, stack: _stack, conn: conn}) do
+  def render("404.json", %{kind: kind, reason: _reason, stack: _stack, status: 404, conn: conn}) do
     %{error: "Got 404 from #{kind} with #{conn.method}"}
   end
 
-  def render("415.html", %{kind: kind, reason: _reason, stack: _stack, conn: conn}) do
+  def render("415.html", %{kind: kind, reason: _reason, stack: _stack, status: 415, conn: conn}) do
     "Got 415 from #{kind} with #{conn.method}"
   end
 
-  def render("500.html", %{kind: kind, reason: _reason, stack: _stack, conn: conn}) do
+  def render("500.html", %{kind: kind, reason: _reason, stack: _stack, status: 500, conn: conn}) do
     "Got 500 from #{kind} with #{conn.method}"
   end
 
diff --git a/test/phoenix/router/helpers_test.exs b/test/phoenix/router/helpers_test.exs
index 51964c65d..263633026 100644
--- a/test/phoenix/router/helpers_test.exs
+++ b/test/phoenix/router/helpers_test.exs
@@ -157,7 +157,7 @@ defmodule Phoenix.Router.HelpersTest do
 
     error_message = fn helper, arity ->
       """
-      no function clause for #{inspect Helpers}.#{helper}/#{arity} and action :skip. The following actions/clauses are supported:
+      no action :skip for #{inspect Helpers}.#{helper}/#{arity}. The following actions/clauses are supported:
 
           #{helper}(conn_or_endpoint, :file, file, params \\\\ [])
           #{helper}(conn_or_endpoint, :show, id, params \\\\ [])
@@ -261,43 +261,15 @@ defmodule Phoenix.Router.HelpersTest do
     assert Helpers.user_comment_path(__MODULE__, :new, 88, []) == "/users/88/comments/new"
     assert Helpers.user_comment_path(__MODULE__, :new, 88) == "/users/88/comments/new"
 
-    error_message = fn helper, arity ->
-      """
-      no function clause for #{inspect Helpers}.#{helper}/#{arity} and action :skip. The following actions/clauses are supported:
-
-          user_comment_file_path(conn_or_endpoint, :create, user_id, comment_id, params \\\\ [])
-          user_comment_file_path(conn_or_endpoint, :delete, user_id, comment_id, id, params \\\\ [])
-          user_comment_file_path(conn_or_endpoint, :edit, user_id, comment_id, id, params \\\\ [])
-          user_comment_file_path(conn_or_endpoint, :index, user_id, comment_id, params \\\\ [])
-          user_comment_file_path(conn_or_endpoint, :new, user_id, comment_id, params \\\\ [])
-          user_comment_file_path(conn_or_endpoint, :show, user_id, comment_id, id, params \\\\ [])
-          user_comment_file_path(conn_or_endpoint, :update, user_id, comment_id, id, params \\\\ [])
-      """ |> String.trim
-    end
-
-    assert_raise ArgumentError, error_message.("user_comment_file_path", 4), fn ->
+    assert_raise ArgumentError, ~r/no action :skip/, fn ->
       Helpers.user_comment_file_path(__MODULE__, :skip, 123, 456)
     end
 
-    assert_raise ArgumentError, error_message.("user_comment_file_path", 5), fn ->
+    assert_raise ArgumentError, ~r/no action :skip/, fn ->
       Helpers.user_comment_file_path(__MODULE__, :skip, 123, 456, foo: "bar")
     end
 
-    arity_error_message =
-      """
-      no action :show for helper #{inspect Helpers}.user_comment_path/3. The following actions/clauses are supported:
-
-          user_comment_path(conn_or_endpoint, :create, user_id, params \\\\ [])
-          user_comment_path(conn_or_endpoint, :delete, user_id, id, params \\\\ [])
-          user_comment_path(conn_or_endpoint, :edit, user_id, id, params \\\\ [])
-          user_comment_path(conn_or_endpoint, :index, user_id, params \\\\ [])
-          user_comment_path(conn_or_endpoint, :new, user_id, params \\\\ [])
-          user_comment_path(conn_or_endpoint, :show, user_id, id, params \\\\ [])
-          user_comment_path(conn_or_endpoint, :update, user_id, id, params \\\\ [])
-
-      """ |> String.trim
-
-    assert_raise ArgumentError, arity_error_message, fn ->
+    assert_raise ArgumentError, ~r/no function clause for Phoenix.Router.HelpersTest.Router.Helpers.user_comment_path\/3 and action :show/, fn ->
       Helpers.user_comment_path(__MODULE__, :show, 123)
     end
   end
diff --git a/test/phoenix/router/routing_test.exs b/test/phoenix/router/routing_test.exs
index 22240eb7a..81a11c849 100644
--- a/test/phoenix/router/routing_test.exs
+++ b/test/phoenix/router/routing_test.exs
@@ -181,6 +181,12 @@ defmodule Phoenix.Router.RoutingTest do
     assert conn.resp_body == "users any"
   end
 
+  test "raises on malformed URIs" do
+    assert_raise Phoenix.Router.MalformedURIError, fn ->
+      call(Router, :get, "/<% foo %>")
+    end
+  end
+
   describe "logging" do
     setup do
       Logger.enable(self())
diff --git a/test/phoenix/socket/transport_test.exs b/test/phoenix/socket/transport_test.exs
index 030c56825..af25c0cdf 100644
--- a/test/phoenix/socket/transport_test.exs
+++ b/test/phoenix/socket/transport_test.exs
@@ -99,6 +99,7 @@ defmodule Phoenix.Socket.TransportTest do
     end
 
     def invalid_allowed?(%URI{host: nil}), do: true
+    def invalid_allowed?(%URI{host: ""}), do: true
 
     test "allows custom MFA check to handle invalid host" do
       mfa = {__MODULE__, :invalid_allowed?, []}