Repository: phoenixframework/phoenix
Pull request #4224 (2021-03-01 01:11:12)

Copy edit Guides - Views and templates


Patch:
diff --git a/README.md b/README.md
index a26c22e2a..a059d387e 100644
--- a/README.md
+++ b/README.md
@@ -25,10 +25,10 @@ You can create a new project using the latest Phoenix source installer (the `phx
 
 1. Remove any previously installed `phx_new` archives so that Mix will pick up the local source code. This can be done with `mix archive.uninstall phx_new` or by simply deleting the file, which is usually in `~/.mix/archives/`.
 2. Copy this repo via `git clone https://github.com/phoenixframework/phoenix` or by downloading it
-3. Run the `phx.new` mix task from within the `installer` directory, for example:
+3. Run the `phx.new` Mix task from within the `installer` directory, for example:
 
 ```bash
-$ cd installer
+$ cd phoenix/installer
 $ mix phx.new dev_app --dev
 ```
 
diff --git a/RELEASE.md b/RELEASE.md
index babdd4ffe..052ee486d 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,21 +1,17 @@
 # Release Instructions
 
   1. Check related deps for required version bumps and compatibility (`phoenix_ecto`, `phoenix_pubsub_redis`, `phoenix_html`)
-  2. Update `phoenix_dep` in `installer/lib/phx_new/generator.ex` to release
-  3. Bump version in related files below
-  4. Run tests, commit, push code
-  5. Publish `phx_new` and `phoenix` packages and docs after pruning any extraneous uncommitted files
-  6. Test installer by generating a new app, running `mix deps.get`, and compiling
-  7. Publish to `npm` with `npm publish`
-  8. Start -dev version in related files below
-  9. Update `phoenix_dep` in `installer/lib/phx_new/generator.ex` back to git
+  2. Bump version in related files below
+  3. Run tests, commit, push code
+  4. Publish `phx_new` and `phoenix` packages and docs after pruning any extraneous uncommitted files
+  5. Test installer by generating a new app, running `mix deps.get`, and compiling
+  6. Publish to `npm` with `npm publish`
+  7. Start -dev version in related files below
 
 ## Files with version
 
   * `CHANGELOG`
   * `mix.exs`
   * `installer/mix.exs`
-  * `installer/README.md`
   * `package.json`
   * `assets/package.json`
-  * `guides/introduction/installation.md`
diff --git a/guides/deployment/gigalixir.md b/guides/deployment/gigalixir.md
index 719d6b416..e56ce5e13 100644
--- a/guides/deployment/gigalixir.md
+++ b/guides/deployment/gigalixir.md
@@ -66,10 +66,12 @@ There are three different ways to deploy a Phoenix app on Gigalixir: with mix, w
 Let's create a Gigalixir application
 
 ```console
-$ gigalixir create
+$ gigalixir create -n "your_app_name"
 ```
 
-Verify it was created
+Note: the app name cannot be changed afterwards. A random name is used if you do not provide one.
+
+Verify the app was created
 
 ```console
 $ gigalixir apps
diff --git a/guides/ecto.md b/guides/ecto.md
index 8bf3fc136..0346f7fc4 100644
--- a/guides/ecto.md
+++ b/guides/ecto.md
@@ -97,7 +97,7 @@ hello_dev=# \d users
 Table "public.users"
 Column         |            Type             | Modifiers
 ---------------+-----------------------------+----------------------------------------------------
-id             | integer                     | not null default nextval('users_id_seq'::regclass)
+id             | bigint                      | not null default nextval('users_id_seq'::regclass)
 name           | character varying(255)      |
 email          | character varying(255)      |
 bio            | character varying(255)      |
diff --git a/guides/howto/custom_error_pages.md b/guides/howto/custom_error_pages.md
index 9a935b1aa..65771a750 100644
--- a/guides/howto/custom_error_pages.md
+++ b/guides/howto/custom_error_pages.md
@@ -1,10 +1,10 @@
 # Custom Error Pages
 
-Phoenix has a view called the `ErrorView` which lives in `lib/hello_web/views/error_view.ex`. The purpose of the `ErrorView` is to handle errors in a general way, from one centralized location.
+Phoenix has a view called the `ErrorView` which lives in `lib/hello_web/views/error_view.ex`. The purpose of `ErrorView` is to handle errors in a general way, from one centralized location.
 
 ## The ErrorView
 
-For new applications, the ErrorView looks like this:
+For new applications, the `ErrorView` view looks like this:
 
 ```elixir
 defmodule HelloWeb.ErrorView do
@@ -49,7 +49,7 @@ def template_not_found(template, _assigns) do
 end
 ```
 
-Great, so we have this `template_not_found/2` function that takes a template and an `assigns` map, which we ignore. The `template_not_found/2` is invoked whenever a Phoenix.View attempts to render a template but no template is found.
+Great, so we have this `template_not_found/2` function that takes a template and an `assigns` map, which we ignore. The `template_not_found/2` is invoked whenever a `Phoenix.View` view attempts to render a template but no template is found.
 
 In other words, to provide custom error pages, we could simply define a proper `render/2` function clause in `HelloWeb.ErrorView`.
 
@@ -61,7 +61,7 @@ end
 
 But we can do even better.
 
-Phoenix generates an `ErrorView` for us, but it doesn't give us a `lib/hello_web/templates/error` directory. Let's create one now. Inside our new directory, let's add a template, `404.html.eex` and give it some markup – a mixture of our application layout and a new `div` with our message to the user.
+Phoenix generates an `ErrorView` for us, but it doesn't give us a `lib/hello_web/templates/error` directory. Let's create one now. Inside our new directory, let's add a template named`404.html.eex` and give it some markup – a mixture of our application layout and a new `<div>` with our message to the user.
 
 ```html
 <!DOCTYPE html>
@@ -98,14 +98,14 @@ Phoenix generates an `ErrorView` for us, but it doesn't give us a `lib/hello_web
 
 Now when we go back to [http://localhost:4000/such/a/wrong/path](http://localhost:4000/such/a/wrong/path), we should see a much nicer error page. It is worth noting that we did not render our `404.html.eex` template through our application layout, even though we want our error page to have the look and feel of the rest of our site. This is to avoid circular errors. For example, what happens if our application failed due to an error in the layout? Attempting to render the layout again will just trigger another error. So ideally we want to minimize the amount of dependencies and logic in our error templates, sharing only what is necessary.
 
-## Custom Exceptions
+## Custom exceptions
 
-Elixir provides a macro called `defexception` for defining custom exceptions. Exceptions are represented as structs, and structs need to be defined inside of modules.
+Elixir provides a macro called `defexception/1` for defining custom exceptions. Exceptions are represented as structs, and structs need to be defined inside of modules.
 
-In order to create a custom exception, we need to define a new module. Conventionally this will have "Error" in the name. Inside of that module, we need to define a new exception with `defexception`.
+In order to create a custom exception, we need to define a new module. Conventionally this will have "Error" in the name. Inside of that module, we need to define a new exception with `defexception/1`, the file `lib/hello_web.ex` seems like a good place for it.
 
 ```elixir
-defmodule MyApp.SomethingNotFoundError do
+defmodule HelloWeb.SomethingNotFoundError do
   defexception [:message]
 end
 ```
@@ -113,15 +113,15 @@ end
 You can raise your new exception like this:
 
 ```elixir
-raise MyApp.SomethingNotFoundError, "oops"
+raise HelloWeb.SomethingNotFoundError, "oops"
 ```
 
 By default, Plug and Phoenix will treat all exceptions as 500 errors. However, Plug provides a protocol called `Plug.Exception` where we are able to customize the status and add actions that exception structs can return on the debug error page.
 
-If we wanted to supply a status of 404 for an `MyApp.SomethingNotFoundError`, we could do it by defining an implementation for the `Plug.Exception` protocol like this:
+If we wanted to supply a status of 404 for an `HelloWeb.SomethingNotFoundError` error, we could do it by defining an implementation for the `Plug.Exception` protocol like this, in `lib/hello_web.ex`:
 
 ```elixir
-defimpl Plug.Exception, for: MyApp.SomethingNotFoundError do
+defimpl Plug.Exception, for: HelloWeb.SomethingNotFoundError do
   def status(_exception), do: 404
   def actions(_exception), do: []
 end
@@ -130,27 +130,40 @@ end
 Alternatively, you could define a `plug_status` field directly in the exception struct:
 
 ```elixir
-defmodule MyApp.SomethingNotFoundError do
+defmodule HelloWeb.SomethingNotFoundError do
   defexception [:message, plug_status: 404]
 end
 ```
 
-However, implementing the `Plug.Exception` protocol by hand can be convenient in certain occasions, such as when providing Actionable Errors.
+However, implementing the `Plug.Exception` protocol by hand can be convenient in certain occasions, such as when providing actionable errors.
 
-## Actionable Errors
+## Actionable errors
 
 Exception actions are functions that can be triggered by the error page, and they're basically a list of maps defining a `label` and a `handler` to be executed.
 
-They are rendered in the error page as a collection of buttons and follow the format of: `[%{label: String.t(), handler: {module(), function :: atom(), args :: []}}]`.
+They are rendered in the error page as a collection of buttons and follow the format of:
+```elixir
+[
+  %{
+    label: String.t(),
+    handler: {module(), function :: atom(), args :: []}
+  }
+]
+```
 
-If we wanted to return some actions for an `MyApp.SomethingNotFoundError` we would implement `Plug.Exception` like this:
+If we wanted to return some actions for an `HelloWeb.SomethingNotFoundError` we would implement `Plug.Exception` like this:
 
 ```elixir
-defimpl Plug.Exception, for: MyApp.SomethingNotFoundError do
+defimpl Plug.Exception, for: HelloWeb.SomethingNotFoundError do
   def status(_exception), do: 404
-  def actions(_exception), do: [%{
-      label: "Run seeds",
-      handler: {Code, :eval_file, "priv/repo/seeds.exs"}
-    }]
+
+  def actions(_exception) do
+    [
+      %{
+        label: "Run seeds",
+        handler: {Code, :eval_file, "priv/repo/seeds.exs"}
+      }
+    ]
+  end
 end
 ```
diff --git a/guides/introduction/installation.md b/guides/introduction/installation.md
index 1853b0072..5333ccefe 100644
--- a/guides/introduction/installation.md
+++ b/guides/introduction/installation.md
@@ -43,7 +43,7 @@ Elixir 1.6.3
 Once we have Elixir and Erlang, we are ready to install the Phoenix application generator:
 
 ```console
-$ mix archive.install hex phx_new 1.5.0
+$ mix archive.install hex phx_new
 ```
 
 The `phx.new` generator is now available to generate new applications in the next guide, called [Up and Running](up_and_running.html). The flags mentioned below are command line options to the generator; see all available options by calling `mix help phx.new`.
diff --git a/guides/plug.md b/guides/plug.md
index abcab2003..0403c2360 100644
--- a/guides/plug.md
+++ b/guides/plug.md
@@ -113,7 +113,7 @@ defmodule HelloWeb.Router do
   ...
 ```
 
-In the [`init/1`] callback, we pass a default locale to use if none is present in the params. We also use pattern matching to define multiple [`call/2`] function heads to validate the locale in the params, and fall back to `"en"` if there is no match.
+In the [`init/1`] callback, we pass a default locale to use if none is present in the params. We also use pattern matching to define multiple [`call/2`] function heads to validate the locale in the params, and fall back to `"en"` if there is no match. The [`assign/3`] is a part of the `Plug.Conn` module and it's how we store values in the `conn` data structure.
 
 To see the assign in action, go to the layout in `lib/hello_web/templates/layout/app.html.eex` and add the following close to the main container:
 
diff --git a/guides/views.md b/guides/views.md
index 9268722c7..e74b045de 100644
--- a/guides/views.md
+++ b/guides/views.md
@@ -2,17 +2,17 @@
 
 > **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).
 
-> **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html).
+> **Requirement**: This guide expects that you have gone through the [request life-cycle guide](request_lifecycle.html).
 
