Repository: phoenixframework/phoenix
Pull request #2514 (2017-09-22 01:44:18)

More accurate diff for controller actions
This is not correcting any mistakes, but does a couple of things to make a particular diff clearer to the reader:
- shows the previous state for some changed lines, so that it's easy to compare what's changed
- removes some parameters that are no longer necessary after the change


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 78521217f..53b3e0f5a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 * Enhancements
   * [Controller] Support partial file downloads with `:offset` and `:length` options to `send_download/3`
+  * [Logger] Add whitelist support to `filter_parameters` logger configuration, via new `:keep` tuple format
 
 ## 1.3.0 (2017-07-28)
 
diff --git a/assets/js/phoenix.js b/assets/js/phoenix.js
index 9ee8a7643..387663c0c 100644
--- a/assets/js/phoenix.js
+++ b/assets/js/phoenix.js
@@ -180,6 +180,7 @@
  * @module phoenix
  */
 
+const global = typeof(self) !== "undefined" ? self : window
 const VSN = "2.0.0"
 const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3}
 const DEFAULT_TIMEOUT = 10000
@@ -688,7 +689,7 @@ export class Socket {
     this.sendBuffer           = []
     this.ref                  = 0
     this.timeout              = opts.timeout || DEFAULT_TIMEOUT
-    this.transport            = opts.transport || window.WebSocket || LongPoll
+    this.transport            = opts.transport || global.WebSocket || LongPoll
     this.defaultEncoder       = Serializer.encode
     this.defaultDecoder       = Serializer.decode
     if(this.transport !== LongPoll){
@@ -1034,18 +1035,13 @@ export class LongPoll {
 export class Ajax {
 
   static request(method, endPoint, accept, body, timeout, ontimeout, callback){
-    if(typeof window !== 'undefined'){
-      if(window.XDomainRequest){
-        let req = new XDomainRequest() // IE8, IE9
-        this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback)
-      } else {
-        let req = window.XMLHttpRequest ?
-                    new window.XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
-                    new ActiveXObject("Microsoft.XMLHTTP") // IE6, IE5
-        this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback)
-      }
+    if(global.XDomainRequest){
+      let req = new XDomainRequest() // IE8, IE9
+      this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback)
     } else {
-      let req = new XMLHttpRequest(); // tvOS support
+      let req = global.XMLHttpRequest ?
+                  new global.XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
+                  new ActiveXObject("Microsoft.XMLHTTP") // IE6, IE5
       this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback)
     }
   }
@@ -1241,4 +1237,4 @@ class Timer {
       this.callback()
     }, this.timerCalc(this.tries + 1))
   }
-}
\ No newline at end of file
+}
diff --git a/guides/docs/contexts.md b/guides/docs/contexts.md
index 134e5759e..1b876dc6e 100644
--- a/guides/docs/contexts.md
+++ b/guides/docs/contexts.md
@@ -100,7 +100,7 @@ $ mix ecto.migrate
 [info]  == Migrated in 0.0s
 ```
 
-Before jump into the generated code, let's start the server with `mix phx.server` and visit [http://localhost:4000/users](http://localhost:4000/users). Let's follow the "New User" link and click the "Submit" button without providing any input. We should be greeted with the following output:
+Before we jump into the generated code, let's start the server with `mix phx.server` and visit [http://localhost:4000/users](http://localhost:4000/users). Let's follow the "New User" link and click the "Submit" button without providing any input. We should be greeted with the following output:
 
 ```
 Oops, something went wrong! Please check the errors below.
@@ -910,15 +910,17 @@ Next, we added an `:authorized_page` plug that makes use of plug's guard clause
 With our new plugs in place, we can now modify our `create`, `edit`, `update`, and `delete` actions to make use of the new values in the connection assigns:
 
 ```elixir
-   def edit(conn, %{"id" => id}) do
--    page = CMS.get_page!(id)
--    changeset = CMS.change_page(page)
--    render(conn, "edit.html", page: page, changeset: changeset)
-+    changeset = CMS.change_page(conn.assigns.page)
-+    render(conn, "edit.html", changeset: changeset)
-   end
-
-  def create(conn, %{"page" => page_params}) do
+  def edit(conn, _) do
+-   page = CMS.get_page!(id)
+-   changeset = CMS.change_page(page)
++   changeset = CMS.change_page(conn.assigns.page)
+-   render(conn, "edit.html", page: page, changeset: changeset)
++   render(conn, "edit.html", changeset: changeset)
+  end
+
+- def create(conn, %{"id" => id, "page" => page_params}) do
++ def create(conn, %{"page" => page_params}) do
+-   case CMS.create_page(page_params) do
 +   case CMS.create_page(conn.assigns.current_author, page_params) do
       {:ok, page} ->
         conn
@@ -929,20 +931,25 @@ With our new plugs in place, we can now modify our `create`, `edit`, `update`, a
     end
   end
 
-  def update(conn, %{"page" => page_params}) do
+- def update(conn, %{"id" => id, "page" => page_params}) do
++ def update(conn, %{"page" => page_params}) do
+-   page = CMS.get_page!(id)
+-   case CMS.update_page(page, page_params) do
 +   case CMS.update_page(conn.assigns.page, page_params) do
       {:ok, page} ->
         conn
         |> put_flash(:info, "Page updated successfully.")
         |> redirect(to: cms_page_path(conn, :show, page))
       {:error, %Ecto.Changeset{} = changeset} ->
-        render(conn, "edit.html", changeset: changeset)
+-       render(conn, "edit.html", page: page, changeset: changeset)
++       render(conn, "edit.html", changeset: changeset)
     end
   end
 
 - def delete(conn, %{"id" => id}) do
 + def delete(conn, _) do
 -   page = CMS.get_page!(id)
+-   {:ok, _page} = CMS.delete_page(page)
 +   {:ok, _page} = CMS.delete_page(conn.assigns.page)
 
     conn
diff --git a/guides/docs/controllers.md b/guides/docs/controllers.md
index af17752e7..a95b5505e 100644
--- a/guides/docs/controllers.md
+++ b/guides/docs/controllers.md
@@ -537,3 +537,139 @@ def index(conn, _params) do
   redirect conn, external: redirect_test_url(conn, :redirect_test)
 end
 ```
