Repository: phoenixframework/phoenix
Pull request #4232 (2021-03-03 17:22:20)

Copy edit - Guides: Ecto


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..1d0628dc4 100644
--- a/guides/ecto.md
+++ b/guides/ecto.md
@@ -2,13 +2,15 @@
 
 > **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).
 
-Most web applications today need some form of data validation and persistence. In the Elixir ecosystem, we have Ecto to enable this. Before we jump into building database-backed web features, we're going to focus on the finer details of Ecto to give a solid base to build our web features on top of. Let's get started!
+Most web applications today need some form of data validation and persistence. In the Elixir ecosystem, we have `Ecto` to enable this. Before we jump into building database-backed web features, we're going to focus on the finer details of Ecto to give a solid base to build our web features on top of. Let's get started!
 
 Phoenix uses Ecto to provide builtin support to the following databases:
 
 * PostgreSQL (via [`postgrex`](https://github.com/elixir-ecto/postgrex))
 * MySQL (via [`myxql`](https://github.com/elixir-ecto/myxql))
 * MSSQL (via [`tds`](https://github.com/livehelpnow/tds))
+* ETS (via [`etso`](https://github.com/evadne/etso))
+
 
 Newly generated Phoenix projects include Ecto with the PostgreSQL adapter by default. You can pass the `--database` option to change or `--no-ecto` flag to exclude this.
 
@@ -18,7 +20,7 @@ This guide assumes that we have generated our new application with Ecto integrat
 
 ## Using the schema and migration generator
 
-Now that we have Ecto and Postgres installed and configured, the easiest way to use Ecto is to generate an Ecto *schema* through the `phx.gen.schema` task. Ecto schemas are a way for us to specify how Elixir data types map to and from external sources, such as database tables. Let's generate a `User` schema with `name`, `email`, `bio`, and `number_of_pets` fields.
+Once we have Ecto and PostgreSQL installed and configured, the easiest way to use Ecto is to generate an Ecto *schema* through the `phx.gen.schema` task. Ecto schemas are a way for us to specify how Elixir data types map to and from external sources, such as database tables. Let's generate a `User` schema with `name`, `email`, `bio`, and `number_of_pets` fields.
 
 ```console
 $ mix phx.gen.schema User users name:string email:string \
@@ -32,7 +34,7 @@ Remember to update your repository by running migrations:
    $ mix ecto.migrate
 ```
 
-A couple of files were generated with this task. First, we have a `user.ex` file, containing our Ecto schema with our schema definition of the fields we passed to the task. Next, a migration file was generated inside `priv/repo/migrations` which will create our database table that our schema maps to.
+A couple of files were generated with this task. First, we have a `user.ex` file, containing our Ecto schema with our schema definition of the fields we passed to the task. Next, a migration file was generated inside `priv/repo/migrations/` which will create our database table that our schema maps to.
 
 With our files in place, let's follow the instructions and run our migration:
 
@@ -70,7 +72,7 @@ hello_dev=# \d
 hello_dev=# \q
 ```
 
-If we take a look at the migration generated by `phx.gen.schema` in `priv/repo/migrations`, we'll see that it will add the columns we specified. It will also add timestamp columns for `inserted_at` and `updated_at` which come from the `timestamps/0` function.
+If we take a look at the migration generated by `phx.gen.schema` in `priv/repo/migrations/`, we'll see that it will add the columns we specified. It will also add timestamp columns for `inserted_at` and `updated_at` which come from the [`timestamps/0`] function.
 
 ```elixir
 defmodule Hello.Repo.Migrations.CreateUsers do
@@ -85,7 +87,6 @@ defmodule Hello.Repo.Migrations.CreateUsers do
 
       timestamps()
     end
-
   end
 end
 ```
@@ -97,7 +98,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)      |
@@ -122,11 +123,11 @@ defmodule Hello.Repo do
 end
 ```
 
-It begins by configuring our `otp_app` name and repo module. Then it sets the adapter – Postgres, in our case. It also sets our login credentials. Of course, you can change these to match your actual credentials if they are different.
+It begins by defining the repository module. Then it configures our `otp_app` name, and the `adapter` – `Postgres`, in our case.
 
-Our repo has three main tasks - to bring in all the common query functions from `Ecto.Repo`, to set the `otp_app` name equal to our application name, and to configure our database adapter. We'll talk more about how to use the Repo in a bit.
+Our repo has three main tasks - to bring in all the common query functions from [`Ecto.Repo`], to set the `otp_app` name equal to our application name, and to configure our database adapter. We'll talk more about how to use `Hello.Repo` in a bit.
 
-When `phx.new` generated our application, it included some basic repo configuration as well. Let's look at `config/dev.exs`.
+When `phx.new` generated our application, it included some basic repository configuration as well. Let's look at `config/dev.exs`.
 
 ```elixir
 ...
@@ -142,9 +143,9 @@ config :hello, Hello.Repo,
 
 We also have similar configuration in `config/test.exs` and `config/runtime.exs` (formerly `config/prod.secret.exs`) which can also be changed to match your actual credentials.
 
-## The Schema
+## The schema
 
-Ecto schemas are responsible for mapping Elixir values to external data sources, as well as mapping external data back into Elixir data-structures. We can also define relationships to other schemas in our applications. For example, our `User` schema might have many `Post`'s, and each `Post` would belong to a `User`. Ecto also handles data validation and type casting with changesets, which we'll discuss in a moment.
+Ecto schemas are responsible for mapping Elixir values to external data sources, as well as mapping external data back into Elixir data structures. We can also define relationships to other schemas in our applications. For example, our `User` schema might have many posts, and each post would belong to a user. Ecto also handles data validation and type casting with changesets, which we'll discuss in a moment.
 
 Here's the `User` schema that Phoenix generated for us.
 
@@ -154,7 +155,6 @@ defmodule Hello.User do
   import Ecto.Changeset
   alias Hello.User
 
-
   schema "users" do
     field :bio, :string
     field :email, :string
@@ -173,11 +173,11 @@ defmodule Hello.User do
 end
 ```
 
-Ecto schemas at their core are simply Elixir structs. Our `schema` block is what tells Ecto how to cast our `%User{}` struct fields to and from the external `users` table. Often, the ability to simply cast data to and from the database isn't enough and extra data validation is required. This is where Ecto Changesets come in. Let's dive in!
+Ecto schemas at their core are simply Elixir structs. Our `schema` block is what tells Ecto how to cast our `%User{}` struct fields to and from the external `users` table. Often, the ability to simply cast data to and from the database isn't enough and extra data validation is required. This is where Ecto changesets come in. Let's dive in!
 
-## Changesets and Validations
+## Changesets and validations
 
-Changesets define a pipeline of transformations our data needs to undergo before it will be ready for our application to use. These transformations might include type-casting, user input validation, and filtering out any extraneous parameters. Often we'll use changesets to validate user input before writing it to the database. Ecto Repos are also changeset-aware, which allows them not only to refuse invalid data, but also perform the minimal database updates possible by inspecting the changeset to know which fields have changed.
+Changesets define a pipeline of transformations our data needs to undergo before it will be ready for our application to use. These transformations might include type-casting, user input validation, and filtering out any extraneous parameters. Often we'll use changesets to validate user input before writing it to the database. Ecto repositories are also changeset-aware, which allows them not only to refuse invalid data, but also perform the minimal database updates possible by inspecting the changeset to know which fields have changed.
 
 Let's take a closer look at our default changeset function.
 
@@ -189,13 +189,13 @@ def changeset(%User{} = user, attrs) do
 end
 ```
 
-Right now, we have two transformations in our pipeline. In the first call, we invoke [`Ecto.Changeset.cast/3`](https://hexdocs.pm/ecto/Ecto.Changeset.html#cast/3), passing in our external parameters and marking which fields are required for validation.
+Right now, we have two transformations in our pipeline. In the first call, we invoke [`Ecto.Changeset.cast/3`], passing in our external parameters and marking which fields are required for validation.
 
-`cast/3` first takes a struct, then the parameters (the proposed updates), and then the final field is the list of columns to be updated. `cast/3` also will only take fields that exist in the schema.
+[`cast/3`] first takes a struct, then the parameters (the proposed updates), and then the final field is the list of columns to be updated. [`cast/3`] also will only take fields that exist in the schema.
 
-Next, [`validate_required/3`](https://hexdocs.pm/ecto/Ecto.Changeset.html#validate_required/3) checks that this list of fields is present in the changeset that `cast/3` returns. By default with the generator, all fields are required.
+Next, [`Ecto.Changeset.validate_required/3`] checks that this list of fields is present in the changeset that [`cast/3`] returns. By default with the generator, all fields are required.
 
-We can verify this functionality in iex. Let's fire up our application inside `iex` by running `iex -S mix`. In order to minimize typing and make this easier to read, let's alias our `Hello.User` struct.
+We can verify this functionality in `IEx`. Let's fire up our application inside IEx by running `iex -S mix`. In order to minimize typing and make this easier to read, let's alias our `Hello.User` struct.
 
 ```console
 $ iex -S mix
@@ -208,16 +208,21 @@ Next, let's build a changeset from our schema with an empty `User` struct, and a
 
 ```elixir
 iex> changeset = User.changeset(%User{}, %{})
-
-#Ecto.Changeset<action: nil, changes: %{},
- errors: [name: {"can't be blank", [validation: :required]},
-  email: {"can't be blank", [validation: :required]},
-  bio: {"can't be blank", [validation: :required]},
-  number_of_pets: {"can't be blank", [validation: :required]}],
- data: #Hello.User<>, valid?: false>
-```
-
-Once we have a changeset, we can check it if it is valid.
+#Ecto.Changeset<
+  action: nil,
+  changes: %{},
+  errors: [
+    name: {"can't be blank", [validation: :required]},
+    email: {"can't be blank", [validation: :required]},
+    bio: {"can't be blank", [validation: :required]},
+    number_of_pets: {"can't be blank", [validation: :required]}
+  ],
+  data: #Hello.User<>,
+  valid?: false
+>
+```
+
+Once we have a changeset, we can check if it is valid.
 
 ```elixir
 iex> changeset.valid?
@@ -228,19 +233,21 @@ Since this one is not valid, we can ask it what the errors are.
 
 ```elixir
 iex> changeset.errors
-[name: {"can't be blank", [validation: :required]},
- email: {"can't be blank", [validation: :required]},
- bio: {"can't be blank", [validation: :required]},
- number_of_pets: {"can't be blank", [validation: :required]}]
+[
+  name: {"can't be blank", [validation: :required]},
+  email: {"can't be blank", [validation: :required]},
+  bio: {"can't be blank", [validation: :required]},
+  number_of_pets: {"can't be blank", [validation: :required]}
+]
 ```
 
-Now, let's make `number_of_pets` optional. In order to do this, we simply remove it from the list.
+Now, let's make `number_of_pets` optional. In order to do this, we simply remove it from the list in the `changeset/2` function, in `Hello.User`.
 
 ```elixir
     |> validate_required([:name, :email, :bio])
 ```
 
-Now casting the changeset should tell us that only `name`, `email`, and `bio` can't be blank. We can test that by running `recompile()` inside `iex` and then rebuilding our changeset.
+Now casting the changeset should tell us that only `name`, `email`, and `bio` can't be blank. We can test that by running `recompile()` inside IEx and then rebuilding our changeset.
 
 ```elixir
 iex> recompile()
@@ -248,16 +255,24 @@ Compiling 1 file (.ex)
 :ok
 
 iex> changeset = User.changeset(%User{}, %{})
-#Ecto.Changeset<action: nil, changes: %{},
- errors: [name: {"can't be blank", [validation: :required]},
-  email: {"can't be blank", [validation: :required]},
-  bio: {"can't be blank", [validation: :required]}],
- data: #Hello.User<>, valid?: false>
+#Ecto.Changeset<
+  action: nil,
+  changes: %{},
+  errors: [
+    name: {"can't be blank", [validation: :required]},
+    email: {"can't be blank", [validation: :required]},
+    bio: {"can't be blank", [validation: :required]}
+  ],
+  data: #Hello.User<>,
+  valid?: false
+>
 
 iex> changeset.errors
-[name: {"can't be blank", [validation: :required]},
- email: {"can't be blank", [validation: :required]},
- bio: {"can't be blank", [validation: :required]}]
+[
+  name: {"can't be blank", [validation: :required]},
+  email: {"can't be blank", [validation: :required]},
+  bio: {"can't be blank", [validation: :required]}
+]
 ```
 
 What happens if we pass a key-value pair that is neither defined in the schema nor required?
@@ -266,18 +281,31 @@ Inside our existing IEx shell, let's create a `params` map with valid values plu
 
 ```elixir
 iex> params = %{name: "Joe Example", email: "joe@example.com", bio: "An example to all", number_of_pets: 5, random_key: "random value"}
-%{email: "joe@example.com", name: "Joe Example", bio: "An example to all",
-number_of_pets: 5, random_key: "random value"}
+%{
+  bio: "An example to all",
+  email: "joe@example.com",
+  name: "Joe Example",
+  number_of_pets: 5,
+  random_key: "random value"
+}
 ```
 
 Next, let's use our new `params` map to create another changeset.
 
 ```elixir
 iex> changeset = User.changeset(%User{}, params)
-#Ecto.Changeset<action: nil,
- changes: %{bio: "An example to all", email: "joe@example.com",
-   name: "Joe Example", number_of_pets: 5}, errors: [],
- data: #Hello.User<>, valid?: true>
+#Ecto.Changeset<
+  action: nil,
+  changes: %{
+    bio: "An example to all",
+    email: "joe@example.com",
+    name: "Joe Example",
+    number_of_pets: 5
+  },
+  errors: [],
+  data: #Hello.User<>,
+  valid?: true
+> 
 ```
 
 Our new changeset is valid.
@@ -295,7 +323,7 @@ iex(9)> changeset.changes
   number_of_pets: 5}
 ```
 
-Notice that our `random_key` and `random_value` have been removed from the final changeset. Changesets allow us to cast external data, such as user input on a web form or data from a CSV file into valid data into our system. Invalid parameters will be stripped and bad data that is unable to be cast according to our schema will be highlighted in the changeset errors.
+Notice that our `random_key` key and `"random_value"` value have been removed from the final changeset. Changesets allow us to cast external data, such as user input on a web form or data from a CSV file into valid data into our system. Invalid parameters will be stripped and bad data that is unable to be cast according to our schema will be highlighted in the changeset errors.
 
 We can validate more than just whether a field is required or not. Let's take a look at some finer-grained validations.
 
@@ -310,14 +338,17 @@ def changeset(%User{} = user, attrs) do
 end
 ```
 
-Now, if we try to cast data containing a value of "A" for our user's bio, we should see the failed validation in the changeset's errors.
+Now, if we try to cast data containing a value of `"A"` for our user's `bio`, we should see the failed validation in the changeset's errors.
 
 
 ```elixir
+iex> recompile()
+
 iex> changeset = User.changeset(%User{}, %{bio: "A"})
+
 iex> changeset.errors[:bio]
 {"should be at least %{count} character(s)",
- [count: 2, validation: :length, min: 2]}
+ [count: 2, validation: :length, kind: :min, type: :string]}
 ```
 
 If we also have a requirement for the maximum length that a bio can have, we can simply add another validation.
@@ -332,7 +363,7 @@ def changeset(%User{} = user, attrs) do
 end
 ```
 
-Let's say we want to perform at least some rudimentary format validation on the `email` field. All we want to check for is the presence of the "@". The `validate_format/3` function is just what we need.
+Let's say we want to perform at least some rudimentary format validation on the `email` field. All we want to check for is the presence of the `@`. The [`Ecto.Changeset.validate_format/3`] function is just what we need.
 
 ```elixir
 def changeset(%User{} = user, attrs) do
@@ -345,21 +376,24 @@ def changeset(%User{} = user, attrs) do
 end
 ```
 
-If we try to cast a user with an email of "example.com", we should see an error message like the following.
+If we try to cast a user with an email of `"example.com"`, we should see an error message like the following:
 
 ```elixir
+iex> recompile()
+
 iex> changeset = User.changeset(%User{}, %{email: "example.com"})
+
 iex> changeset.errors[:email]
 {"has invalid format", [validation: :format]}
 ```
 
 There are many more validations and transformations we can perform in a changeset. Please see the [Ecto Changeset documentation](https://hexdocs.pm/ecto/Ecto.Changeset.html) for more information.
 
-## Data Persistence
+## Data persistence
 
-We've explored migrations and schemas, but we haven't yet persisted any of our schemas or changesets. We briefly looked at our repo module in `lib/hello/repo.ex` earlier, now it's time to put it to use.
+We've explored migrations and schemas, but we haven't yet persisted any of our schemas or changesets. We briefly looked at our repository module in `lib/hello/repo.ex` earlier, now it's time to put it to use.
 
-Ecto Repos are the interface into a storage system, be it a Database like PostgreSQL or an external service like a RESTful API. The Repo module's purpose is to take care of the finer details of persistence and data querying for us. As the caller, we only care about fetching and persisting data. The Repo takes care of the underlying Database adapter communication, connection pooling, and error translation for database constraint violations.
+Ecto repositories are the interface into a storage system, be it a database like PostgreSQL or an external service like a RESTful API. The `Repo` module's purpose is to take care of the finer details of persistence and data querying for us. As the caller, we only care about fetching and persisting data. The `Repo` module takes care of the underlying database adapter communication, connection pooling, and error translation for database constraint violations.
 
 Let's head back over to IEx with `iex -S mix`, and insert a couple of users into the database.
 
@@ -381,6 +415,7 @@ INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETUR
    number_of_pets: nil,
    updated_at: ~N[2021-02-25 01:58:55]
  }}
+
 iex> Repo.insert(%User{email: "user2@example.com"})
 [debug] QUERY OK db=1.3ms idle=1402.7ms
 INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user2@example.com", ~N[2021-02-25 02:03:28], ~N[2021-02-25 02:03:28]]
@@ -397,7 +432,7 @@ INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETUR
  }}
 ```
 
-We started by aliasing our `User` and `Repo` modules for easy access. Next, we called `Repo.insert/1` and passed a user struct. Since we're in the `dev` environment, we can see the debug logs for the query our Repo performed when inserting the underlying `%User{}` data. We received a 2-tuple back with `{:ok, %User{}}`, which lets us know the insertion was successful. With a couple of users inserted, let's fetch them back out of the repo.
+We started by aliasing our `User` and `Repo` modules for easy access. Next, we called [`Repo.insert/1`] and passed a user struct. Since we are in the `dev` environment, we can see the debug logs for the query our repository performed when inserting the underlying `%User{}` data. We received a two-element tuple back with `{:ok, %User{}}`, which lets us know the insertion was successful. With a couple of users inserted, let's fetch them back out of the repo.
 
 ```elixir
 iex> Repo.all(User)
@@ -427,7 +462,7 @@ SELECT u0."id", u0."bio", u0."email", u0."name", u0."number_of_pets", u0."insert
 ]
 ```
 
-That was easy! `Repo.all/1` takes a data source, our `User` schema in this case, and translates that to an underlying SQL query against our database. After it fetches the data, the Repo then uses our Ecto schema to map the database values back into Elixir data-structures according to our `User` schema. We're not just limited to basic querying – Ecto includes a full-fledged query DSL for advanced SQL generation. In addition to a natural Elixir DSL, Ecto's query engine gives us multiple great features, such as SQL injection protection and compile-time optimization of queries. Let's try it out.
+That was easy! `Repo.all/1` takes a data source, our `User` schema in this case, and translates that to an underlying SQL query against our database. After it fetches the data, the Repo then uses our Ecto schema to map the database values back into Elixir data structures according to our `User` schema. We're not just limited to basic querying – Ecto includes a full-fledged query DSL for advanced SQL generation. In addition to a natural Elixir DSL, Ecto's query engine gives us multiple great features, such as SQL injection protection and compile-time optimization of queries. Let's try it out.
 
 ```elixir
 iex> import Ecto.Query
@@ -439,7 +474,7 @@ SELECT u0."email" FROM "users" AS u0 []
 ["user1@example.com", "user2@example.com"]
 ```
 
-First, we imported `Ecto.Query`, which imports the `from` macro of Ecto's Query DSL. Next, we built a query which selects all the email addresses in our users table. Let's try another example.
+First, we imported [`Ecto.Query`], which imports the [`from/2`] macro of Ecto's Query DSL. Next, we built a query which selects all the email addresses in our users table. Let's try another example.
 
 ```elixir
 iex)> Repo.one(from u in User, where: ilike(u.email, "%1%"),
@@ -448,7 +483,7 @@ iex)> Repo.one(from u in User, where: ilike(u.email, "%1%"),
 1
 ```
 
-Now we're starting to get a taste of Ecto's rich querying capabilities. We used `Repo.one/1` to fetch the count of all users with an email address containing "1", and received the expected count in return. This just scratches the surface of Ecto's query interface, and much more is supported such as sub-querying, interval queries, and advanced select statements. For example, let's build a query to fetch a map of all user id's to their email addresses.
+Now we're starting to get a taste of Ecto's rich querying capabilities. We used [`Repo.one/1`] to fetch the count of all users with an email address containing `1`, and received the expected count in return. This just scratches the surface of Ecto's query interface, and much more is supported such as sub-querying, interval queries, and advanced select statements. For example, let's build a query to fetch a map of all user id's to their email addresses.
 
 ```elixir
 iex> Repo.all(from u in User, select: %{u.id => u.email})
@@ -459,11 +494,11 @@ SELECT u0."id", u0."email" FROM "users" AS u0 []
 
 That little query packed a big punch. It both fetched all user emails from the database and efficiently built a map of the results in one go. You should browse the [Ecto.Query documentation](https://hexdocs.pm/ecto/Ecto.Query.html#content) to see the breadth of supported query features.
 
-In addition to inserts, we can also perform updates and deletes with `Repo.update/1` and `Repo.delete/1` to update or delete a single schema. Ecto also supports bulk persistence with the `Repo.insert_all`, `Repo.update_all`, and `Repo.delete_all` functions.
+In addition to inserts, we can also perform updates and deletes with `Repo.update/2` and `Repo.delete/2` to update or delete a single schema. Ecto also supports bulk persistence with the `Repo.insert_all/3`, `Repo.update_all/3`, and `Repo.delete_all/2` functions.
 
-There is quite a bit more that Ecto can do and we've only barely scratched the surface. With a solid Ecto foundation in place, we're now ready to continue building our app and integrate the web facing application with our backend persistence. Along the way, we'll expand our Ecto knowledge and learn how to properly isolate our web interface from the underlying details of our system. Please take a look at the [Ecto documentation](https://hexdocs.pm/ecto/) for the rest of the story.
+There is quite a bit more that Ecto can do and we've only barely scratched the surface. With a solid Ecto foundation in place, we're now ready to continue building our app and integrate the web-facing application with our backend persistence. Along the way, we'll expand our Ecto knowledge and learn how to properly isolate our web interface from the underlying details of our system. Please take a look at the [Ecto documentation](https://hexdocs.pm/ecto/) for the rest of the story.
 
-In our [context guide](contexts.html), we'll find out how to wrap up our Ecto access and business logic behind modules that group related functionality. We'll see how Phoenix helps us design maintainable applications, and we'll find out about other neat Ecto features along the way.
+In our [contexts guide](contexts.html), we'll find out how to wrap up our Ecto access and business logic behind modules that group related functionality. We'll see how Phoenix helps us design maintainable applications, and we'll find out about other neat Ecto features along the way.
 
 ## Using MySQL
 
@@ -506,7 +541,7 @@ defmodule HelloPhoenix.MixProject do
 end
 ```
 
-Next, we need to configure our adapter to use the default MySQL credentials. Let's open up our `config/dev.exs` file and do that.
+Next, we need to configure our adapter to use the default MySQL credentials by updating `config/dev.exs`:
 
 ```elixir
 config :hello_phoenix, HelloPhoenix.Repo,
@@ -531,8 +566,8 @@ With our new adapter installed and configured, we're ready to create our databas
 $ mix ecto.create
 ```
 
-The database for HelloPhoenix.repo has been created.
-We're also ready to run any migrations, or do anything else with Ecto that we might choose.
+The database for HelloPhoenix.Repo has been created.
+We're also ready to run any migrations, or do anything else with Ecto that we may choose.
 
 ```console
 $ mix ecto.migrate
@@ -543,6 +578,11 @@ $ mix ecto.migrate
 
 ## Other options
 
-While Phoenix uses the [Ecto project](https://hexdocs.pm/ecto) to interact with the data access layer, there are many other data access options, some even built into the Erlang standard library. [ETS](http://www.erlang.org/doc/man/ets.html) and [DETS](http://www.erlang.org/doc/man/dets.html) are key-value data stores built into [OTP](http://www.erlang.org/doc/). OTP also provides a relational database called [mnesia](http://www.erlang.org/doc/man/mnesia.html) with its own query language called QLC. Both Elixir and Erlang also have a number of libraries for working with a wide range of popular data stores.
+While Phoenix uses the `Ecto` project to interact with the data access layer, there are many other data access options, some even built into the Erlang standard library. [ETS](http://www.erlang.org/doc/man/ets.html) – available in Ecto via [`etso`](https://hexdocs.pm/etso/) – and [DETS](http://www.erlang.org/doc/man/dets.html) are key-value data stores built into [OTP](http://www.erlang.org/doc/). OTP also provides a relational database called [Mnesia](http://www.erlang.org/doc/man/mnesia.html) with its own query language called QLC. Both Elixir and Erlang also have a number of libraries for working with a wide range of popular data stores.
 
 The data world is your oyster, but we won't be covering these options in these guides.
+
+
+[`cast/3`]: `Ecto.Changeset.cast/3`
+[`from/2`]: `Ecto.Query.from/2`
+[`timestamps/0`]: `Ecto.Schema.html.timestamps/0`
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")