-A Phoenix view's main job is to render the body of the response which gets sent back to browsers and API clients. Most of the time, we use templates to build these responses, but we can also craft them by hand. We will learn how.
+The main job of a Phoenix view is to render the body of the response which gets sent back to browsers and to API clients. Most of the time, we use templates to build these responses, but we can also craft them by hand. We will learn how.
 
-## Rendering Templates
+## Rendering templates
 
-Phoenix assumes a strong naming convention from controllers to views to the templates they render. The `PageController` requires a `PageView` to render templates in the `lib/hello_web/templates/page` directory. While all of these can be customizable (see `Phoenix.View` and `Phoenix.Template` for more information), we recommend users stick with Phoenix' convention.
+Phoenix assumes a strong naming convention from controllers to views to the templates they render. The `PageController` controller requires a `PageView` view to render templates in the `lib/hello_web/templates/page/` directory. While all of these can be customizable (see `Phoenix.View` and `Phoenix.Template` for more information), we recommend users stick with Phoenix' convention.
 
-A newly generated Phoenix application has three view modules - `ErrorView`, `LayoutView`, and `PageView` -  which are all in the, `lib/hello_web/views` directory.
+A newly generated Phoenix application has three view modules - `ErrorView`, `LayoutView`, and `PageView` -  which are all in the `lib/hello_web/views/` directory.
 