+
+### Action Fallback
+
+Action Fallback allows us to centralize error handling code in plugs which are called when a controller action fails to return a `Plug.Conn.t`. These plugs receive both the conn which was originally passed to the controller action along with the return value of the action.
+
+Let's say we have a `show` action which uses `with` to fetch a blog post and then authorize the current user to view that blog post. In this example we might expect `Blog.fetch_post/1` to return `{:error, :not_found}` if the post is not found and `Authorizer.authorize/3` might return `{:error, :unauthorized}` if the user is unauthorized. We could render the error views for these non-happy-paths directly.
+
+```elixir
+defmodule HelloWeb.MyController do
+  use Phoenix.Controller
+  alias Hello.{Authorizer, Blog}
+
+  def show(conn, %{"id" => id}, current_user) do
+    with {:ok, post} <- Blog.fetch_post(id),
+         :ok <- Authorizer.authorize(current_user, :view, post) do
+
+      render(conn, "show.json", post: post)
+    else
+      {:error, :not_found} ->
+        conn
+        |> put_status(:not_found)
+        |> render(ErrorView, :"404")
+      {:error, :unauthorized} ->
+        conn
+        |> put_status(403)
+        |> render(ErrorView, :"403")
+    end
+  end
+end
+```
+
+Many times - especially when implementing controllers for an API - error handling in the controllers like this results in a lot of repetition. Instead we can define a plug which knows how to handle these error cases.
+
+```elixir
+defmodule HelloWeb.MyFallbackController do
+  use Phoenix.Controller
+  alias HelloWeb.ErrorView
+
+  def call(conn, {:error, :not_found}) do
+    conn
+    |> put_status(:not_found)
+    |> render(ErrorView, :"404")
+  end
+
+  def call(conn, {:error, :unauthorized}) do
+    conn
+    |> put_status(403)
+    |> render(ErrorView, :"403")
+  end
+end
+```
+
+Then we can reference that plug using action_fallback and simply remove the `else` block from our `with`.  Our plug will receive the original conn as well as the result of the action and respond appropriately.
+
+```elixir
+defmodule HelloWeb.MyController do
+  use Phoenix.Controller
+  alias Hello.{Authorizer, Blog}
+
+  action_fallback HelloWeb.MyFallbackController
+
+  def show(conn, %{"id" => id}, current_user) do
+    with {:ok, post} <- Blog.fetch_post(id),
+         :ok <- Authorizer.authorize(current_user, :view, post) do
+
+      render(conn, "show.json", post: post)
+    end
+  end
+end
+```
+
+### Halting the Plug Pipeline
+
+As we mentioned - Controllers are plugs.... specifically plugs which are called toward the end of the plug pipeline.  At any step of the pipeline we might have cause to stop processing - typically because we've redirected or rendered a response. `Plug.Conn.t` has a `:halted` key - setting it to true will cause downstream plugs to be skipped. We can do that easily using `Plug.Conn.halt/1`.
+
+Consider a `HelloWeb.PostFinder` plug. On call, if we find a post related to a given id then we add it to `assigns`; and if we don't find the post we respond with a 404 page.
+
+```elixir
+defmodule HelloWeb.PostFinder do
+  use Plug
+  import Plog.Conn
+
+  alias Hello.Blog
+
+  def init(opts), do: opts
+
+  def call(conn, _) do
+    case Blog.get_post(conn.params["id"]) do
+      {:ok, post} ->
+        assign(conn, :post, post)
+      {:error, :notfound} ->
+        conn
+        |> send_resp(404, "Not found")
+    end
+  end
+end
+```
+
+If we call this plug as part of the plug pipeline any downstream plugs will still be processed. If we want to prevent downstream plugs from being processed in the event of the 404 response we can simply call `Plug.Conn.halt/1`.
+
+```elixir
+    case Blog.get_post(conn.params["id"]) do
+      {:ok, post} ->
+        assign(conn, :post, post)
+      {:error, :notfound} ->
+        conn
+        |> send_resp(404, "Not found")
+        |> halt()
+    end
+```
+
+It's important to note that `halt/1` simply sets the `:halted` key on `Plug.Conn.t` to `true`.  This is enough to prevent downstream plugs from being invoked but it will not stop the execution of code locally. As such
+
+```elixir
+  conn
+  |> send_resp(404, "Not found")
+  |> halt()
+```
+
+... is functionally equivalent to...
+
+```elixir
+  conn
+  |> halt()
+  |> send_resp(404, "Not found")
+```
+
+It's also important to note that halting will only stop the plug pipeline from continuing. Function plugs will still execute unless their implementation checks for the `:halt` value.
+
+```
+  def post_authorization_plug(%{halted: true} = conn, _), do: conn
+  def post_authorization_plug(conn, _) do
+  . . .
+  end
+```
+
diff --git a/guides/docs/ecto.md b/guides/docs/ecto.md
index 446cffc89..3f9986db2 100644
--- a/guides/docs/ecto.md
+++ b/guides/docs/ecto.md
@@ -27,11 +27,11 @@ ALTER USER postgres WITH SUPERUSER;
 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.
 
 ```console
-$ mix phx.gen.schema Accounts.User users name:string email:string \
+$ mix phx.gen.schema User users name:string email:string \
 bio:string number_of_pets:integer
 
-* creating ./lib/hello/accounts/user.ex
-* creating priv/repo/migrations/20170523151118_create_users.exs
+* creating ./lib/hello/user.ex
+* creating priv/repo/migrations/20170523151118_create_user.exs
 
 Remember to update your repository by running migrations:
 
@@ -162,10 +162,10 @@ Ecto schemas are responsible for mapping Elixir values to external data sources,
 Here's the `User` schema that Phoenix generated for us.
 
 ```elixir
-defmodule Hello.Accounts.User do
+defmodule Hello.User do
   use Ecto.Schema
   import Ecto.Changeset
-  alias Hello.Accounts.User
+  alias Hello.User
 
 
   schema "users" do
@@ -206,13 +206,13 @@ Right now, we have two transformations in our pipeline. In the first call, we in
 `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` 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.Accounts.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
 
-iex> alias Hello.Accounts.User
-Hello.Accounts.User
+iex> alias Hello.User
+Hello.User
 ```
 
 Next, let's build a changeset from our schema with an empty `User` struct, and an empty map of parameters.
@@ -225,7 +225,7 @@ iex> changeset = User.changeset(%User{}, %{})
   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.Accounts.User<>, valid?: false>
+ data: #Hello.User<>, valid?: false>
 ```
 
 Once we have a changeset, we can check it if it is valid.
@@ -288,7 +288,7 @@ 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.Accounts.User<>, valid?: true>
+ data: #Hello.User<>, valid?: true>
 ```
 
 Our new changeset is valid.
@@ -373,13 +373,13 @@ We've talked a lot about migrations and data-storage, but we haven't yet persist
 Let's head back over to IEx with `iex -S mix`, and insert a couple of users to the database.
 
 ```console
-iex> alias Hello.{Repo, Accounts.User}
-[Hello.Repo, Hello.Accounts.User]
+iex> alias Hello.{Repo, User}
+[Hello.Repo, Hello.User]
 iex> Repo.insert(%User{email: "user1@example.com"})
 [debug] QUERY OK db=4.6ms
 INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user1@example.com", {{2017, 5, 23}, {19, 6, 4, 822044}}, {{2017, 5, 23}, {19, 6, 4, 822055}}]
 {:ok,
- %Hello.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
+ %Hello.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   bio: nil, email: "user1@example.com", id: 3,
   inserted_at: ~N[2017-05-23 19:06:04.822044], name: nil, number_of_pets: nil,
   updated_at: ~N[2017-05-23 19:06:04.822055]}}
@@ -388,7 +388,7 @@ iex> Repo.insert(%User{email: "user2@example.com"})
 [debug] QUERY OK db=5.1ms
 INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETURNING "id" ["user2@example.com", {{2017, 5, 23}, {19, 6, 8, 452545}}, {{2017, 5, 23}, {19, 6, 8, 452556}}]
 {:ok,
- %Hello.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
+ %Hello.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   bio: nil, email: "user2@example.com", id: 4,
   inserted_at: ~N[2017-05-23 19:06:08.452545], name: nil, number_of_pets: nil,
   updated_at: ~N[2017-05-23 19:06:08.452556]}}
@@ -400,11 +400,11 @@ We started by aliasing our `User` and `Repo` modules for easy access. Next, we c
 iex> Repo.all(User)
 [debug] QUERY OK source="users" db=2.7ms
 SELECT u0."id", u0."bio", u0."email", u0."name", u0."number_of_pets", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
-[%Hello.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
+[%Hello.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   bio: nil, email: "user1@example.com", id: 3,
   inserted_at: ~N[2017-05-23 19:06:04.822044], name: nil, number_of_pets: nil,
   updated_at: ~N[2017-05-23 19:06:04.822055]},
- %Hello.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
+ %Hello.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
   bio: nil, email: "user2@example.com", id: 4,
   inserted_at: ~N[2017-05-23 19:06:08.452545], name: nil, number_of_pets: nil,
   updated_at: ~N[2017-05-23 19:06:08.452556]}]
diff --git a/guides/docs/introduction/overview.md b/guides/docs/introduction/overview.md
index 93940e852..510caa053 100644
--- a/guides/docs/introduction/overview.md
+++ b/guides/docs/introduction/overview.md
@@ -81,4 +81,4 @@ Ecto is built around four main abstractions:
 A new Phoenix application will use Ecto with PostgreSQL storage by default.
 
 ## A Note about these guides