-Let's take a quick look at the `LayoutView`.
+Let's take a quick look at the `LayoutView` view.
 
 ```elixir
 defmodule HelloWeb.LayoutView do
@@ -20,7 +20,7 @@ defmodule HelloWeb.LayoutView do
 end
 ```
 
-That's simple enough. There's only one line, `use HelloWeb, :view`. This line calls the `view/0` function we just saw above. Besides allowing us to change our template root, `view/0` exercises the `__using__` macro in the `Phoenix.View` module. It also handles any module imports or aliases our application's view modules might need.
+That's simple enough. There's only one line, `use HelloWeb, :view`. This line calls the `view/0` function defined in `HelloWeb` which sets up the basic imports and configuration for our views and templates.
 
 All of the imports and aliases we make in our view will also be available in our templates. That's because templates are effectively compiled into functions inside their respective views. For example, if you define a function in your view, you will be able to invoke it directly from the template. Let's see this in practice.
 
@@ -50,7 +50,7 @@ end
 
 When we reload our home page, we should see our new title. Since templates are compiled inside the view, we could invoke the view function simply as `title()`, otherwise we would have to type `HelloWeb.LayoutView.title()`.
 
-As you may recall, Elixir templates use Embedded Elixir, known as `EEx`. We use `<%= expression %>` to execute Elixir expressions. The result of the expression is interpolated into the template. You can use pretty much any Elixir expression. For example, in order to have conditionals:
+As you may recall, Elixir templates use `EEx`, which stands for Embedded Elixir. We use `<%= expression %>` to execute Elixir expressions and interpolate their results into the template. You can use pretty much any Elixir expression. For example, in order to have conditionals:
 
 ```html
 <%= if some_condition? do %>
@@ -81,7 +81,7 @@ At the end of the day, our templates are always compiled into Elixir code. Let's
 
 ### Understanding template compilation
 
-When a template is compiled into a view, it is simply compiled as a `render` function that expects two arguments: the template name and the assigns.
+When a template is compiled into a view, it is simply compiled as a [`render/2`] function that expects two arguments: the template name and the assigns.
 
 You can prove this by temporarily adding this function clause to your `PageView` module in `lib/hello_web/views/page_view.ex`.
 
@@ -95,15 +95,15 @@ defmodule HelloWeb.PageView do
 end
 ```
 
-Now if you fire up the server with `mix phx.server` and visit `http://localhost:4000`, you should see the following text below your layout header instead of the main template page:
+Now if you fire up the server with `mix phx.server` and visit [`http://localhost:4000`](http://localhost:4000), you should see the following text below your layout header instead of the main template page:
 
 ```console
 rendering with assigns [:conn]
 ```
 
-By defining our own clause in `render`, it takes higher priority than the template, but the template is still there, which you can verify by simply removing the newly added clause.
+By defining our own clause in [`render/2`], it takes higher priority than the template, but the template is still there, which you can verify by simply removing the newly added clause.
 
-Pretty neat, right? At compile-time, Phoenix precompiles all `*.html.eex` templates and turns them into `render/2` function clauses on their respective view modules. At runtime, all templates are already loaded in memory. There's no disk reads, complex file caching, or template engine computation involved.
+Pretty neat, right? At compile-time, Phoenix precompiles all `*.html.eex` templates and turns them into [`render/2`] function clauses on their respective view modules. At runtime, all templates are already loaded in memory. There's no disk reads, complex file caching, or template engine computation involved.
 
 ### Manually rendering templates
 
@@ -115,14 +115,14 @@ Let's create a new template to play around with, `lib/hello_web/templates/page/t
 This is the message: <%= @message %>
 ```
 
-This doesn't correspond to any action in our controller, which is fine. We'll exercise it in an `iex` session. At the root of our project, we can run `iex -S mix`, and then explicitly render our template.
+This doesn't correspond to any action in our controller, which is fine. We'll exercise it in an `IEx` session. At the root of our project, we can run `iex -S mix`, and then explicitly render our template.
 
 ```elixir
 iex(1)> Phoenix.View.render(HelloWeb.PageView, "test.html", message: "Hello from IEx!")
 {:safe, ["This is the message: ", "Hello from IEx!"]}
 ```
 
-As we can see, we're calling `render/3` with the individual view responsible for our test template, the name of our test template, and a set of assigns we might have wanted to pass in. The return value is a tuple beginning with the atom `:safe` and the resultant io list of the interpolated template. "Safe" here means that Phoenix has escaped the contents of our rendered template to avoid XSS injection attacks.
+As we can see, we're calling [`render/3`] with the individual view responsible for our test template, the name of our test template, and a set of assigns we might have wanted to pass in. The return value is a tuple beginning with the atom `:safe` and the resultant IO list of the interpolated template. "Safe" here means that Phoenix has escaped the contents of our rendered template to avoid XSS injection attacks.
 
 Let's test out the HTML escaping, just for fun:
 
@@ -131,7 +131,7 @@ iex(2)> Phoenix.View.render(HelloWeb.PageView, "test.html", message: "<script>ba
 {:safe, ["This is the message: ", "&lt;script&gt;badThings();&lt;/script&gt;"]}
 ```
 
-If we need only the rendered string, without the whole tuple, we can use `render_to_string/3`.
+If we need only the rendered string, without the whole tuple, we can use [`render_to_string/3`].
 
 ```elixir
 iex(5)> Phoenix.View.render_to_string(HelloWeb.PageView, "test.html", message: "Hello from IEx!")
@@ -142,13 +142,13 @@ iex(5)> Phoenix.View.render_to_string(HelloWeb.PageView, "test.html", message: "
 
 Now that we have acquainted ourselves with `Phoenix.View.render/3`, we are ready to share views and templates from inside other views and templates.
 
-For example, if you want to render the "test.html" template from inside our layout, you can invoke `render/3` directly from the layout:
+For example, if you want to render the `test.html` template from inside our layout, you can invoke [`render/3`] directly from the layout `lib/hello_web/templates/layout/app.html.eex`:
 
 ```html
 <%= Phoenix.View.render(HelloWeb.PageView, "test.html", message: "Hello from layout!") %>
 ```
 
-If you visit the Welcome page, you should see the message from the layout.
+If you visit the [welcome page], you should see the message from the layout.
 
 Since `Phoenix.View` is automatically imported into our templates, we could even skip the `Phoenix.View` module name and simply invoke `render(...)` directly:
 
@@ -176,11 +176,11 @@ In other words, the inner template is placed in the `@inner_content` assign.
 
 ## Rendering JSON
 
-The view's job is not only to render HTML templates. Views are about data presentation. Given a bag of data, the view's purpose is to present that in a meaningful way given some format, be it HTML, JSON, CSV, or others. Many web apps today return JSON to remote clients, and Phoenix Views are *great* for JSON rendering.
+The view's job is not only to render HTML templates. Views are about data presentation. Given a bag of data, the view's purpose is to present that in a meaningful way given some format, be it HTML, JSON, CSV, or others. Many web apps today return JSON to remote clients, and Phoenix views are *great* for JSON rendering.
 
-Phoenix uses [Jason](https://github.com/michalmuskala/jason) to encode JSON, so all we need to do in our views is format the data we'd like to respond with as a list or a map, and Phoenix will do the rest.
+Phoenix uses the [Jason](https://github.com/michalmuskala/jason) library to encode JSON, so all we need to do in our views is to format the data we would like to respond with as a list or a map, and Phoenix will do the rest.
 
-While it is possible to respond with JSON back directly from the controller and skip the view, Phoenix Views provide a much more structured approach for doing  so. Let's take our `PageController`, and see what it might look like when we respond with some static page maps as JSON, instead of HTML.
+While it is possible to respond with JSON back directly from the controller and skip the view, Phoenix views provide a much more structured approach for doing  so. Let's take our `PageController`, and see what it may look like when we respond with some static page maps as JSON, instead of HTML.
 
 ```elixir
 defmodule HelloWeb.PageController do
@@ -200,7 +200,7 @@ defmodule HelloWeb.PageController do
 end
 ```
 
-Here, we have our `show/2` and `index/2` actions returning static page data. Instead of passing in `"show.html"` to `render/3` as the template name, we pass `"show.json"`. This way, we can have views that are responsible for rendering HTML as well as JSON by pattern matching on different file types.
+Here, we have our `show/2` and `index/2` actions returning static page data. Instead of passing in `"show.html"` to [`render/3`] as the template name, we pass `"show.json"`. This way, we can have views that are responsible for rendering HTML as well as JSON by pattern matching on different file types.
 
 ```elixir
 defmodule HelloWeb.PageView do
@@ -220,7 +220,7 @@ defmodule HelloWeb.PageView do
 end
 ```
 
-In the view we see our `render/2` function pattern matching on `"index.json"`, `"show.json"`, and `"page.json"`. The `"index.json"` and `"show.json"` are the ones requested directly from the controller. They also match on the assigns sent by the controller. `"index.json"` will respond with JSON like this:
+In the view we see our [`render/2`] function pattern matching on `"index.json"`, `"show.json"`, and `"page.json"`. The `"index.json"` and `"show.json"` are the ones requested directly from the controller. They also match on the assigns sent by the controller. `"index.json"` will respond with JSON like this:
 
 ```javascript
 {
@@ -235,7 +235,7 @@ In the view we see our `render/2` function pattern matching on `"index.json"`, `
 }
 ```
 
-And the `render/2` matching `"show.json"`:
+And the [`render/2`] matching `"show.json"`:
 
 ```javascript
 {
@@ -247,9 +247,9 @@ And the `render/2` matching `"show.json"`:
 
 This works because both `index.json` and `show.json` build themselves on top of an internal `page.json` clause.
 
-The `render_many/3` function takes the data we want to respond with (`pages`), a view, and a string to pattern match on the `render/2` function defined on view. It will map over each item in `pages` and call `PageView.render("page.json", %{page: page})`. `render_one/3` follows the same signature, ultimately using the `render/2` matching `page.json` to specify what each `page` looks like.
+The [`render_many/3`] function takes the data we want to respond with (`pages`), a view, and a string to pattern match on the [`render/2`] function defined on view. It will map over each item in `pages` and call `PageView.render("page.json", %{page: page})`. [`render_one/3`] follows the same signature, ultimately using the [`render/2`] matching `page.json` to specify what each `page` looks like.
 
-It's useful to build our views like this so that they are composable. Imagine a situation where our `Page` has a `has_many` relationship with `Author`, and depending on the request, we may want to send back `author` data with the `page`. We can easily accomplish this with a new `render/2`:
+It's useful to build our views like this so that they are composable. Imagine a situation where our `Page` has a `has_many` relationship (#NOTE: We haven't talked about has_many relationship yet#) with `Author`, and depending on the request, we may want to send back `author` data with the `page`. We can easily accomplish this with a new [`render/2`]:
 
 ```elixir
 defmodule HelloWeb.PageView do
@@ -267,7 +267,7 @@ defmodule HelloWeb.PageView do
 end
 ```
 
-The name used in assigns is determined from the view. For example the `PageView` will use `%{page: page}` and the `AuthorView` will use `%{author: author}`. This can be overridden with the `as` option. Let's assume that the author view uses `%{writer: writer}` instead of `%{author: author}`:
+The name used in assigns is determined from the view. For example the `PageView` view will use `%{page: page}` and the `AuthorView` view will use `%{author: author}`. This can be overridden with the `as` option. Let's assume that the author view uses `%{writer: writer}` instead of `%{author: author}`:
 
 ```elixir
 def render("page_with_authors.json", %{page: page}) do
@@ -278,4 +278,12 @@ end
 
 ## Error pages
 
-Phoenix has a view called the `ErrorView` which lives in `lib/hello_web/views/error_view.ex`. The purpose of the `ErrorView` is to handle errors in a general way, from one centralized location.  Similar to the views we built in this guide, error views can return both HTML and JSON responses. See the [Custom Error Pages How-To](custom_error_pages.html) for more information.
+Phoenix has a view called the `ErrorView` which lives in `lib/hello_web/views/error_view.ex`. The purpose of `ErrorView` is to handle errors in a general way, from one centralized location.  Similar to the views we built in this guide, error views can return both HTML and JSON responses. See the [custom error pages how-to](custom_error_pages.html) for more information.
+
+
+[welcome page]: http://localhost:4000
+[`render/2`]: Phoenix.View.render/3
+[`render/3`]: Phoenix.View.render/3
+[`render_many/3`]: Phoenix.View.render_many/3
+[`render_one/3`]: Phoenix.View.render_one/3
+[`render_to_string/3`]: Phoenix.View.render_to_string/3
diff --git a/installer/README.md b/installer/README.md
index f155fda50..0f3f53819 100644
--- a/installer/README.md
+++ b/installer/README.md
@@ -2,9 +2,9 @@
 
 Provides `phx.new` installer as an archive.
 
-To install from hex, run:
+To install from Hex, run:
 
-    $ mix archive.install hex phx_new 1.5.0
+    $ mix archive.install hex phx_new
 
 To build and install it locally,
 ensure any previous archive versions are removed:
diff --git a/installer/lib/phx_new/generator.ex b/installer/lib/phx_new/generator.ex
index 88c0a4ab2..420d0a822 100644
--- a/installer/lib/phx_new/generator.ex
+++ b/installer/lib/phx_new/generator.ex
@@ -200,7 +200,7 @@ defmodule Phx.New.Generator do
       endpoint_module: inspect(Module.concat(project.web_namespace, Endpoint)),
       web_namespace: inspect(project.web_namespace),
       phoenix_github_version_tag: "v#{version.major}.#{version.minor}",
-      phoenix_dep: phoenix_dep(phoenix_path),
+      phoenix_dep: phoenix_dep(phoenix_path, version),
       phoenix_path: phoenix_path,
       phoenix_webpack_path: phoenix_webpack_path(project, dev),
       phoenix_html_webpack_path: phoenix_html_webpack_path(project),
@@ -240,9 +240,7 @@ defmodule Phx.New.Generator do
 
     config_inject(project_path, "config/dev.exs", """
     # Configure your database
-    config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{
-      kw_to_config(adapter_config[:dev])
-    },
+    config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{kw_to_config(adapter_config[:dev])},
       pool_size: 10
     """)
 
@@ -252,9 +250,7 @@ defmodule Phx.New.Generator do
     # The MIX_TEST_PARTITION environment variable can be used
     # to provide built-in test partitioning in CI environment.
     # Run `mix help test` for more information.
-    config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{
-      kw_to_config(adapter_config[:test])
-    }
+    config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{kw_to_config(adapter_config[:test])}
     """)
 
     prod_only_config_inject(project_path, "config/runtime.exs", """
@@ -382,10 +378,14 @@ defmodule Phx.New.Generator do
   defp phoenix_live_view_webpack_path(%Project{in_umbrella?: false}),
     do: "../deps/phoenix_live_view"
 
-  defp phoenix_dep("deps/phoenix"), do: ~s[{:phoenix, "~> #{@phoenix_version}"}]
+  defp phoenix_dep("deps/phoenix", %{pre: ["dev"]}),
+    do: ~s[{:phoenix, github: "phoenixframework/phoenix", override: true}]
 
-  # defp phoenix_dep("deps/phoenix"), do: ~s[{:phoenix, github: "phoenixframework/phoenix", override: true}]
-  defp phoenix_dep(path), do: ~s[{:phoenix, path: #{inspect(path)}, override: true}]
+  defp phoenix_dep("deps/phoenix", version),
+    do: ~s[{:phoenix, "~> #{version}"}]
+
+  defp phoenix_dep(path, _version),
+    do: ~s[{:phoenix, path: #{inspect(path)}, override: true}]
 
   defp phoenix_static_path("deps/phoenix"), do: "deps/phoenix"
   defp phoenix_static_path(path), do: Path.join("..", path)
diff --git a/installer/mix.exs b/installer/mix.exs
index 2a0d1e7b3..ce748dcb4 100644
--- a/installer/mix.exs
+++ b/installer/mix.exs
@@ -1,4 +1,4 @@
-for path <- :code.get_path,
+for path <- :code.get_path(),
     Regex.match?(~r/phx_new\-\d+\.\d+\.\d\/ebin$/, List.to_string(path)) do
   Code.delete_path(path)
 end
@@ -6,7 +6,7 @@ end
 defmodule Phx.New.MixProject do
   use Mix.Project
 
-  @version "1.5.0"
+  @version "1.6.0-dev"
   @github_path "phoenixframework/phoenix"
   @url "https://github.com/#{@github_path}"
 
diff --git a/lib/mix/tasks/phx.gen.context.ex b/lib/mix/tasks/phx.gen.context.ex
index c4b19f750..839c51dba 100644
--- a/lib/mix/tasks/phx.gen.context.ex
+++ b/lib/mix/tasks/phx.gen.context.ex
@@ -338,8 +338,8 @@ defmodule Mix.Tasks.Phx.Gen.Context do
       Mix.shell().info("""
       You are generating into an existing context.
 
-      The #{inspect context.module} context currently has #{function_count} functions and \
-      #{file_count} files in its directory.
+      The #{inspect(context.module)} context currently has #{singularize(function_count, "functions")} and \
+      #{singularize(file_count, "files")} in its directory.
 
         * It's OK to have multiple resources in the same context as \
       long as they are closely related. But if a context grows too \
@@ -356,4 +356,7 @@ defmodule Mix.Tasks.Phx.Gen.Context do
       Mix.shell().yes?("Would you like to proceed?")
     end)
   end
+
+  defp singularize(1, plural), do: "1 " <> String.trim_trailing(plural, "s")
+  defp singularize(amount, plural), do: "#{amount} #{plural}"
 end
diff --git a/lib/phoenix/config.ex b/lib/phoenix/config.ex
index 685755b82..6927f91bb 100644
--- a/lib/phoenix/config.ex
+++ b/lib/phoenix/config.ex
@@ -46,7 +46,18 @@ defmodule Phoenix.Config do
   """
   @spec cache(module, term, (module -> {:cache | :nocache, term})) :: term
   def cache(module, key, fun) do
-    case :ets.lookup(module, key) do
+    try do
+      :ets.lookup(module, key)
+    rescue
+      e ->
+        case :ets.info(module) do
+          :undefined ->
+            raise "could not find ets table for endpoint #{inspect(module)}. Make sure your endpoint is started and note you cannot access endpoint functions at compile-time."
+
+          _ ->
+            reraise e, __STACKTRACE__
+        end
+    else
       [{^key, :cache, val}] ->
         val
 
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index ec1158f63..49ee33ffc 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -218,7 +218,7 @@ defmodule Phoenix.Endpoint do
       `c:broadcast_from/4`, `c:broadcast_from!/4`, `c:local_broadcast/3`,
       and `c:local_broadcast_from/4`
 
-    * for configuration: `c:start_link/0`, `c:config/2`, and `c:config_change/2`
+    * for configuration: `c:start_link/1`, `c:config/2`, and `c:config_change/2`
 
     * as required by the `Plug` behaviour: `c:Plug.init/1` and `c:Plug.call/2`
 
diff --git a/lib/phoenix/router.ex b/lib/phoenix/router.ex
index 2ec096cfb..2402c49b5 100644
--- a/lib/phoenix/router.ex
+++ b/lib/phoenix/router.ex
@@ -346,6 +346,9 @@ defmodule Phoenix.Router do
 
     case pipeline.(conn) do
       %Plug.Conn{halted: true} = halted_conn ->
+        measurements = %{duration: System.monotonic_time() - start}
+        metadata = %{metadata | conn: halted_conn}
+        :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
         halted_conn
       %Plug.Conn{} = piped_conn ->
         try do
@@ -408,12 +411,6 @@ defmodule Phoenix.Router do
     end
   end
 
-  @anno (if :erlang.system_info(:otp_release) >= '19' do
-    [generated: true]
-  else
-    [line: -1]
-  end)
-
   @doc false
   defmacro __before_compile__(env) do
     routes = env.module |> Module.get_attribute(:phoenix_routes) |> Enum.reverse
@@ -431,9 +428,8 @@ defmodule Phoenix.Router do
         end
       end
 
-    # @anno is used here to avoid warnings if forwarding to root path
     match_404 =
-      quote @anno do
+      quote [generated: true] do
         def __match_route__(_method, _path_info, _host) do
           :error
         end
@@ -628,6 +624,14 @@ defmodule Phoenix.Router do
   are appended to the ones previously given.
   """
   defmacro pipeline(plug, do: block) do
+    with true <- is_atom(plug),
+         imports = __CALLER__.macros ++ __CALLER__.functions,
+         {mod, _} <- Enum.find(imports, fn {_, imports} -> {plug, 2} in imports end) do
+      raise ArgumentError,
+            "cannot define pipeline named #{inspect(plug)} " <>
+              "because there is an import from #{inspect(mod)} with the same name"
+    end
+
     block =
       quote do
         plug = unquote(plug)
diff --git a/lib/phoenix/router/helpers.ex b/lib/phoenix/router/helpers.ex
index d0cc374a0..1fdd30d02 100644
--- a/lib/phoenix/router/helpers.ex
+++ b/lib/phoenix/router/helpers.ex
@@ -5,12 +5,6 @@ defmodule Phoenix.Router.Helpers do
   alias Phoenix.Router.Route
   alias Plug.Conn
 
-  @anno (if :erlang.system_info(:otp_release) >= '19' do
-    [generated: true, unquote: false]
-  else
-    [line: -1, unquote: false]
-  end)
-
   @doc """
   Callback invoked by the url generated in each helper module.
   """
@@ -119,7 +113,7 @@ defmodule Phoenix.Router.Helpers do
 
     catch_all = Enum.map(groups, &defhelper_catch_all/1)
 
-    defhelper = quote @anno do
+    defhelper = quote [generated: true, unquote: false] do
       defhelper = fn helper, vars, opts, bins, segs, trailing_slash? ->
         def unquote(:"#{helper}_path")(conn_or_endpoint, unquote(Macro.escape(opts)), unquote_splicing(vars)) do
           unquote(:"#{helper}_path")(conn_or_endpoint, unquote(Macro.escape(opts)), unquote_splicing(vars), [])
@@ -142,7 +136,7 @@ defmodule Phoenix.Router.Helpers do
       end
     end
 
-    defcatch_all = quote @anno do
+    defcatch_all = quote [generated: true, unquote: false] do
       defcatch_all = fn helper, lengths, routes ->
 	      for length <- lengths do
 	        binding = List.duplicate({:_, [], nil}, length)
diff --git a/priv/templates/phx.gen.html/show.html.eex b/priv/templates/phx.gen.html/show.html.eex
index c19a2c955..469fc4a13 100644
--- a/priv/templates/phx.gen.html/show.html.eex
+++ b/priv/templates/phx.gen.html/show.html.eex
@@ -9,5 +9,5 @@
 <% end %>
 </ul>
 
-<span><%%= link "Edit", to: Routes.<%= schema.route_helper %>_path(@conn, :edit, @<%= schema.singular %>) %></span>
+<span><%%= link "Edit", to: Routes.<%= schema.route_helper %>_path(@conn, :edit, @<%= schema.singular %>) %></span> |
 <span><%%= link "Back", to: Routes.<%= schema.route_helper %>_path(@conn, :index) %></span>
diff --git a/priv/templates/phx.gen.live/show.html.leex b/priv/templates/phx.gen.live/show.html.leex
index 236f80e45..9de080b77 100644
--- a/priv/templates/phx.gen.live/show.html.leex
+++ b/priv/templates/phx.gen.live/show.html.leex
@@ -18,5 +18,5 @@
 <% end %>
 </ul>
 
-<span><%%= live_patch "Edit", to: Routes.<%= schema.route_helper %>_show_path(@socket, :edit, @<%= schema.singular %>), class: "button" %></span>
+<span><%%= live_patch "Edit", to: Routes.<%= schema.route_helper %>_show_path(@socket, :edit, @<%= schema.singular %>), class: "button" %></span> |
 <span><%%= live_redirect "Back", to: Routes.<%= schema.route_helper %>_index_path(@socket, :index) %></span>


Test patch:
diff --git a/test/mix/tasks/phx.gen.cert_test.exs b/test/mix/tasks/phx.gen.cert_test.exs
index 188b2aa63..7e8c66090 100644
--- a/test/mix/tasks/phx.gen.cert_test.exs
+++ b/test/mix/tasks/phx.gen.cert_test.exs
@@ -3,67 +3,62 @@ Code.require_file("../../../installer/test/mix_helper.exs", __DIR__)
 defmodule Mix.Tasks.Phx.CertTest do
   use ExUnit.Case
 
-  @otp_release :erlang.system_info(:otp_release) |> List.to_integer()
+  import MixHelper
+  alias Mix.Tasks.Phx.Gen
 
-  if @otp_release >= 20 do
-    import MixHelper
-    alias Mix.Tasks.Phx.Gen
+  @timeout 5_000
 
-    @timeout 5_000
+  test "write certificate and key files" do
+    in_tmp("mix_phx_gen_cert", fn ->
+      Gen.Cert.run([])
 
-    # RSA key generation requires OTP 20 or later
-    test "write certificate and key files" do
-      in_tmp("mix_phx_gen_cert", fn ->
-        Gen.Cert.run([])
+      assert_received {:mix_shell, :info, ["* creating priv/cert/selfsigned_key.pem"]}
+      assert_received {:mix_shell, :info, ["* creating priv/cert/selfsigned.pem"]}
 
-        assert_received {:mix_shell, :info, ["* creating priv/cert/selfsigned_key.pem"]}
-        assert_received {:mix_shell, :info, ["* creating priv/cert/selfsigned.pem"]}
-
-        assert_file("priv/cert/selfsigned_key.pem", "-----BEGIN RSA PRIVATE KEY-----")
-        assert_file("priv/cert/selfsigned.pem", "-----BEGIN CERTIFICATE-----")
-      end)
-    end
+      assert_file("priv/cert/selfsigned_key.pem", "-----BEGIN RSA PRIVATE KEY-----")
+      assert_file("priv/cert/selfsigned.pem", "-----BEGIN CERTIFICATE-----")
+    end)
+  end
 
-    test "write certificate and key with custom filename" do
-      in_tmp("mix_phx_gen_cert", fn ->
-        Gen.Cert.run(["-o", "priv/cert/localhost"])
+  test "write certificate and key with custom filename" do
+    in_tmp("mix_phx_gen_cert", fn ->
+      Gen.Cert.run(["-o", "priv/cert/localhost"])
 
-        assert_received {:mix_shell, :info, ["* creating priv/cert/localhost_key.pem"]}
-        assert_received {:mix_shell, :info, ["* creating priv/cert/localhost.pem"]}
+      assert_received {:mix_shell, :info, ["* creating priv/cert/localhost_key.pem"]}
+      assert_received {:mix_shell, :info, ["* creating priv/cert/localhost.pem"]}
 
-        assert_file("priv/cert/localhost_key.pem", "-----BEGIN RSA PRIVATE KEY-----")
-        assert_file("priv/cert/localhost.pem", "-----BEGIN CERTIFICATE-----")
-      end)
-    end
-
-    test "TLS connection with generated certificate and key" do
-      Application.ensure_all_started(:ssl)
+      assert_file("priv/cert/localhost_key.pem", "-----BEGIN RSA PRIVATE KEY-----")
+      assert_file("priv/cert/localhost.pem", "-----BEGIN CERTIFICATE-----")
+    end)
+  end
 
-      in_tmp("mix_phx_gen_cert", fn ->
-        Gen.Cert.run([])
+  test "TLS connection with generated certificate and key" do
+    Application.ensure_all_started(:ssl)
 
-        assert {:ok, server} =
-                 :ssl.listen(
-                   0,
-                   certfile: "priv/cert/selfsigned.pem",
-                   keyfile: "priv/cert/selfsigned_key.pem"
-                 )
+    in_tmp("mix_phx_gen_cert", fn ->
+      Gen.Cert.run([])
 
-        {:ok, {_, port}} = :ssl.sockname(server)
+      assert {:ok, server} =
+               :ssl.listen(
+                 0,
+                 certfile: "priv/cert/selfsigned.pem",
+                 keyfile: "priv/cert/selfsigned_key.pem"
+               )
 
-        spawn_link(fn ->
-          with {:ok, conn} <- :ssl.transport_accept(server, @timeout),
-               :ok <- :ssl.handshake(conn, @timeout) do
-            :ssl.close(conn)
-          end
-        end)
+      {:ok, {_, port}} = :ssl.sockname(server)
 
-        # We don't actually verify the server cert contents, we just check that
-        # the client and server are able to complete the TLS handshake
-        assert {:ok, client} = :ssl.connect('localhost', port, [], @timeout)
-        :ssl.close(client)
-        :ssl.close(server)
+      spawn_link(fn ->
+        with {:ok, conn} <- :ssl.transport_accept(server, @timeout),
+             :ok <- :ssl.handshake(conn, @timeout) do
+          :ssl.close(conn)
+        end
       end)
-    end
+
+      # We don't actually verify the server cert contents, we just check that
+      # the client and server are able to complete the TLS handshake
+      assert {:ok, client} = :ssl.connect('localhost', port, [], @timeout)
+      :ssl.close(client)
+      :ssl.close(server)
+    end)
   end
 end
diff --git a/test/mix/tasks/phx.gen.context_test.exs b/test/mix/tasks/phx.gen.context_test.exs
index 9158dba2f..6424f3d54 100644
--- a/test/mix/tasks/phx.gen.context_test.exs
+++ b/test/mix/tasks/phx.gen.context_test.exs
@@ -175,7 +175,7 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
       Gen.Context.run(~w(Blog Comment comments title:string))
 
       assert_received {:mix_shell, :info, ["You are generating into an existing context" <> notice]}
-      assert notice =~ "Phoenix.Blog context currently has 6 functions and 1 files in its directory"
+      assert notice =~ "Phoenix.Blog context currently has 6 functions and 1 file in its directory"
       assert_received {:mix_shell, :yes?, ["Would you like to proceed?"]}
 
       assert_file "lib/phoenix/blog/comment.ex", fn file ->
diff --git a/test/phoenix/config_test.exs b/test/phoenix/config_test.exs
index ad69c5d5a..6c0156d49 100644
--- a/test/phoenix/config_test.exs
+++ b/test/phoenix/config_test.exs
@@ -22,6 +22,12 @@ defmodule Phoenix.ConfigTest do
     assert :ets.lookup(meta.test, :custom)  == [custom: true]
   end
 
+  test "raises with warning about compile time when table not started" do
+    assert_raise RuntimeError,
+                 "could not find ets table for endpoint Fooz. Make sure your endpoint is started and note you cannot access endpoint functions at compile-time.",
+                 fn -> cache(Fooz, :foo, fn _ -> {:nocache, :bar} end) end
+  end
+
   test "can change configuration", meta do
     {:ok, pid} = start_link({meta.test, @all, @defaults, []})
     ref = Process.monitor(pid)
diff --git a/test/phoenix/router/pipeline_test.exs b/test/phoenix/router/pipeline_test.exs
index 053838686..2838c2b2c 100644
--- a/test/phoenix/router/pipeline_test.exs
+++ b/test/phoenix/router/pipeline_test.exs
@@ -158,4 +158,19 @@ defmodule Phoenix.Router.PipelineTest do
       end
     end
   end
+
+  test "pipeline raises on conflict" do
+    assert_raise ArgumentError, ~r{there is an import from Kernel with the same nam}, fn ->
+      defmodule ConflictingPipeline do
+        use Phoenix.Router, otp_app: :phoenix
+        pipeline :raise do
+          plug Plug.Head
+        end
+        scope "/" do
+          pipe_through [:raise]
+          get "/", UnknownController, :index
+        end
+      end
+    end
+  end
 end
diff --git a/test/phoenix/router/routing_test.exs b/test/phoenix/router/routing_test.exs
index 888a50571..8dc01c095 100644
--- a/test/phoenix/router/routing_test.exs
+++ b/test/phoenix/router/routing_test.exs
@@ -23,6 +23,11 @@ defmodule Phoenix.Router.RoutingTest do
     def any(conn, _params), do: text(conn, "users any")
     def raise(_conn, _params), do: raise("boom")
     def exit(_conn, _params), do: exit(:boom)
+    def halt(conn, _params) do
+      conn
+      |> send_resp(401, "Unauthorized")
+      |> halt()
+    end
   end
 
   defmodule LogLevel do
@@ -44,6 +49,7 @@ defmodule Phoenix.Router.RoutingTest do
     get "/backups/*path", UserController, :image
     get "/static/images/icons/*image", UserController, :image
     get "/exit", UserController, :exit
+    get "/halt-controller", UserController, :halt
 
     trace("/trace", UserController, :trace)
     options "/options", UserController, :options
@@ -60,9 +66,18 @@ defmodule Phoenix.Router.RoutingTest do
     get "/no_log", SomePlug, [], log: false
     get "/fun_log", SomePlug, [], log: {LogLevel, :log_level, []}
     get "/users/:user_id/files/:id", UserController, :image
+
+    scope "/halt-plug" do
+      pipe_through :halt
+      get "/", UserController, :raise
+    end
+
     get "/*path", UserController, :not_found
 
     defp noop(conn, _), do: conn
+    defp halt(conn, _) do
+      conn |> Plug.Conn.send_resp(401, "Unauthorized") |> halt()
+    end
   end
 
   setup do
@@ -285,6 +300,38 @@ defmodule Phoenix.Router.RoutingTest do
                        {_, %{route: "/users/:id"}, _}}
     end
 
+    test "phoenix.router_dispatch.start and .stop are emitted when conn halted in router" do
+      conn = call(Router, :get, "/halt-plug")
+
+      assert conn.halted
+      assert conn.status == 401
+
+      assert_received {:telemetry_event, @router_start_event,
+                       {_, %{route: "/halt-plug"}, _}}
+
+      assert_received {:telemetry_event, @router_stop_event,
+                       {_, %{route: "/halt-plug"}, _}}
+
+      refute_received {:telemetry_event, @router_exception_event,
+                       {_, %{route: "/halt-plug"}, _}}
+    end
+
+    test "phoenix.router_dispatch.start and .stop are emitted when conn is halted in controller" do
+      conn = call(Router, :get, "/halt-controller")
+
+      assert conn.halted
+      assert conn.status == 401
+
+      assert_received {:telemetry_event, @router_start_event,
+                       {_, %{route: "/halt-controller"}, _}}
+
+      assert_received {:telemetry_event, @router_stop_event,
+                       {_, %{route: "/halt-controller"}, _}}
+
+      refute_received {:telemetry_event, @router_exception_event,
+                       {_, %{route: "/halt-controller"}, _}}
+    end
+
     test "phoenix.router_dispatch.start and .exception are emitted on crash" do
       assert_raise Plug.Conn.WrapperError, ~r/UndefinedFunctionError/, fn ->
         call(Router, :get, "route_that_crashes")