-If you find an issue with the guides or would like to help improve these guides please checkout the [Phoenix Guides](https://github.com/phoenixframework/phoenix_guides/) github. Issues and Pull Requests are happily accepted!
+If you find an issue with the guides or would like to help improve these guides please checkout the [Phoenix Guides](https://github.com/phoenixframework/phoenix/tree/master/guides/) on github. Issues and Pull Requests are happily accepted!
diff --git a/lib/mix/phoenix/context.ex b/lib/mix/phoenix/context.ex
index c6e280b37..e7c92dece 100644
--- a/lib/mix/phoenix/context.ex
+++ b/lib/mix/phoenix/context.ex
@@ -54,6 +54,26 @@ defmodule Mix.Phoenix.Context do
 
   def pre_existing_tests?(%Context{test_file: file}), do: File.exists?(file)
 
+  def function_count(%Context{file: file}) do
+    {_ast, count} =
+      file
+      |> File.read!()
+      |> Code.string_to_quoted!()
+      |> Macro.postwalk(0, fn
+        {:def, _, _} = node, count -> {node, count + 1}
+        node, count -> {node, count}
+      end)
+
+    count
+  end
+
+  def file_count(%Context{dir: dir}) do
+    dir
+    |> Path.join("**/*.ex")
+    |> Path.wildcard()
+    |> Enum.count()
+  end
+
   defp web_module do
     base = Mix.Phoenix.base()
     if String.ends_with?(base, "Web") do
diff --git a/lib/mix/tasks/phx.gen.context.ex b/lib/mix/tasks/phx.gen.context.ex
index 66ecd75bf..e0e2232c5 100644
--- a/lib/mix/tasks/phx.gen.context.ex
+++ b/lib/mix/tasks/phx.gen.context.ex
@@ -84,6 +84,7 @@ defmodule Mix.Tasks.Phx.Gen.Context do
     paths = Mix.Phoenix.generator_paths()
 
     prompt_for_conflicts(context)
+    prompt_for_code_injection(context)
 
     context
     |> copy_new_files(paths, binding)
@@ -234,4 +235,27 @@ defmodule Mix.Tasks.Phx.Gen.Context do
     split over distinct contexts (such as Accounts.User and Payments.User).
     """
   end
+
+  def prompt_for_code_injection(%Context{} = context) do
+    if Context.pre_existing?(context) do
+      function_count = Context.function_count(context)
+      file_count = Context.file_count(context)
+
+      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.
+
+        * It's ok to have multiple resources in the same context as
+          long as they are closely related
+        * If they are not closely related, another context probably works better
+
+      If you are not sure, prefer a new context over adding to the existing one.
+
+      """
+      unless Mix.shell.yes?("Would you like proceed? [Y/n]") do
+        System.halt()
+      end
+    end
+  end
 end
diff --git a/lib/mix/tasks/phx.gen.html.ex b/lib/mix/tasks/phx.gen.html.ex
index bb27c74b2..46d05ab35 100644
--- a/lib/mix/tasks/phx.gen.html.ex
+++ b/lib/mix/tasks/phx.gen.html.ex
@@ -103,7 +103,10 @@ defmodule Mix.Tasks.Phx.Gen.Html do
     if Mix.Project.umbrella? do
       Mix.raise "mix phx.gen.html can only be run inside an application directory"
     end
+
     {context, schema} = Gen.Context.build(args)
+    Gen.Context.prompt_for_code_injection(context)
+
     binding = [context: context, schema: schema, inputs: inputs(schema)]
     paths = Mix.Phoenix.generator_paths()
 
diff --git a/lib/mix/tasks/phx.gen.json.ex b/lib/mix/tasks/phx.gen.json.ex
index 662c18093..c66960465 100644
--- a/lib/mix/tasks/phx.gen.json.ex
+++ b/lib/mix/tasks/phx.gen.json.ex
@@ -105,6 +105,8 @@ defmodule Mix.Tasks.Phx.Gen.Json do
     end
 
     {context, schema} = Gen.Context.build(args)
+    Gen.Context.prompt_for_code_injection(context)
+
     binding = [context: context, schema: schema]
     paths = Mix.Phoenix.generator_paths()
 
diff --git a/lib/phoenix/logger.ex b/lib/phoenix/logger.ex
index 3fbf0085c..6d4a1cd6c 100644
--- a/lib/phoenix/logger.ex
+++ b/lib/phoenix/logger.ex
@@ -15,6 +15,15 @@ defmodule Phoenix.Logger do
   case sensitive.
 
   Phoenix's default is `["password"]`.
+
+  Phoenix can filter all parameters by default and selectively keep
+  parameters. This can be configured like so:
+
+      config :phoenix, :filter_parameters, {:keep, ["id", "order"]}
+
+  With the configuration above, Phoenix will filter all parameters,
+  except those that match exactly `id` or `order`. If a kept parameter
+  matches, all parameters nested under that one will also be kept.
   """
   require Logger
   import Phoenix.Controller
@@ -48,22 +57,41 @@ defmodule Phoenix.Logger do
 
   @doc false
   def filter_values(values, params \\ Application.get_env(:phoenix, :filter_parameters))
-  def filter_values(%{__struct__: mod} = struct, _filter_params) when is_atom(mod) do
+  def filter_values(values, {:discard, params}), do: discard_values(values, params)
+  def filter_values(values, {:keep, params}), do: keep_values(values, params)
+  def filter_values(values, params), do: discard_values(values, params)
+
+  defp discard_values(%{__struct__: mod} = struct, _params) when is_atom(mod) do
     struct
   end
-  def filter_values(%{} = map, filter_params) do
+  defp discard_values(%{} = map, params) do
     Enum.into map, %{}, fn {k, v} ->
-      if is_binary(k) and String.contains?(k, filter_params) do
+      if is_binary(k) and String.contains?(k, params) do
         {k, "[FILTERED]"}
       else
-        {k, filter_values(v, filter_params)}
+        {k, discard_values(v, params)}
+      end
+    end
+  end
+  defp discard_values([_|_] = list, params) do
+    Enum.map(list, &discard_values(&1, params))
+  end
+  defp discard_values(other, _params), do: other
+
+  defp keep_values(%{__struct__: mod}, _params) when is_atom(mod), do: "[FILTERED]"
+  defp keep_values(%{} = map, params) do
+    Enum.into map, %{}, fn {k, v} ->
+      if is_binary(k) and k in params do
+        {k, discard_values(v, [])}
+      else
+        {k, keep_values(v, params)}
       end
     end
   end
-  def filter_values([_|_] = list, filter_params) do
-    Enum.map(list, &filter_values(&1, filter_params))
+  defp keep_values([_|_] = list, params) do
+    Enum.map(list, &keep_values(&1, params))
   end
-  def filter_values(other, _filter_params), do: other
+  defp keep_values(_other, _params), do: "[FILTERED]"
 
   defp params(%Plug.Conn.Unfetched{}), do: "[UNFETCHED]"
   defp params(params) do
diff --git a/priv/static/phoenix.js b/priv/static/phoenix.js
index 331a1c0f9..3e3958df9 100644
--- a/priv/static/phoenix.js
+++ b/priv/static/phoenix.js
@@ -201,6 +201,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
  * @module phoenix
  */
 
+var global = typeof self !== "undefined" ? self : window;
 var VSN = "2.0.0";
 var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
 var DEFAULT_TIMEOUT = 10000;
@@ -891,7 +892,7 @@ var Socket = exports.Socket = function () {
     this.sendBuffer = [];
     this.ref = 0;
     this.timeout = opts.timeout || DEFAULT_TIMEOUT;
-    this.transport = opts.transport || window.WebSocket || LongPoll;
+    this.transport = opts.transport || global.WebSocket || LongPoll;
     this.defaultEncoder = Serializer.encode;
     this.defaultDecoder = Serializer.decode;
     if (this.transport !== LongPoll) {
@@ -1410,18 +1411,13 @@ var Ajax = exports.Ajax = function () {
   _createClass(Ajax, null, [{
     key: "request",
     value: function request(method, endPoint, accept, body, timeout, ontimeout, callback) {
-      if (typeof window !== 'undefined') {
-        if (window.XDomainRequest) {
-          var req = new XDomainRequest(); // IE8, IE9
-          this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
-        } else {
-          var _req = window.XMLHttpRequest ? new window.XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
-          new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
-          this.xhrRequest(_req, method, endPoint, accept, body, timeout, ontimeout, callback);
-        }
+      if (global.XDomainRequest) {
+        var req = new XDomainRequest(); // IE8, IE9
+        this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
       } else {
-        var _req2 = new XMLHttpRequest(); // tvOS support
-        this.xhrRequest(_req2, method, endPoint, accept, body, timeout, ontimeout, callback);
+        var _req = global.XMLHttpRequest ? new global.XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
+        new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
+        this.xhrRequest(_req, method, endPoint, accept, body, timeout, ontimeout, callback);
       }
     }
   }, {


Test patch:
diff --git a/guides/docs/testing/testing_controllers.md b/guides/docs/testing/testing_controllers.md
index b6a9c4a03..c7a994b7f 100644
--- a/guides/docs/testing/testing_controllers.md
+++ b/guides/docs/testing/testing_controllers.md
@@ -5,40 +5,73 @@ We're going to take a look at how we might test drive a controller which has end
 Phoenix has a generator for creating a JSON resource which looks like this:
 
 ```console
-mix phoenix.gen.json Stuff Thing things some_attr:string another_attr:string
+$ mix phx.gen.json  AllTheThings Thing things some_attr:string another_attr:string
 ```
 
-Stuff is the context module name, Thing is the schema module name, things is the table name, and some_attr and another_attr are database columns on table things of type string. Don't run this command, we're going to explore test driving out a similar result to what a generator would give us.
+In this command, AllTheThings is the context; Thing is the schema; things is the plural name of the schema (which is used as the table name). Then `some_attr` and `another_attr` are the database columns on table `things` of type string.
+
+However, *don't* actually run this command. Instead, we're going to explore test driving out a similar result to what a generator would give us.
 
 ### Set up
-Let's create a `User` schema. Since schema creation is not in scope of this guide, we will use the generator.  If you aren't familiar, read [this section of the Mix guide](mix_tasks.html#phoenix-specific-mix-tasks).
+
+If you haven't already done so, first create a blank project by running:
+
+```console
+$ mix phx.new hello -y
+```
+
+Change into the newly-created `hello` directory, configure your database in `config/dev.exs` and then run:
+
+```console
+$ mix ecto.create
+```
+
+If you have any questions about this process, now is a good time to jump over to the [Up and Running Guide](up_and_running.html).
+
+Let's create an `Accounts` context for this example. Since context creation is not in scope of this guide, we will use the generator. If you aren't familiar, read [this section of the Mix guide](phoenix_mix_tasks.html#phoenix-specific-mix-tasks) and [the Contexts Guide](contexts.html#content).
 
 ```console
-$ mix phx.gen.schema Accounts.User users name:string email:string
+$ mix phx.gen.context Accounts User users name:string email:string:unique password:string
+
 * creating lib/hello/accounts/user.ex
-* creating priv/repo/migrations/20170908031352_create_users.exs
+* creating priv/repo/migrations/20170913155721_create_users.exs
+* creating lib/hello/accounts/accounts.ex
+* injecting lib/hello/accounts/accounts.ex
+* creating test/hello/accounts/accounts_test.exs
+* injecting test/hello/accounts/accounts_test.exs
 
 Remember to update your repository by running migrations:
 
     $ mix ecto.migrate
 ```
 
-Now run migrations:
+Ordinarily we would spend time tweaking the generated migration file (`priv/repo/migrations/<datetime>_create_users.exs`) to add things like non-null constraints and so on, but we don't need to make any changes for this example, so we can just run the migration:
 
 ```console
 $ mix ecto.migrate
+Compiling 2 files (.ex)
+Generated hello app
 [info] == Running Hello.Repo.Migrations.CreateUsers.change/0 forward
 [info] create table users
+[info] create index users_email_index
 [info] == Migrated in 0.0s
 ```
 
+As a final check before we start developing, we can run `mix test` and make sure that all is well.
+
+```console
+$ mix test
+```
+
+All of the tests should pass, but sometimes the database isn't configured properly in `config/test.exs`, or some other issue crops up. It is best to correct these issues now, *before* we complicate things with deliberately breaking tests!
+
 ### Test driving
 
 What we are going for is a controller with the standard CRUD actions. We'll start with our test since we're TDDing this. Create a `user_controller_test.exs` file in `test/hello_web/controllers`
 
 ```elixir
 defmodule HelloWeb.UserControllerTest do
-  use HelloWeb.ConnCase, async: true
+  use HelloWeb.ConnCase
 
 end
 ```
@@ -47,7 +80,7 @@ There are many ways to approach TDD. Here, we will think about each action we wa
 
 ```elixir
 defmodule HelloWeb.UserControllerTest do
-  use HelloWeb.ConnCase, async: true
+  use HelloWeb.ConnCase
 
   test "index/2 responds with all Users"
 
@@ -57,7 +90,7 @@ defmodule HelloWeb.UserControllerTest do
   end
 
   describe "show/2" do
-    test "Responds with a newly created user if the user is found"
+    test "Responds with user info if the user is found"
     test "Responds with a message indicating user not found"
   end
 
@@ -71,53 +104,60 @@ defmodule HelloWeb.UserControllerTest do
 end
 ```
 
-Here we have tests around the 5 controller CRUD actions we need to implement for a typical JSON API. In 2 cases, index and delete, we are only testing the happy path, because in our case they generally won't fail because of domain rules (or lack thereof). In practical application, our delete could fail easily once we have associated resources that cannot leave orphaned resources behind, or number of other situations. On index, we could have filtering and searching to test. Also, both could require authorization.
+Here we have tests around the 5 controller CRUD actions we need to implement for a typical JSON API. At the top of the module we are using the module `HelloWeb.ConnCase`, which provides connections to our test repository. Then we define the 8 tests. In 2 cases, index and delete, we are only testing the happy path, because in our case they generally won't fail because of domain rules (or lack thereof). In practical application, our delete could fail easily once we have associated resources that cannot leave orphaned resources behind, or number of other situations. On index, we could have filtering and searching to test. Also, both could require authorization.
 
 Create, show and update have more typical ways to fail because they need a way to find the resource, which could be non existent, or invalid data was supplied in the params. Since we have multiple tests for each of these endpoints, putting them in a `describe` block is good way to organize our tests.
 
 Let's run the test:
 
 ```console
-$ mix test test/controllers/user_controller_test.exs
+$ mix test test/hello_web/controllers/user_controller_test.exs
 ```
 
 We get 8 failures that say "Not implemented" which is good. Our tests don't have blocks yet.
 
+### The first test
+
 Let's add our first test. We'll start with `index/2`.
 
 ```elixir
 defmodule HelloWeb.UserControllerTest do
-  use HelloWeb.ConnCase, async: true
+  use HelloWeb.ConnCase
+
+  alias Hello.Accounts
 
-  alias Hello.{Repo, Accounts.User}
+  test "index/2 responds with all Users", %{conn: conn} do
 
-  test "index/2 responds with all Users" do
-    users = [ User.changeset(%User{}, %{name: "John", email: "john@example.com"}),
-              User.changeset(%User{}, %{name: "Jane", email: "jane@example.com"}) ]
+    users = [%{name: "John", email: "john@example.com", password: "john pass"},
+             %{name: "Jane", email: "jane@example.com", password: "jane pass"}]
 
-    Enum.each(users, &Repo.insert!(&1))
+    # create users local to this database connection and test
+    [{:ok, user1},{:ok, user2}] = Enum.map(users, &Accounts.create_user(&1))
 
-    response = build_conn()
-    |> get(user_path(build_conn(), :index))
+    response = conn
+    |> get(user_path(conn, :index))
     |> json_response(200)
 
     expected = %{
       "data" => [
-        %{ "name" => "John", "email" => "john@example.com" },
-        %{ "name" => "Jane", "email" => "jane@example.com" }
+        %{ "name" => user1.name, "email" => user1.email },
+        %{ "name" => user2.name, "email" => user2.email }
       ]
     }
 
     assert response == expected
   end
 ```
-Let's take a look at what's going on here. We build our users, and use the `get` function to make a `GET` request to our `UserController` index action, which is piped into `json_response/2` along with the expected HTTP status code. This will return the JSON from the response body, when everything is wired up properly. We represent the JSON we want the controller action to return with the variable `expected`, and assert that the `response` and `expected` are the same.
 
-Our expected data is a JSON response with a top level key of `"data"` containing an array of users that have `"name"` and `"email"` properties.
+Let's take a look at what's going on here. First we alias `Hello.Accounts`, the context module that provides us with our repository manipulation functions. When we use the `HelloWeb.ConnCase` module, it sets things up such that each connection is wrapped in a transaction, *and* all of the database interactions inside of the test use the same database connection and transaction. This module also sets up a `conn` attribute in our ExUnit context, using `Phoenix.ConnCase/build_conn/0`m we pattern match this to use it in each test case. For details, take a look at the file `test/support/conn_case.ex`, as well as the [Ecto documentation for SQL.Sandbox](https://hexdocs.pm/ecto/Ecto.Adapters.SQL.Sandbox.html). We could put a `build_conn/0` call inside of each test, but it is cleaner to use a setup block to do it.
+
+The index test then hooks into the context to extract the contents of the `conn:` key. We then create two users using the `Hello.Accounts.create_user/1` function. Again, note that this function accesses the test repo, but even though we don't pass the `conn` variable to the call, it still uses the same connection and puts these new users inside the same database transaction. Next the `conn` is piped to a `get` function to make a `GET` request to our `UserController` index action, which is in turn piped into `json_response/2` along with the expected HTTP status code. This will return the JSON from the response body, when everything is wired up properly. We represent the JSON we want the controller action to return with the variable `expected`, and assert that the `response` and `expected` are the same.
+
+Our expected data is a JSON response with a top level key of `"data"` containing an array of users that have `"name"` and `"email"` properties that should match the users created before making the request. Also, we do not want the users' "password" properties to show up in our JSON response.
 
 When we run the test we get an error that we have no `user_path` function.
 
-In our router, we'll add a resource for `User` in our API pipe:
+In our router, we'll uncomment the `api` scope at the bottom of the auto-generated file, and then use the resources macro to generate the routes for the "/users" path. Because we aren't going to be generating forms to create and update users, we add the `except: [:new, :edit]` to skip those endpoints.
 
 ```elixir
 defmodule HelloWeb.Router do
@@ -133,29 +173,52 @@ defmodule HelloWeb.Router do
 
   pipeline :api do
     plug :accepts, ["json"]
-    resources "/users", HelloWeb.UserController
   end
 
-  ...
+  scope "/", HelloWeb do
+    pipe_through :browser # Use the default browser stack
+
+    get "/", PageController, :index
+  end
+
+  # Other scopes may use custom stacks.
+  scope "/api", HelloWeb do
+    pipe_through :api
+    resources "/users", UserController, except: [:new, :edit]
+  end
+end
 ```
 
-We should get a new error now. Running the test informs us we don't have a `UserController`. Let's add it, along with the `index/2` action we're testing. Our test description has us returning all users:
+Before running the test again, check out our new paths by running `mix phx.routes`. You should see six new "/api" routes in addition to the default page controller route:
+
+```console
+$ mix phx.routes
+Compiling 6 files (.ex)
+page_path  GET     /               HelloWeb.PageController :index
+user_path  GET     /api/users      HelloWeb.UserController :index
+user_path  GET     /api/users/:id  HelloWeb.UserController :show
+user_path  POST    /api/users      HelloWeb.UserController :create
+user_path  PATCH   /api/users/:id  HelloWeb.UserController :update
+           PUT     /api/users/:id  HelloWeb.UserController :update
+user_path  DELETE  /api/users/:id  HelloWeb.UserController :delete
+```
+
+We should get a new error now. Running the test informs us we don't have a `HelloWeb.UserController`. Let's create that controller by opening the file `lib/hello_web/controllers/user_controller.ex` and adding the `index/2` action we're testing. Our test description has us returning all users:
 
 ```elixir
 defmodule HelloWeb.UserController do
   use HelloWeb, :controller
-
-  alias Hello.{Accounts.User, Repo}
+  alias Hello.Accounts
 
   def index(conn, _params) do
-    users = Repo.all(User)
-    render conn, "index.json", users: users
+    users = Accounts.list_users()
+    render(conn, "index.json", users: users)
   end
 
 end
 ```
 
-When we run the test again, our failing test tells us we have no view. Let's add it. Our test specifies a JSON format with a top key of `"data"`, containing an array of users with attributes `"name"` and `"email"`.
+When we run the test again, our failing test tells us the module `HelloWeb.UserView` is not available. Let's add it by creating the file `lib/hello_web/views/user_view.ex`. Our test specifies a JSON format with a top key of `"data"`, containing an array of users with attributes `"name"` and `"email"`.
 
 ```elixir
 defmodule HelloWeb.UserView do
@@ -172,67 +235,124 @@ defmodule HelloWeb.UserView do
 end
 ```
 
+The view module for the index uses the `render_many/4` function. According to the [documentation](https://hexdocs.pm/phoenix/Phoenix.View.html#render_many/4), using `render_many/4` is "roughly equivalent" to using `Enum.map/2`, and in fact `Enum.map` is called under the hood. The main difference between `render_many/4` and directly calling `Enum.map/2` is that the former benefits from library-quality error checking, properly handling missing values, and so on. `render_many/4` also has an `:as` option that can used so that the key in the assigns map can be renamed. By default, this is inferred from the module name (`:user` in this case), but it can be changed if necessary to fit the render function being used.
+
 And with that, our test passes when we run it.
 
+### Testing the show action
+
 We'll also cover the `show/2` action here so we can see how to handle an error case.
 
 Our show tests currently look like this:
 
 ```elixir
   describe "show/2" do
-    test "Responds with a newly created user if the user is found"
+    test "Responds with user info if the user is found"
     test "Responds with a message indicating user not found"
   end
 ```
 
-Run this test only by running the following command: (if your show tests don't start on line 32, change the line number accordingly)
+Run this test only by running the following command: (if your show tests don't start on line 40, change the line number accordingly)
 
 ```console
-$ mix test test/hello_web/controllers/user_controller_test.exs:32
+$ mix test test/hello_web/controllers/user_controller_test.exs:40
 ```
 
-Our first `show/2` test result is, as expected, not implemented.
-Let's build a test around what we think a successful `show/2` should look like.
+Our first `show/2` test result is, as expected, not implemented. Let's build a test around what we think a successful `show/2` should look like.
 
 ```elixir
-test "Responds with a newly created user if the user is found" do
-  user = User.changeset(%User{}, %{name: "John", email: "john@example.com"})
-  |> Repo.insert!
+test "Responds with user info if the user is found", %{conn: conn} do
+  {:ok, user} = Accounts.create_user(%{name: "John", email: "john@example.com", password: "john pass"})
 
-  response = build_conn()
-  |> get(user_path(build_conn(), :show, user.id))
+  response = conn
+  |> get(user_path(conn, :show, user.id))
   |> json_response(200)
 
-  expected = %{ "data" => %{ "name" => "John", "email" => "john@example.com" } }
+  expected = %{ "data" => %{"email" => user.email, "name" => user.name} }
 
   assert response == expected
 end
 ```
 
-This is very similar to our `index/2` test, except `show/2` requires a user id, and our data is a single JSON object instead of an array.
+This is fine, but it can be refactored slightly. Notice that both this test and the index test need users in the database. Instead of creating these users over and over again, we can instead call another `setup/1` function to populate the database with users on an as-needed basis. To do this, first create a private function at the bottom of the test module as follows:
+
+```elixir
+defp create_user(_) do
+  {:ok, user} = Accounts.create_user(@create_attrs)
+  {:ok, user: user}
+end
+```
+Next define `@create_attrs` as a custom attribute for the module at the top, as follows.
+
+```elixir
+alias Hello.Accounts
+
+@create_attrs %{name: "John", email: "john@example.com", password: "john pass"}
+```
+
+
+Finally, invoke the function using a second `setup/1` call inside of the `describe` block:
+
+```elixir
+describe "show/2" do
+  setup [:create_user]
+  test "Responds with user info if the user is found", %{conn: conn, user: user} do
+
+    response = conn
+    |> get(user_path(conn, :show, user.id))
+    |> json_response(200)
+
+    expected = %{ "data" => %{"email" => user.email, "name" => user.name} }
+
+    assert response == expected
+  end
+  test "Responds with a message indicating user not found"
+end
+```
+
+The functions called by `setup` take an ExUnit context (not to be confused with the contexts we are describing throughout this guide) and allow us to add additional fields when we return. In this case, `create_user` doesn't care about the existing context (hence the underscore parameter), and adds a new user to the ExUnit context under the key `user:`  by returning `{:ok, user: user}`. The test can then access both the database connection and this new user from the ExUnit context.
 
-When we run our test tells us we need a `show/2` action.
+Finally, let's change our `index/2` test to also use the new `create_user` function. The index test doesn't *really* need two users, after all. The revised `index/2` test should look like this:
+
+```elixir
+  describe "index/2" do
+    setup [:create_user]
+    test "index/2 responds with all Users", %{conn: conn, user: user} do
+
+      response = conn
+      |> get(user_path(conn, :index))
+      |> json_response(200)
+
+      expected = %{ "data" => [%{ "name" => user.name, "email" => user.email }] }
+
+      assert response == expected
+    end
+  end
+```
+
+The biggest change here is that we now wrapped the old test inside of another `describe` block so that we have somewhere to put the `setup/2` call for the index test. We are now accessing the user from the ExUnit context, and expecting just a single user from the `index/2` test results, not two.
+
+The `index/2` test should still pass, but the `show/2` test will error with a message that we need a `HelloWeb.UserController.show/2` action. Let's add that to the UserController module next.
 
 ```elixir
 defmodule HelloWeb.UserController do
   use HelloWeb, :controller
-
-  alias Hello.{Accounts.User, Repo}
+  alias Hello.Accounts
 
   def index(conn, _params) do
-    users = Repo.all(User)
-    render conn, "index.json", users: users
+    users = Accounts.list_users()
+    render(conn, "index.json", users: users)
   end
 
   def show(conn, %{"id" => id}) do
-    case Repo.get(User, id) do
-      user -> render conn, "show.json", user: user
-    end
+    user = Accounts.get_user!(id)
+    render(conn, "show.json", user: user)
   end
+
 end
 ```
 
-You'll notice we only handle the case where we successfully find a user. When we TDD we only want to write enough code to make the test pass. We'll add more code when we get to the error handling test for `show/2`.
+You might notice the exclamation point in the `get_user!/1` function. This convention means that this function will throw an error if the requested user is not found. You'll also notice that we aren't properly handling the possibility of a thrown error here. When we TDD we only want to write enough code to make the test pass. We'll add more code when we get to the error handling test for `show/2`.
 
 Running the test tells us we need a `render/2` function that can pattern match on `"show.json"`:
 
@@ -255,47 +375,75 @@ defmodule HelloWeb.UserView do
 end
 ```
 
+Notice the "show.json" rendering path uses `render_one/4` instead of `render_many/4` because it is only rendering a single user, not a list.
+
 When we run the test again, it passes.
 
+### Show when the user is not found
+
 The last item we'll cover is the case where we don't find a user in `show/2`.
 
 Try this one on your own and see what you come up with. One possible solution will be given below.
 
-Walking through our TDD steps, we add a test that supplies a non existent user id to `user_path` which returns a 404 status and an error message:
+Walking through our TDD steps, we add a test that supplies a non-existent user id to `user_path` which returns a 404 status and an error message. One interesting problem here is how we might define a "non-existent" id. We could just pick a large integer, but who's to say some future test won't generate thousands of test users and break our test? Instead of going bigger, we can also go the other way. Database ids tend to start at 1 and increase forever. Negative numbers are perfectly valid integers, and yet never used for database ids. So we'll pick -1 as our "unobtainable" user id, which *should* always fail.
 
 ```elixir
-test "Responds with a message indicating user not found" do
-  response = build_conn()
-  |> get(user_path(build_conn(), :show, 300))
-  |> json_response(404)
+test "Responds with a message indicating user not found", %{conn:  conn} do
+  response =
+    conn
+    |> get(user_path(conn, :show, -1 ))
+    |> json_response(404)
 
-  expected = %{ "error" => "User not found." }
+  expected = %{ "errors" => "User not found." }
 
   assert response == expected
 end
 ```
 
-We want a HTTP code of 404 to notify the requester that this resource was not found, as well as an accompanying error message.
+We want a HTTP code of 404 to notify the requester that this resource was not found, as well as an accompanying error message. You can run this test now to see what happens. You should see that an `Ecto.NoResultsError` is thrown, because there is no such user in the database.
 
-Our controller action:
+Our controller action needs to handle the error thrown by Ecto. We have two choices here. By default, this will be handled by the [phoenix_ecto](https://github.com/phoenixframework/phoenix_ecto) library, returning a 404. However if we want to show a custom error message, we can create a new `get_user/1` function that does not throw an Ecto error. For this example, we'll take the second path and implement a new `get_user/1` function in the file `lib/hello/accounts/accounts.ex`, just before the `get_user!/1` function:
 
 ```elixir
-def show(conn, %{"id" => id}) do
-  case Repo.get(User, id) do
-    nil -> conn |> put_status(404) |> render("error.json")
-    user -> render conn, "show.json", user: user
-  end
-end
+@doc """
+Gets a single `%User{}` from the data store  where the primary key matches the
+given id.
+
+Returns `nil` if no result was found.
+
+## Examples
+
+    iex> get_user(123)
+    %User{}
+
+    iex> get_user(456)
+    nil
+
+"""
+def get_user(id), do: Repo.get(User, id)
 ```
 
-And our view:
+This function is just a thin wrapper around `Ecto.Repo.get/3`, and like that function will return either a `%User{}` if the user is found, or `nil` if not. Next change the `show/2` function to use the non-throwing version, and handle the two possible result cases.
 
 ```elixir
-def render("error.json", _assigns) do
-  %{error: "User not found."}
+def show(conn, %{"id" => id}) do
+  case Accounts.get_user(id) do
+    nil ->
+      conn
+      |> put_status(:not_found)
+      |> json(%{error: "User not found"})
+    user ->
+      render(conn, "show.json", user: user)
+  end
 end
 ```
 
+The second branch of the case statement handles the "happy path" we've already covered.
+
+The first branch of the case statement handles the `nil` result case. First, we use the [`put_status/2`](https://hexdocs.pm/plug/Plug.Conn.html#put_status/2) function from `Plug.Conn` to set the desired error status. The complete list of allowed codes can be found in the [Plug.Conn.Status documentation](https://hexdocs.pm/plug/Plug.Conn.Status.html), where we can see that `:not_found` corresponds to our desired "404" status. We then return a JSON error.
+
 With those implemented, our tests pass.
 
-The rest of the controller is left to you to implement as practice. Happy testing!
+The rest of the controller is left for you to implement as practice. If you are not sure where to begin, it is worth using the Phoenix JSON generator and seeing what tests are automatically generated for you.
+
+Happy testing!
diff --git a/test/mix/tasks/phx.gen.context_test.exs b/test/mix/tasks/phx.gen.context_test.exs
index dd1ffe6c5..c2db08b72 100644
--- a/test/mix/tasks/phx.gen.context_test.exs
+++ b/test/mix/tasks/phx.gen.context_test.exs
@@ -144,7 +144,14 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
         assert file =~ "create unique_index(:posts, [:slug])"
       end
 
+
+      send self(), {:mix_shell_input, :yes?, true}
       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\n2 files in its directory"
+      assert_received {:mix_shell, :yes?, ["Would you like proceed? [Y/n]"]}
+
       assert_file "lib/phoenix/blog/comment.ex", fn file ->
         assert file =~ "field :title, :string"
       end
diff --git a/test/mix/tasks/phx.gen.html_test.exs b/test/mix/tasks/phx.gen.html_test.exs
index 364ebbe4b..c17035c92 100644
--- a/test/mix/tasks/phx.gen.html_test.exs
+++ b/test/mix/tasks/phx.gen.html_test.exs
@@ -132,7 +132,10 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
         refute file =~ ~s(<%= number_input f, :user_id)
       end
 
+      send self(), {:mix_shell_input, :yes?, true}
       Gen.Html.run(~w(Blog Comment comments title:string))
+      assert_received {:mix_shell, :info, ["You are generating into an existing context" <> _]}
+
       assert_file "lib/phoenix/blog/comment.ex"
 
       assert_file "test/phoenix_web/controllers/comment_controller_test.exs", fn file ->
diff --git a/test/phoenix/logger_test.exs b/test/phoenix/logger_test.exs
index a454000f2..23dddfd1c 100644
--- a/test/phoenix/logger_test.exs
+++ b/test/phoenix/logger_test.exs
@@ -72,32 +72,67 @@ defmodule Phoenix.LoggerTest do
     assert output =~ "Parameters: [UNFETCHED]"
   end
 
-  test "filter_values" do
-    assert Phoenix.Logger.filter_values(%{"foo" => "bar", "password" => "should_not_show"}, ["password"]) ==
-           %{"foo" => "bar", "password" => "[FILTERED]"}
-  end
+  describe "filter_values/2 with discard strategy" do
+    test "in top level map" do
+      values = %{"foo" => "bar", "password" => "should_not_show"}
+      assert Phoenix.Logger.filter_values(values, ["password"]) ==
+            %{"foo" => "bar", "password" => "[FILTERED]"}
+    end
 
-  test "filter_values when a map has secret key" do
-    assert Phoenix.Logger.filter_values(%{"foo" => "bar", "map" => %{"password" => "should_not_show"}}, ["password"]) ==
-           %{"foo" => "bar", "map" => %{"password" => "[FILTERED]"}}
-  end
+    test "when a map has secret key" do
+      values = %{"foo" => "bar", "map" => %{"password" => "should_not_show"}}
+      assert Phoenix.Logger.filter_values(values, ["password"]) ==
+            %{"foo" => "bar", "map" => %{"password" => "[FILTERED]"}}
+    end
 
-  test "filter_values when a list has a map with secret" do
-    assert Phoenix.Logger.filter_values(%{"foo" => "bar", "list" => [%{"password" => "should_not_show"}]}, ["password"]) ==
-           %{"foo" => "bar", "list" => [%{"password" => "[FILTERED]"}]}
-  end
+    test "when a list has a map with secret" do
+      values = %{"foo" => "bar", "list" => [%{"password" => "should_not_show"}]}
+      assert Phoenix.Logger.filter_values(values, ["password"]) ==
+            %{"foo" => "bar", "list" => [%{"password" => "[FILTERED]"}]}
+    end
 
-  test "filter_values does not filter structs" do
-    assert Phoenix.Logger.filter_values(%{"foo" => "bar", "file" => %Plug.Upload{}}, ["password"]) ==
-           %{"foo" => "bar", "file" => %Plug.Upload{}}
+    test "does not filter structs" do
+      values = %{"foo" => "bar", "file" => %Plug.Upload{}}
+      assert Phoenix.Logger.filter_values(values, ["password"]) ==
+            %{"foo" => "bar", "file" => %Plug.Upload{}}
+
+      values = %{"foo" => "bar", "file" => %{__struct__: "s"}}
+      assert Phoenix.Logger.filter_values(values, ["password"]) ==
+            %{"foo" => "bar", "file" => %{:__struct__ => "s"}}
+    end
 
-    assert Phoenix.Logger.filter_values(%{"foo" => "bar", "file" => %{__struct__: "s"}}, ["password"]) ==
-           %{"foo" => "bar", "file" => %{:__struct__ => "s"}}
+    test "does not fail on atomic keys" do
+      values = %{:foo => "bar", "password" => "should_not_show"}
+      assert Phoenix.Logger.filter_values(values, ["password"]) ==
+            %{:foo => "bar", "password" => "[FILTERED]"}
+    end
   end
 
-  test "filter_values does not fail on atomic keys" do
-    assert Phoenix.Logger.filter_values(%{:foo => "bar", "password" => "should_not_show"}, ["password"]) ==
-           %{:foo => "bar", "password" => "[FILTERED]"}
+  describe "filter_values/2 with keep strategy" do
+    test "discards values not specified in params" do
+      values = %{"foo" => "bar", "password" => "abc123", "file" => %Plug.Upload{}}
+      assert Phoenix.Logger.filter_values(values, {:keep, []}) ==
+            %{"foo" => "[FILTERED]", "password" => "[FILTERED]", "file" => "[FILTERED]"}
+    end
+
+    test "keeps values that are specified in params" do
+      values = %{"foo" => "bar", "password" => "abc123", "file" => %Plug.Upload{}}
+      assert Phoenix.Logger.filter_values(values, {:keep, ["foo", "file"]}) ==
+            %{"foo" => "bar", "password" => "[FILTERED]", "file" => %Plug.Upload{}}
+    end
+
+    test "keeps all values under keys that are kept" do
+      values = %{"foo" => %{"bar" => 1, "baz" => 2}}
+      assert Phoenix.Logger.filter_values(values, {:keep, ["foo"]}) ==
+            %{"foo" => %{"bar" => 1, "baz" => 2}}
+    end
+
+    test "only filters leaf values" do
+      values = %{"foo" => %{"bar" => 1, "baz" => 2}, "ids" => [1, 2]}
+      assert Phoenix.Logger.filter_values(values, {:keep, []}) ==
+            %{"foo" => %{"bar" => "[FILTERED]", "baz" => "[FILTERED]"},
+              "ids" => ["[FILTERED]", "[FILTERED]"]}
+    end
   end
 
   test "logs phoenix_channel_join as configured by the channel" do