Repository: phoenixframework/phoenix
Pull request #3364 (2019-04-11 09:28:35)

Support `:disposition_type` option in send_download/3
Based on issue discussion:
https://github.com/phoenixframework/phoenix/issues/1786#issuecomment-481631844


Patch:
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
index 1c87e8848..3e04cc8e9 100644
--- a/ISSUE_TEMPLATE.md
+++ b/ISSUE_TEMPLATE.md
@@ -1,7 +1,7 @@
 <!--
 ### Precheck
 
-* For help and support, use Stack Overflow or the Phoenix Talk mailing list
+* For help and support, use Stack Overflow or the Elixir Forum
 * For proposing a new feature, start a discussion on the Phoenix Core mailing list
 * For bugs, do a quick search and make sure the bug has not yet been reported
 * Ensure that this issue is related to the Phoenix library and not one of the dependencies listed in mix.exs (Ecto, Plug, etc.)
diff --git a/RELEASE.md b/RELEASE.md
index f7b7347c7..cc19fcd0f 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -4,12 +4,12 @@
 
   1. Check related deps for required version bumps and compatibility (`phoenix_ecto`, `phoenix_pubsub_redis`, `phoenix_html`)
   2. Bump version in related files below
-  3. Update `phoenix_dep` in `installer/lib/phoenix_new.ex` and `installer/lib/phx_new/generator.ex` to "~> version to be released"
+  3. Update `@phoenix_version` in `installer/lib/phx_new/generator.ex` to "x.y.z"
   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. Start -dev version in related files below
-  8. Update `phoenix_dep` in `installer/lib/phoenix_new.ex` back to git
+  8. Update `phoenix_dep` in `installer/lib/phx_new/generator.ex` back to git
   9. Publish to `npm` with `npm publish`
   10. Replace `master` for `source_url_pattern` in `installer/mix.exs`
 
diff --git a/guides/contexts.md b/guides/contexts.md
index 38c800dc5..45a72887f 100644
--- a/guides/contexts.md
+++ b/guides/contexts.md
@@ -61,8 +61,8 @@ username:string:unique
 * creating priv/repo/migrations/20170629175236_create_users.exs
 * creating lib/hello/accounts.ex
 * injecting lib/hello/accounts.ex
-* creating test/hello/accounts/accounts_test.exs
-* injecting test/hello/accounts/accounts_test.exs
+* creating test/hello/accounts_test.exs
+* injecting test/hello/accounts_test.exs
 
 Add the resource to your browser scope in lib/hello_web/router.ex:
 
@@ -258,7 +258,7 @@ email:string:unique user_id:references:users
 * creating lib/hello/accounts/credential.ex
 * creating priv/repo/migrations/20170629180555_create_credentials.exs
 * injecting lib/hello/accounts.ex
-* injecting test/hello/accounts/accounts_test.exs
+* injecting test/hello/accounts_test.exs
 
 Remember to update your repository by running migrations:
 
diff --git a/installer/lib/mix/tasks/phx.new.ecto.ex b/installer/lib/mix/tasks/phx.new.ecto.ex
index 1a90eb1f9..77478a74d 100644
--- a/installer/lib/mix/tasks/phx.new.ecto.ex
+++ b/installer/lib/mix/tasks/phx.new.ecto.ex
@@ -56,6 +56,6 @@ defmodule Mix.Tasks.Phx.New.Ecto do
       Mix.raise "The ecto task can only be run within an umbrella's apps directory"
     end
 
-    Mix.Tasks.Phx.New.run(args ++ ["--no-webpack", "--ecto"], Phx.New.Ecto)
+    Mix.Tasks.Phx.New.run(args ++ ["--no-webpack", "--ecto"], Phx.New.Ecto, :app_path)
   end
 end
diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex
index 4323889d4..12030f85c 100644
--- a/installer/lib/mix/tasks/phx.new.ex
+++ b/installer/lib/mix/tasks/phx.new.ex
@@ -98,48 +98,52 @@ defmodule Mix.Tasks.Phx.New do
   def run([version]) when version in ~w(-v --version) do
     Mix.shell.info("Phoenix v#{@version}")
   end
+
   def run(argv) do
     elixir_version_check!()
     case parse_opts(argv) do
-      {_opts, []}             -> Mix.Tasks.Help.run(["phx.new"])
+      {_opts, []} ->
+        Mix.Tasks.Help.run(["phx.new"])
+
       {opts, [base_path | _]} ->
         generator = if opts[:umbrella], do: Umbrella, else: Single
-        generate(base_path, generator, opts)
+        generate(base_path, generator, :project_path, opts)
     end
   end
-  def run(argv, generator) do
+
+  def run(argv, generator, path) do
     elixir_version_check!()
     case parse_opts(argv) do
-      {_opts, []}             -> Mix.Tasks.Help.run(["phx.new"])
-      {opts, [base_path | _]} -> generate(base_path, generator, opts)
+      {_opts, []} -> Mix.Tasks.Help.run(["phx.new"])
+      {opts, [base_path | _]} -> generate(base_path, generator, path, opts)
     end
   end
 
-  def generate(base_path, generator, opts) do
+  def generate(base_path, generator, path, opts) do
     base_path
     |> Project.new(opts)
     |> generator.prepare_project()
     |> Generator.put_binding()
-    |> validate_project()
+    |> validate_project(path)
     |> generator.generate()
-    |> prompt_to_install_deps(generator)
+    |> prompt_to_install_deps(generator, path)
   end
 
-  defp validate_project(%Project{opts: opts} = project) do
+  defp validate_project(%Project{opts: opts} = project, path) do
     check_app_name!(project.app, !!opts[:app])
-    check_directory_existence!(project.project_path)
+    check_directory_existence!(Map.fetch!(project, path))
     check_module_name_validity!(project.root_mod)
     check_module_name_availability!(project.root_mod)
 
     project
   end
 
-  defp prompt_to_install_deps(%Project{} = project, generator) do
+  defp prompt_to_install_deps(%Project{} = project, generator, path) do
+    path = Map.fetch!(project, path)
     install? = Mix.shell.yes?("\nFetch and install dependencies?")
+    cd_step = ["$ cd #{relative_app_path(path)}"]
 
-    cd_step = ["$ cd #{relative_app_path(project.project_path)}"]
-
-    maybe_cd(project.project_path, fn ->
+    maybe_cd(path, fn ->
       mix_step = install_mix(project, install?)
 
       compile =
@@ -158,7 +162,7 @@ defmodule Mix.Tasks.Phx.New do
       print_missing_steps(cd_step ++ mix_step ++ webpack_step)
 
       if Project.ecto?(project) do
-        print_ecto_info(project, generator)
+        print_ecto_info(generator)
       end
 
       print_mix_info(generator)
@@ -218,22 +222,16 @@ defmodule Mix.Tasks.Phx.New do
     """
   end
 
-  defp print_ecto_info(%Project{}, Web), do: :ok
-  defp print_ecto_info(%Project{app_path: nil}, _gen), do: :ok
-  defp print_ecto_info(%Project{app_path: app_path} = project, _gen) do
-    config_path =
-      app_path
-      |> Path.join("config/dev.exs")
-      |> Path.relative_to(project.project_path)
-
+  defp print_ecto_info(Web), do: :ok
+  defp print_ecto_info(_gen) do
     Mix.shell.info """
-    Then configure your database in #{config_path} and run:
+    Then configure your database in config/dev.exs and run:
 
         $ mix ecto.create
     """
   end
 
-  defp print_mix_info(gen) when gen in [Ecto] do
+  defp print_mix_info(Ecto) do
     Mix.shell.info """
     You can run your app inside IEx (Interactive Elixir) as:
 
@@ -251,10 +249,11 @@ defmodule Mix.Tasks.Phx.New do
         $ iex -S mix phx.server
     """
   end
+
   defp relative_app_path(path) do
     case Path.relative_to_cwd(path) do
       ^path -> Path.basename(path)
-      rel   -> rel
+      rel -> rel
     end
   end
 
diff --git a/installer/lib/mix/tasks/phx.new.web.ex b/installer/lib/mix/tasks/phx.new.web.ex
index dcac4e922..052c4766d 100644
--- a/installer/lib/mix/tasks/phx.new.web.ex
+++ b/installer/lib/mix/tasks/phx.new.web.ex
@@ -37,6 +37,6 @@ defmodule Mix.Tasks.Phx.New.Web do
       Mix.raise "The web task can only be run within an umbrella's apps directory"
     end
 
-    Mix.Tasks.Phx.New.run(args, Phx.New.Web)
+    Mix.Tasks.Phx.New.run(args, Phx.New.Web, :web_path)
   end
 end
diff --git a/installer/lib/phx_new/ecto.ex b/installer/lib/phx_new/ecto.ex
index 0c0ea927d..5d56a4776 100644
--- a/installer/lib/phx_new/ecto.ex
+++ b/installer/lib/phx_new/ecto.ex
@@ -6,46 +6,30 @@ defmodule Phx.New.Ecto do
   @pre "phx_umbrella/apps/app_name"
 
   template :new, [
-    {:eex,  "#{@pre}/config/config.exs",           :app, "config/config.exs"},
-    {:eex,  "#{@pre}/config/dev.exs",              :app, "config/dev.exs"},
-    {:eex,  "#{@pre}/config/prod.exs",             :app, "config/prod.exs"},
-    {:eex,  "#{@pre}/config/prod.secret.exs",      :app, "config/prod.secret.exs"},
-    {:eex,  "#{@pre}/config/test.exs",             :app, "config/test.exs"},
-    {:eex,  "#{@pre}/lib/app_name/application.ex", :app, "lib/:app/application.ex"},
-    {:eex,  "#{@pre}/lib/app_name.ex",             :app, "lib/:app.ex"},
-    {:eex,  "#{@pre}/test/test_helper.exs",        :app, "test/test_helper.exs"},
-    {:eex,  "#{@pre}/README.md",                   :app, "README.md"},
-    {:eex,  "#{@pre}/mix.exs",                     :app, "mix.exs"},
-    {:eex,  "#{@pre}/gitignore",                   :app, ".gitignore"},
-    {:eex,  "#{@pre}/formatter.exs",               :app, ".formatter.exs"},
-  ]
-
-  template :ecto, [
-    {:eex,  "phx_ecto/repo.ex",              :app, "lib/:app/repo.ex"},
-    {:keep, "phx_ecto/priv/repo/migrations", :app, "priv/repo/migrations"},
-    {:eex,  "phx_ecto/seeds.exs",            :app, "priv/repo/seeds.exs"},
-    {:eex,  "phx_ecto/data_case.ex",         :app, "test/support/data_case.ex"},
-    {:eex,  "phx_ecto/formatter.exs",        :app, "priv/repo/migrations/.formatter.exs"},
+    {:config, "#{@pre}/config/config.exs",           :project, "config/config.exs"},
+    {:eex,    "#{@pre}/lib/app_name/application.ex", :app, "lib/:app/application.ex"},
+    {:eex,    "#{@pre}/lib/app_name.ex",             :app, "lib/:app.ex"},
+    {:eex,    "#{@pre}/test/test_helper.exs",        :app, "test/test_helper.exs"},
+    {:eex,    "#{@pre}/README.md",                   :app, "README.md"},
+    {:eex,    "#{@pre}/mix.exs",                     :app, "mix.exs"},
+    {:eex,    "#{@pre}/gitignore",                   :app, ".gitignore"},
+    {:eex,    "#{@pre}/formatter.exs",               :app, ".formatter.exs"},
   ]
 
   def prepare_project(%Project{} = project) do
-    project_path = Path.expand(project.base_path)
+    app_path = Path.expand(project.base_path)
+    project_path = Path.dirname(Path.dirname(app_path))
 
     %Project{project |
              in_umbrella?: true,
-             app_path: project_path,
+             app_path: app_path,
              project_path: project_path}
   end
 
   def generate(%Project{} = project) do
     copy_from project, __MODULE__, :new
-    if Project.ecto?(project), do: gen_ecto(project)
-
+    if Project.ecto?(project), do: Phx.New.Single.gen_ecto(project)
     project
   end
 
-  defp gen_ecto(project) do
-    copy_from project, __MODULE__, :ecto
-    gen_ecto_config(project)
-  end
 end
diff --git a/installer/lib/phx_new/generator.ex b/installer/lib/phx_new/generator.ex
index 46fa1a083..765b49f7a 100644
--- a/installer/lib/phx_new/generator.ex
+++ b/installer/lib/phx_new/generator.ex
@@ -4,7 +4,6 @@ defmodule Phx.New.Generator do
   alias Phx.New.{Project}
 
   @phoenix Path.expand("../..", __DIR__)
-
   @phoenix_version Version.parse!("1.4.0")
 
   @callback prepare_project(Project.t) :: Project.t
@@ -13,8 +12,8 @@ defmodule Phx.New.Generator do
   defmacro __using__(_env) do
     quote do
       @behaviour unquote(__MODULE__)
-      import unquote(__MODULE__)
       import Mix.Generator
+      import unquote(__MODULE__)
       Module.register_attribute(__MODULE__, :templates, accumulate: true)
       @before_compile unquote(__MODULE__)
     end
@@ -22,23 +21,21 @@ defmodule Phx.New.Generator do
 
   defmacro __before_compile__(env) do
     root = Path.expand("../../templates", __DIR__)
-    templates_ast = for {name, mappings} <- Module.get_attribute(env.module, :templates) do
-      for {format, source, _, _} <- mappings, format != :keep do
-        path = Path.join(root, source)
-        quote do
-          @external_resource unquote(path)
-          def render(unquote(name), unquote(source)), do: unquote(File.read!(path))
+
+    templates_ast =
+      for {name, mappings} <- Module.get_attribute(env.module, :templates) do
+        for {format, source, _, _} <- mappings, format != :keep do
+          path = Path.join(root, source)
+          quote do
+            @external_resource unquote(path)
+            def render(unquote(name), unquote(source)), do: unquote(File.read!(path))
+          end
         end
       end
-    end
 
     quote do
       unquote(templates_ast)
       def template_files(name), do: Keyword.fetch!(@templates, name)
-      # Embed missing files from Phoenix static.
-      embed_text :phoenix_js, from_file: Path.expand("../../templates/phx_assets/phoenix.js", unquote(__DIR__))
-      embed_text :phoenix_png, from_file: Path.expand("../../templates/phx_assets/phoenix.png", unquote(__DIR__))
-      embed_text :phoenix_favicon, from_file: Path.expand("../../templates/phx_assets/favicon.ico", unquote(__DIR__))
     end
   end
 
@@ -58,8 +55,9 @@ defmodule Phx.New.Generator do
           File.mkdir_p!(target)
         :text ->
           create_file(target, mod.render(name, source))
-        :append ->
-          append_to(Path.dirname(target), Path.basename(target), mod.render(name, source))
+        :config ->
+          contents = EEx.eval_string(mod.render(name, source), project.binding, file: source)
+          config_inject(Path.dirname(target), Path.basename(target), contents)
         :eex  ->
           contents = EEx.eval_string(mod.render(name, source), project.binding, file: source)
           create_file(target, contents)
@@ -67,9 +65,29 @@ defmodule Phx.New.Generator do
     end
   end
 
-  def append_to(path, file, contents) do
+  def config_inject(path, file, to_inject) do
     file = Path.join(path, file)
-    File.write!(file, File.read!(file) <> contents)
+
+    contents =
+      case File.read(file) do
+        {:ok, bin} -> bin
+        {:error, _} -> "use Mix.Config\n"
+      end
+
+    with :error <- split_with_self(contents, "use Mix.Config\n"),
+         :error <- split_with_self(contents, "import Config\n") do
+      Mix.raise ~s[Could not find "use Mix.Config" or "import Config" in #{inspect(file)}]
+    else
+      [left, middle, right] ->
+        File.write!(file, [left, middle, ?\n, String.strip(to_inject), ?\n, right])
+    end
+  end
+
+  defp split_with_self(contents, text) do
+    case :binary.split(contents, text) do
+      [left, right] -> [left, text, right]
+      [_] -> :error
+    end
   end
 
   def in_umbrella?(app_path) do
@@ -147,24 +165,21 @@ defmodule Phx.New.Generator do
     Macro.camelize(project.app) != inspect(project.app_mod)
   end
 
-  def gen_ecto_config(%Project{app_path: app_path, binding: binding}) do
+  def gen_ecto_config(%Project{project_path: project_path, binding: binding}) do
     adapter_config = binding[:adapter_config]
 
-    append_to app_path, "config/dev.exs", """
-
+    config_inject project_path, "config/dev.exs", """
     # Configure your database
     config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{kw_to_config adapter_config[:dev]},
       pool_size: 10
     """
 
-    append_to app_path, "config/test.exs", """
-
+    config_inject project_path, "config/test.exs", """
     # Configure your database
     config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{kw_to_config adapter_config[:test]}
     """
 
-    append_to app_path, "config/prod.secret.exs", """
-
+    config_inject project_path, "config/prod.secret.exs", """
     # Configure your database
     config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{kw_to_config adapter_config[:prod]},
       pool_size: 15
diff --git a/installer/lib/phx_new/project.ex b/installer/lib/phx_new/project.ex
index 51bed3019..7ccdcb17a 100644
--- a/installer/lib/phx_new/project.ex
+++ b/installer/lib/phx_new/project.ex
@@ -55,6 +55,7 @@ defmodule Phx.New.Project do
     |> Path.join(path)
     |> expand_path_with_bindings(project)
   end
+
   defp expand_path_with_bindings(path, %Project{} = project) do
     Regex.replace(Mix.Tasks.Phx.New.recompile(~r/:[a-zA-Z0-9_]+/), path, fn ":" <> key, _ ->
         project |> Map.fetch!(:"#{key}") |> to_string()
diff --git a/installer/lib/phx_new/single.ex b/installer/lib/phx_new/single.ex
index 4b14b8f90..073a94e84 100644
--- a/installer/lib/phx_new/single.ex
+++ b/installer/lib/phx_new/single.ex
@@ -36,26 +36,6 @@ defmodule Phx.New.Single do
     {:eex,  "phx_gettext/errors.pot",               :project, "priv/gettext/errors.pot"}
   ]
 
-  template :ecto, [
-    {:eex,  "phx_ecto/repo.ex",              :project, "lib/:app/repo.ex"},
-    {:keep, "phx_ecto/priv/repo/migrations", :project, "priv/repo/migrations"},
-    {:eex,  "phx_ecto/formatter.exs",        :project, "priv/repo/migrations/.formatter.exs"},
-    {:eex,  "phx_ecto/seeds.exs",            :project, "priv/repo/seeds.exs"},
-    {:eex,  "phx_ecto/data_case.ex",         :project, "test/support/data_case.ex"},
-  ]
-
-  template :webpack, [
-    {:eex,  "phx_assets/webpack/webpack.config.js", :project, "assets/webpack.config.js"},
-    {:text,  "phx_assets/webpack/babelrc",          :project, "assets/.babelrc"},
-    {:text, "phx_assets/app.css",                   :project, "assets/css/app.css"},
-    {:text, "phx_assets/phoenix.css",               :project, "assets/css/phoenix.css"},
-    {:eex,  "phx_assets/webpack/app.js",            :project, "assets/js/app.js"},
-    {:eex,  "phx_assets/webpack/socket.js",         :project, "assets/js/socket.js"},
-    {:eex,  "phx_assets/webpack/package.json",      :project, "assets/package.json"},
-    {:text, "phx_assets/robots.txt",                :project, "assets/static/robots.txt"},
-    {:keep, "phx_assets/vendor",                    :project, "assets/vendor"},
-  ]
-
   template :html, [
     {:eex, "phx_web/controllers/page_controller.ex",         :project, "lib/:lib_web_name/controllers/page_controller.ex"},
     {:eex, "phx_web/templates/layout/app.html.eex",          :project, "lib/:lib_web_name/templates/layout/app.html.eex"},
@@ -67,13 +47,33 @@ defmodule Phx.New.Single do
     {:eex, "phx_test/views/page_view_test.exs",              :project, "test/:lib_web_name/views/page_view_test.exs"},
   ]
 
+  template :ecto, [
+    {:eex,  "phx_ecto/repo.ex",              :app, "lib/:app/repo.ex"},
+    {:keep, "phx_ecto/priv/repo/migrations", :app, "priv/repo/migrations"},
+    {:eex,  "phx_ecto/formatter.exs",        :app, "priv/repo/migrations/.formatter.exs"},
+    {:eex,  "phx_ecto/seeds.exs",            :app, "priv/repo/seeds.exs"},
+    {:eex,  "phx_ecto/data_case.ex",         :app, "test/support/data_case.ex"},
+  ]
+
+  template :webpack, [
+    {:eex,  "phx_assets/webpack.config.js", :web, "assets/webpack.config.js"},
+    {:text, "phx_assets/babelrc",           :web, "assets/.babelrc"},
+    {:eex,  "phx_assets/app.js",            :web, "assets/js/app.js"},
+    {:eex,  "phx_assets/socket.js",         :web, "assets/js/socket.js"},
+    {:eex,  "phx_assets/package.json",      :web, "assets/package.json"},
+    {:keep, "phx_assets/vendor",            :web, "assets/vendor"},
+  ]
+
   template :bare, []
 
   template :static, [
-    {:text, "phx_assets/app.css",     :project, "priv/static/css/app.css"},
-    {:text, "phx_assets/phoenix.css", :project, "priv/static/css/phoenix.css"},
-    {:text, "phx_assets/bare/app.js", :project, "priv/static/js/app.js"},
-    {:text, "phx_assets/robots.txt",  :project, "priv/static/robots.txt"},
+    {:text, "phx_static/app.js",      :web, "priv/static/js/app.js"},
+    {:text, "phx_static/app.css",     :web, "priv/static/css/app.css"},
+    {:text, "phx_static/phoenix.css", :web, "priv/static/css/phoenix.css"},
+    {:text, "phx_static/robots.txt",  :web, "priv/static/robots.txt"},
+    {:text, "phx_static/phoenix.js",  :web, "priv/static/js/phoenix.js"},
+    {:text, "phx_static/phoenix.png", :web, "priv/static/images/phoenix.png"},
+    {:text, "phx_static/favicon.ico", :web, "priv/static/favicon.ico"}
   ]
 
   def prepare_project(%Project{app: app} = project) when not is_nil(app) do
@@ -119,29 +119,36 @@ defmodule Phx.New.Single do
     project
   end
 
-  defp gen_html(project) do
+  def gen_html(project) do
     copy_from project, __MODULE__, :html
   end
 
-  defp gen_ecto(project) do
+  def gen_ecto(project) do
     copy_from project, __MODULE__, :ecto
     gen_ecto_config(project)
   end
 
-  defp gen_static(%Project{web_path: web_path} = project) do
+  def gen_static(%Project{} = project) do
     copy_from project, __MODULE__, :static
-    create_file Path.join(web_path, "priv/static/js/phoenix.js"), phoenix_js_text()
-    create_file Path.join(web_path, "priv/static/images/phoenix.png"), phoenix_png_text()
-    create_file Path.join(web_path, "priv/static/favicon.ico"), phoenix_favicon_text()
   end
 
-  defp gen_webpack(%Project{web_path: web_path} = project) do
+  def gen_webpack(%Project{web_path: web_path} = project) do
     copy_from project, __MODULE__, :webpack
-    create_file Path.join(web_path, "assets/static/images/phoenix.png"), phoenix_png_text()
-    create_file Path.join(web_path, "assets/static/favicon.ico"), phoenix_favicon_text()
+
+    statics = %{
+      "phx_static/app.css" => "assets/css/app.css",
+      "phx_static/phoenix.css" => "assets/css/phoenix.css",
+      "phx_static/robots.txt" => "assets/static/robots.txt",
+      "phx_static/phoenix.png" => "assets/static/images/phoenix.png",
+      "phx_static/favicon.ico" => "assets/static/favicon.ico"
+    }
+
+    for {source, target} <- statics do
+      create_file Path.join(web_path, target), render(:static, source)
+    end
   end
 
-  defp gen_bare(%Project{} = project) do
+  def gen_bare(%Project{} = project) do
     copy_from project, __MODULE__, :bare
   end
 end
diff --git a/installer/lib/phx_new/umbrella.ex b/installer/lib/phx_new/umbrella.ex
index 236129582..1c46d37dc 100644
--- a/installer/lib/phx_new/umbrella.ex
+++ b/installer/lib/phx_new/umbrella.ex
@@ -4,14 +4,15 @@ defmodule Phx.New.Umbrella do
   alias Phx.New.{Ecto, Web, Project}
 
   template :new, [
-    {:eex,  "phx_umbrella/gitignore",         :project, ".gitignore"},
-    {:eex,  "phx_umbrella/config/config.exs", :project, "config/config.exs"},
-    {:eex,  "phx_umbrella/config/dev.exs",    :project, "config/dev.exs"},
-    {:eex,  "phx_umbrella/config/test.exs",   :project, "config/test.exs"},
-    {:eex,  "phx_umbrella/config/prod.exs",   :project, "config/prod.exs"},
-    {:eex,  "phx_umbrella/mix.exs",           :project, "mix.exs"},
-    {:eex,  "phx_umbrella/README.md",         :project, "README.md"},
-    {:eex,  "phx_umbrella/formatter.exs",     :project, ".formatter.exs"},
+    {:eex,  "phx_umbrella/gitignore",              :project, ".gitignore"},
+    {:eex,  "phx_umbrella/config/config.exs",      :project, "config/config.exs"},
+    {:eex,  "phx_umbrella/config/dev.exs",         :project, "config/dev.exs"},
+    {:eex,  "phx_umbrella/config/test.exs",        :project, "config/test.exs"},
+    {:eex,  "phx_umbrella/config/prod.exs",        :project, "config/prod.exs"},
+    {:eex,  "phx_umbrella/config/prod.secret.exs", :project, "config/prod.secret.exs"},
+    {:eex,  "phx_umbrella/mix.exs",                :project, "mix.exs"},
+    {:eex,  "phx_umbrella/README.md",              :project, "README.md"},
+    {:eex,  "phx_umbrella/formatter.exs",          :project, ".formatter.exs"},
   ]
 
   def prepare_project(%Project{app: app} = project) when not is_nil(app) do
diff --git a/installer/lib/phx_new/web.ex b/installer/lib/phx_new/web.ex
index 26c8c21b2..21f32f9b4 100644
--- a/installer/lib/phx_new/web.ex
+++ b/installer/lib/phx_new/web.ex
@@ -6,11 +6,11 @@ defmodule Phx.New.Web do
   @pre "phx_umbrella/apps/app_name_web"
 
   template :new, [
-    {:eex,  "#{@pre}/config/config.exs",              :web, "config/config.exs"},
-    {:eex,  "#{@pre}/config/dev.exs",                 :web, "config/dev.exs"},
-    {:eex,  "#{@pre}/config/prod.exs",                :web, "config/prod.exs"},
-    {:eex,  "#{@pre}/config/prod.secret.exs",         :web, "config/prod.secret.exs"},
-    {:eex,  "#{@pre}/config/test.exs",                :web, "config/test.exs"},
+    {:config, "#{@pre}/config/config.exs",            :project, "config/config.exs"},
+    {:config, "#{@pre}/config/dev.exs",               :project, "config/dev.exs"},
+    {:config, "#{@pre}/config/prod.exs",              :project, "config/prod.exs"},
+    {:config, "#{@pre}/config/prod.secret.exs",       :project, "config/prod.secret.exs"},
+    {:config, "#{@pre}/config/test.exs",              :project, "config/test.exs"},
     {:eex,  "#{@pre}/lib/app_name.ex",                :web, "lib/:web_app.ex"},
     {:eex,  "#{@pre}/lib/app_name/application.ex",    :web, "lib/:web_app/application.ex"},
     {:eex,  "phx_web/channels/user_socket.ex",        :web, "lib/:web_app/channels/user_socket.ex"},
@@ -37,18 +37,6 @@ defmodule Phx.New.Web do
     {:eex,  "phx_gettext/errors.pot",               :web, "priv/gettext/errors.pot"}
   ]
 
-  template :webpack, [
-    {:eex,  "phx_assets/webpack/webpack.config.js", :web, "assets/webpack.config.js"},
-    {:text, "phx_assets/webpack/babelrc",           :web, "assets/.babelrc"},
-    {:text, "phx_assets/app.css",                   :web, "assets/css/app.css"},
-    {:text, "phx_assets/phoenix.css",               :web, "assets/css/phoenix.css"},
-    {:eex,  "phx_assets/webpack/app.js",            :web, "assets/js/app.js"},
-    {:eex,  "phx_assets/webpack/socket.js",         :web, "assets/js/socket.js"},
-    {:eex,  "phx_assets/webpack/package.json",      :web, "assets/package.json"},
-    {:text, "phx_assets/robots.txt",                :web, "assets/static/robots.txt"},
-    {:keep, "phx_assets/vendor",                    :web, "assets/vendor"},
-  ]
-
   template :html, [
     {:eex,  "phx_web/controllers/page_controller.ex",         :web, "lib/:web_app/controllers/page_controller.ex"},
     {:eex,  "phx_web/templates/layout/app.html.eex",          :web, "lib/:web_app/templates/layout/app.html.eex"},
@@ -60,22 +48,14 @@ defmodule Phx.New.Web do
     {:eex,  "phx_test/views/page_view_test.exs",              :web, "test/:web_app/views/page_view_test.exs"},
   ]
 
-  template :bare, []
-
-  template :static, [
-    {:text, "phx_assets/app.css",     :web, "priv/static/css/app.css"},
-    {:text, "phx_assets/phoenix.css", :web, "priv/static/css/phoenix.css"},
-    {:text, "phx_assets/bare/app.js", :web, "priv/static/js/app.js"},
-    {:text, "phx_assets/robots.txt",  :web, "priv/static/robots.txt"},
-  ]
-
   def prepare_project(%Project{app: app} = project) when not is_nil(app) do
-    project_path = Path.expand(project.base_path)
+    web_path = Path.expand(project.base_path)
+    project_path = Path.dirname(Path.dirname(web_path))
 
     %Project{project |
              in_umbrella?: true,
              project_path: project_path,
-             web_path: project_path,
+             web_path: web_path,
              web_app: app,
              generators: [context_app: false],
              web_namespace: project.app_mod}
@@ -88,9 +68,9 @@ defmodule Phx.New.Web do
     if Project.html?(project), do: gen_html(project)
 
     case {Project.webpack?(project), Project.html?(project)} do
-      {true, _}      -> gen_webpack(project)
-      {false, true}  -> gen_static(project)
-      {false, false} -> gen_bare(project)
+      {true, _}      -> Phx.New.Single.gen_webpack(project)
+      {false, true}  -> Phx.New.Single.gen_static(project)
+      {false, false} -> Phx.New.Single.gen_bare(project)
     end
 
     project
@@ -99,21 +79,4 @@ defmodule Phx.New.Web do
   defp gen_html(%Project{} = project) do
     copy_from project, __MODULE__, :html
   end
-
-  defp gen_static(%Project{web_path: web_path} = project) do
-    copy_from project, __MODULE__, :static
-    create_file Path.join(web_path, "priv/static/js/phoenix.js"), phoenix_js_text()
-    create_file Path.join(web_path, "priv/static/images/phoenix.png"), phoenix_png_text()
-    create_file Path.join(web_path, "priv/static/favicon.ico"), phoenix_favicon_text()
-  end
-
-  defp gen_webpack(%Project{web_path: web_path} = project) do
-    copy_from project, __MODULE__, :webpack
-    create_file Path.join(web_path, "assets/static/images/phoenix.png"), phoenix_png_text()
-    create_file Path.join(web_path, "assets/static/favicon.ico"), phoenix_favicon_text()
-  end
-
-  defp gen_bare(%Project{} = project) do
-    copy_from project, __MODULE__, :bare
-  end
 end
diff --git a/installer/templates/phx_assets/webpack/app.js b/installer/templates/phx_assets/app.js
similarity index 100%
rename from installer/templates/phx_assets/webpack/app.js
rename to installer/templates/phx_assets/app.js
diff --git a/installer/templates/phx_assets/webpack/babelrc b/installer/templates/phx_assets/babelrc
similarity index 100%
rename from installer/templates/phx_assets/webpack/babelrc
rename to installer/templates/phx_assets/babelrc
diff --git a/installer/templates/phx_assets/webpack/package.json b/installer/templates/phx_assets/package.json
similarity index 100%
rename from installer/templates/phx_assets/webpack/package.json
rename to installer/templates/phx_assets/package.json
diff --git a/installer/templates/phx_assets/webpack/socket.js b/installer/templates/phx_assets/socket.js
similarity index 100%
rename from installer/templates/phx_assets/webpack/socket.js
rename to installer/templates/phx_assets/socket.js
diff --git a/installer/templates/phx_assets/webpack/webpack.config.js b/installer/templates/phx_assets/webpack.config.js
similarity index 100%
rename from installer/templates/phx_assets/webpack/webpack.config.js
rename to installer/templates/phx_assets/webpack.config.js
diff --git a/installer/templates/phx_assets/app.css b/installer/templates/phx_static/app.css
similarity index 100%
rename from installer/templates/phx_assets/app.css
rename to installer/templates/phx_static/app.css
diff --git a/installer/templates/phx_assets/bare/app.js b/installer/templates/phx_static/app.js
similarity index 100%
rename from installer/templates/phx_assets/bare/app.js
rename to installer/templates/phx_static/app.js
diff --git a/installer/templates/phx_assets/favicon.ico b/installer/templates/phx_static/favicon.ico
similarity index 100%
rename from installer/templates/phx_assets/favicon.ico
rename to installer/templates/phx_static/favicon.ico
diff --git a/installer/templates/phx_assets/phoenix.css b/installer/templates/phx_static/phoenix.css
similarity index 100%
rename from installer/templates/phx_assets/phoenix.css
rename to installer/templates/phx_static/phoenix.css
diff --git a/installer/templates/phx_assets/phoenix.js b/installer/templates/phx_static/phoenix.js
similarity index 100%
rename from installer/templates/phx_assets/phoenix.js
rename to installer/templates/phx_static/phoenix.js
diff --git a/installer/templates/phx_assets/phoenix.png b/installer/templates/phx_static/phoenix.png
similarity index 100%
rename from installer/templates/phx_assets/phoenix.png
rename to installer/templates/phx_static/phoenix.png
diff --git a/installer/templates/phx_assets/robots.txt b/installer/templates/phx_static/robots.txt
similarity index 100%
rename from installer/templates/phx_assets/robots.txt
rename to installer/templates/phx_static/robots.txt
diff --git a/installer/templates/phx_umbrella/apps/app_name/config/config.exs b/installer/templates/phx_umbrella/apps/app_name/config/config.exs
index e88b16afd..eaaf50309 100644
--- a/installer/templates/phx_umbrella/apps/app_name/config/config.exs
+++ b/installer/templates/phx_umbrella/apps/app_name/config/config.exs
@@ -1,11 +1,4 @@
-# Since configuration is shared in umbrella projects, this file
-# should only configure the :<%= app_name %> application itself
-# and only for organization purposes. All other config goes to
-# the umbrella root.
-use Mix.Config<%= if namespaced? || ecto do %>
-
+<%= if namespaced? || ecto do %># Configure Mix tasks and generators
 config :<%= app_name %><%= if namespaced? do %>,
   namespace: <%= app_module %><% end %><%= if ecto do %>,
   ecto_repos: [<%= app_module %>.Repo]<% end %><% end %>
-
-import_config "#{Mix.env()}.exs"
diff --git a/installer/templates/phx_umbrella/apps/app_name/config/dev.exs b/installer/templates/phx_umbrella/apps/app_name/config/dev.exs
deleted file mode 100644
index 8cd2bc327..000000000
--- a/installer/templates/phx_umbrella/apps/app_name/config/dev.exs
+++ /dev/null
@@ -1,5 +0,0 @@
-# Since configuration is shared in umbrella projects, this file
-# should only configure the :<%= app_name %> application itself
-# and only for organization purposes. All other config goes to
-# the umbrella root.
-use Mix.Config
diff --git a/installer/templates/phx_umbrella/apps/app_name/config/prod.exs b/installer/templates/phx_umbrella/apps/app_name/config/prod.exs
deleted file mode 100644
index 8873b3767..000000000
--- a/installer/templates/phx_umbrella/apps/app_name/config/prod.exs
+++ /dev/null
@@ -1,7 +0,0 @@
-# Since configuration is shared in umbrella projects, this file
-# should only configure the :<%= app_name %> application itself
-# and only for organization purposes. All other config goes to
-# the umbrella root.
-use Mix.Config
-
-import_config "prod.secret.exs"
diff --git a/installer/templates/phx_umbrella/apps/app_name/config/prod.secret.exs b/installer/templates/phx_umbrella/apps/app_name/config/prod.secret.exs
deleted file mode 100644
index 874f1a539..000000000
--- a/installer/templates/phx_umbrella/apps/app_name/config/prod.secret.exs
+++ /dev/null
@@ -1,10 +0,0 @@
-use Mix.Config
-
-# In this file, we keep production configuration that
-# you'll likely want to automate and keep away from
-# your version control system.
-#
-# You should document the content of this
-# file or create a script for recreating it, since it's
-# kept out of version control and might be hard to recover
-# or recreate for your teammates (or yourself later on).
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs
index e52c0bd93..b08e92407 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/config.exs
@@ -1,10 +1,4 @@
-# Since configuration is shared in umbrella projects, this file
-# should only configure the :<%= web_app_name %> application itself
-# and only for organization purposes. All other config goes to
-# the umbrella root.
-use Mix.Config
-
-<%= if namespaced? || ecto || generators do %># General application configuration
+<%= if namespaced? || ecto || generators do %>
 config :<%= web_app_name %><%= if namespaced? do %>,
   namespace: <%= web_namespace %><% end %><%= if ecto do %>,
   ecto_repos: [<%= app_module %>.Repo]<% end %><%= if generators do %>,
@@ -16,7 +10,3 @@ config :<%= web_app_name %>, <%= endpoint_module %>,
   secret_key_base: "<%= secret_key_base %>",
   render_errors: [view: <%= web_namespace %>.ErrorView, accepts: ~w(<%= if html do %>html <% end %>json), layout: false],
   pubsub: [name: <%= web_namespace %>.PubSub, adapter: Phoenix.PubSub.PG2]
-
-# Import environment specific config. This must remain at the bottom
-# of this file so it overrides the configuration defined above.
-import_config "#{Mix.env()}.exs"
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs
index f00851b5b..f9bdc93f2 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs
@@ -1,9 +1,3 @@
-# Since configuration is shared in umbrella projects, this file
-# should only configure the :<%= web_app_name %> application itself
-# and only for organization purposes. All other config goes to
-# the umbrella root.
-use Mix.Config
-
 # For development, we disable any cache and enable
 # debugging and code reloading.
 #
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.exs
index 67688df68..4dc5b3366 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.exs
@@ -1,9 +1,3 @@
-# Since configuration is shared in umbrella projects, this file
-# should only configure the :<%= web_app_name %> application itself
-# and only for organization purposes. All other config goes to
-# the umbrella root.
-use Mix.Config
-
 # For production, don't forget to configure the url host
 # to something meaningful, Phoenix uses this information
 # when generating URLs.
@@ -66,7 +60,3 @@ config :<%= web_app_name %>, <%= endpoint_module %>,
 #
 # Note you can't rely on `System.get_env/1` when using releases.
 # See the releases documentation accordingly.
-
-# Finally import the config/prod.secret.exs which should be versioned
-# separately.
-import_config "prod.secret.exs"
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs
index 14f600146..fce3afa4b 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/prod.secret.exs
@@ -1,12 +1,2 @@
-use Mix.Config
-
-# In this file, we keep production configuration that
-# you'll likely want to automate and keep away from
-# your version control system.
-#
-# You should document the content of this
-# file or create a script for recreating it, since it's
-# kept out of version control and might be hard to recover
-# or recreate for your teammates (or yourself later on).
 config :<%= web_app_name %>, <%= endpoint_module %>,
   secret_key_base: "<%= prod_secret_key_base %>"
diff --git a/installer/templates/phx_umbrella/config/config.exs b/installer/templates/phx_umbrella/config/config.exs
index 4a095e2db..0284598b0 100644
--- a/installer/templates/phx_umbrella/config/config.exs
+++ b/installer/templates/phx_umbrella/config/config.exs
@@ -1,17 +1,14 @@
-# This file is responsible for configuring your application
-# and its dependencies with the aid of the Mix.Config module.
+# This file is responsible for configuring your umbrella
+# and **all applications** and their dependencies with the
+# help of Mix.Config.
+#
+# Note that all applications in your umbrella share the
+# same configuration and dependencies, which is why they
+# all use the same configuration file. If you want different
+# configurations or dependencies per app, it is best to
+# move said applications out of the umbrella.
 use Mix.Config
 
-# By default, the umbrella project as well as each child
-# application will require this configuration file, as
-# configuration and dependencies are shared in an umbrella
-# project. While one could configure all applications here,
-# we prefer to keep the configuration of each individual
-# child application in their own app, but all other
-# dependencies, regardless if they belong to one or multiple
-# apps, should be configured in the umbrella to avoid confusion.
-import_config "../apps/*/config/config.exs"
-
 # Configures Elixir's Logger
 config :logger, :console,
   format: "$time $metadata[$level] $message\n",
diff --git a/installer/templates/phx_umbrella/config/prod.secret.exs b/installer/templates/phx_umbrella/config/prod.secret.exs
new file mode 100644
index 000000000..e2d45c783
--- /dev/null
+++ b/installer/templates/phx_umbrella/config/prod.secret.exs
@@ -0,0 +1,12 @@
+# In this file, we keep production configuration that
+# you'll likely want to automate and keep away from
+# your version control system.
+#
+# You should document the content of this file or create
+# a script for recreating it, since it's kept out of version
+# control and might be hard to recover or recreate for your
+# teammates (or yourself later on).
+#
+# Alternatively, you can keep this file versioned and use
+# environment variables instead.
+use Mix.Config
diff --git a/lib/mix/phoenix/context.ex b/lib/mix/phoenix/context.ex
index ffc45bd42..a80b914f3 100644
--- a/lib/mix/phoenix/context.ex
+++ b/lib/mix/phoenix/context.ex
@@ -31,7 +31,7 @@ defmodule Mix.Phoenix.Context do
     dir       = Mix.Phoenix.context_lib_path(ctx_app, basedir)
     file      = dir <> ".ex"
     test_dir  = Mix.Phoenix.context_test_path(ctx_app, basedir)
-    test_file = Path.join([test_dir, basename <> "_test.exs"])
+    test_file = test_dir <> "_test.exs"
     generate? = Keyword.get(opts, :context, true)
 
     %Context{
diff --git a/lib/mix/tasks/phx.server.ex b/lib/mix/tasks/phx.server.ex
index 1666da28e..426f5291f 100644
--- a/lib/mix/tasks/phx.server.ex
+++ b/lib/mix/tasks/phx.server.ex
@@ -6,6 +6,9 @@ defmodule Mix.Tasks.Phx.Server do
   @moduledoc """
   Starts the application by configuring all endpoints servers to run.
 
+  Note: to start the endpoint without using this mix task you must set
+  `server: true` in your `Phoenix.Endpoint` configuration.
+
   ## Command line options
 
   This task accepts the same command-line arguments as `run`.
diff --git a/lib/phoenix/controller.ex b/lib/phoenix/controller.ex
index bde5b6c58..37af085b1 100644
--- a/lib/phoenix/controller.ex
+++ b/lib/phoenix/controller.ex
@@ -894,6 +894,11 @@ defmodule Phoenix.Controller do
     * `:content_type` - the content type of the file or binary
       sent as download. It is automatically inferred from the
       filename extension
+    * `:disposition` - specifies dispositon type
+      (`:attachment` or `:inline`). If `:attachment` was used,
+      user will be prompted to save the file. If `:inline` was used,
+      the browser will attempt to open the file.
+      Defaults to `:attachment`.
     * `:charset` - the charset of the file, such as "utf-8".
       Defaults to none
     * `:offset` - the bytes to offset when reading. Defaults to `0`
@@ -945,15 +950,20 @@ defmodule Phoenix.Controller do
   defp prepare_send_download(conn, filename, opts) do
     content_type = opts[:content_type] || MIME.from_path(filename)
     encoded_filename = encode_filename(filename, Keyword.get(opts, :encode, true))
+    disposition_type = get_disposition_type(Keyword.get(opts, :disposition, :attachment))
     warn_if_ajax(conn)
     conn
     |> put_resp_content_type(content_type, opts[:charset])
-    |> put_resp_header("content-disposition", ~s[attachment; filename="#{encoded_filename}"])
+    |> put_resp_header("content-disposition", ~s[#{disposition_type}; filename="#{encoded_filename}"])
   end
 
   defp encode_filename(filename, false), do: filename
   defp encode_filename(filename, true), do: URI.encode_www_form(filename)
 
+  defp get_disposition_type(:attachment), do: "attachment"
+  defp get_disposition_type(:inline), do: "inline"
+  defp get_disposition_type(other), do: raise ArgumentError, "expected :disposition to be :attachment or :inline, got: #{inspect(other)}"
+
   defp ajax?(conn) do
     case get_req_header(conn, "x-requested-with") do
       [value] -> value in ["XMLHttpRequest", "xmlhttprequest"]
@@ -1273,24 +1283,7 @@ defmodule Phoenix.Controller do
   @doc """
   Fetches the flash storage.
   """
-  def fetch_flash(conn, _opts \\ []) do
-    session_flash = get_session(conn, "phoenix_flash")
-    conn = persist_flash(conn, session_flash || %{})
-
-    register_before_send conn, fn conn ->
-      flash = conn.private.phoenix_flash
-      flash_size = map_size(flash)
-
-      cond do
-        is_nil(session_flash) and flash_size == 0 ->
-          conn
-        flash_size > 0 and conn.status in 300..308 ->
-          put_session(conn, "phoenix_flash", flash)
-        true ->
-          delete_session(conn, "phoenix_flash")
-      end
-    end
-  end
+  defdelegate fetch_flash(conn, opts \\ []), to: Phoenix.Controller.Flash
 
   @doc """
   Persists a value in flash.
@@ -1304,9 +1297,7 @@ defmodule Phoenix.Controller do
       "Welcome Back!"
 
   """
-  def put_flash(conn, key, message) do
-    persist_flash(conn, Map.put(get_flash(conn), flash_key(key), message))
-  end
+  defdelegate put_flash(conn, key, message), to: Phoenix.Controller.Flash
 
   @doc """
   Returns a map of previously set flash messages or an empty map.
@@ -1321,10 +1312,7 @@ defmodule Phoenix.Controller do
       %{"info" => "Welcome Back!"}
 
   """
-  def get_flash(conn) do
-    Map.get(conn.private, :phoenix_flash) ||
-      raise ArgumentError, message: "flash not fetched, call fetch_flash/2"
-  end
+  defdelegate get_flash(conn), to: Phoenix.Controller.Flash
 
   @doc """
   Returns a message from flash by `key`.
@@ -1336,9 +1324,7 @@ defmodule Phoenix.Controller do
       "Welcome Back!"
 
   """
-  def get_flash(conn, key) do
-    get_flash(conn)[flash_key(key)]
-  end
+  defdelegate get_flash(conn, key), to: Phoenix.Controller.Flash
 
   @doc """
   Generates a status message from the template name.
@@ -1364,16 +1350,7 @@ defmodule Phoenix.Controller do
   @doc """
   Clears all flash messages.
   """
-  def clear_flash(conn) do
-    persist_flash(conn, %{})
-  end
-
-  defp flash_key(binary) when is_binary(binary), do: binary
-  defp flash_key(atom) when is_atom(atom), do: Atom.to_string(atom)
-
-  defp persist_flash(conn, value) do
-    put_private(conn, :phoenix_flash, value)
-  end
+  defdelegate clear_flash(conn), to: Phoenix.Controller.Flash
 
   @doc """
   Returns the current request path with its default query parameters:
diff --git a/lib/phoenix/controller/flash.ex b/lib/phoenix/controller/flash.ex
new file mode 100644
index 000000000..d7035cf93
--- /dev/null
+++ b/lib/phoenix/controller/flash.ex
@@ -0,0 +1,56 @@
+defmodule Phoenix.Controller.Flash do
+  import Plug.Conn
+
+  @moduledoc false
+
+  @session_key "phoenix_flash"
+  @session_atom :phoenix_flash
+
+  @doc false
+  def fetch_flash(conn, _opts \\ []) do
+    found_flash = get_session(conn, @session_key)
+    conn = persist_flash(conn, found_flash || %{})
+
+    register_before_send conn, fn conn ->
+      flash = Map.fetch!(conn.private, @session_atom)
+      flash_size = map_size(flash)
+
+      cond do
+        is_nil(found_flash) and flash_size == 0 ->
+          conn
+        flash_size > 0 and conn.status in 300..308 ->
+          put_session(conn, @session_key, flash)
+        true ->
+          delete_session(conn, @session_key)
+      end
+    end
+  end
+
+  @doc false
+  def clear_flash(conn) do
+    persist_flash(conn, %{})
+  end
+
+  @doc false
+  def put_flash(conn, key, message) do
+    persist_flash(conn, Map.put(get_flash(conn), flash_key(key), message))
+  end
+
+  @doc false
+  def get_flash(conn) do
+    Map.get(conn.private, @session_atom) ||
+      raise ArgumentError, "flash not fetched, call fetch_flash/2"
+  end
+
+  def get_flash(conn, key) do
+    get_flash(conn)[flash_key(key)]
+  end
+
+  defp persist_flash(conn, value) do
+    put_private(conn, @session_atom, value)
+  end
+
+  defp flash_key(binary) when is_binary(binary), do: binary
+  defp flash_key(atom) when is_atom(atom), do: Atom.to_string(atom)
+
+end
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index c4030ce47..07986ed59 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -922,6 +922,9 @@ defmodule Phoenix.Endpoint do
 
     * `:max_frame_size` - the maximum allowed frame size in bytes.
       Supported from Cowboy 2.3 onwards, defaults to "infinity"
+      
+    * `:compress` - whether to enable per message compresssion on
+      all data frames, defaults to false
 
   ## Longpoll configuration
 


Test patch:
diff --git a/installer/templates/phx_umbrella/apps/app_name/config/test.exs b/installer/templates/phx_umbrella/apps/app_name/config/test.exs
deleted file mode 100644
index 8cd2bc327..000000000
--- a/installer/templates/phx_umbrella/apps/app_name/config/test.exs
+++ /dev/null
@@ -1,5 +0,0 @@
-# Since configuration is shared in umbrella projects, this file
-# should only configure the :<%= app_name %> application itself
-# and only for organization purposes. All other config goes to
-# the umbrella root.
-use Mix.Config
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/config/test.exs b/installer/templates/phx_umbrella/apps/app_name_web/config/test.exs
index 40f0ce1d9..81892bfa1 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/config/test.exs
+++ b/installer/templates/phx_umbrella/apps/app_name_web/config/test.exs
@@ -1,9 +1,3 @@
-# Since configuration is shared in umbrella projects, this file
-# should only configure the :<%= web_app_name %> application itself
-# and only for organization purposes. All other config goes to
-# the umbrella root.
-use Mix.Config
-
 # We don't run a server during test. If one is required,
 # you can enable the server option below.
 config :<%= web_app_name %>, <%= endpoint_module %>,
diff --git a/installer/test/mix_helper.exs b/installer/test/mix_helper.exs
index ee27cf0ff..ad9a458ba 100644
--- a/installer/test/mix_helper.exs
+++ b/installer/test/mix_helper.exs
@@ -55,10 +55,15 @@ defmodule MixHelper do
 
     try do
       apps_path = Path.join(path, "apps")
+      config_path = Path.join(path, "config")
       File.rm_rf!(path)
       File.mkdir_p!(path)
       File.mkdir_p!(apps_path)
+      File.mkdir_p!(config_path)
       File.touch!(Path.join(path, "mix.exs"))
+      for file <- ~w(config.exs dev.exs test.exs prod.exs prod.secret.exs) do
+        File.write!(Path.join(config_path, file), "use Mix.Config\n")
+      end
       File.cd!(apps_path, function)
     after
       Application.put_env(:phoenix, :generators, conf_before)
diff --git a/installer/test/phx_new_umbrella_test.exs b/installer/test/phx_new_umbrella_test.exs
index 474a7383a..7fc5c667d 100644
--- a/installer/test/phx_new_umbrella_test.exs
+++ b/installer/test/phx_new_umbrella_test.exs
@@ -54,24 +54,21 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       end
 
       assert_file root_path(@app, "config/config.exs"), fn file ->
-        assert file =~ ~S[import_config "../apps/*/config/config.exs"]
         assert file =~ ~S[import_config "#{Mix.env()}.exs"]
         assert file =~ "config :phoenix, :json_library, Jason"
-      end
-
-      assert_file app_path(@app, "config/config.exs"), fn file ->
         assert file =~ "ecto_repos: [PhxUmb.Repo]"
+        assert file =~ ":phx_umb_web, PhxUmbWeb.Endpoint"
+        assert file =~ "generators: [context_app: :phx_umb]\n"
         refute file =~ "namespace"
-        refute file =~ "config :phx_blog_web, :generators"
       end
 
-      assert_file web_path(@app, "config/config.exs"), fn file ->
-        assert file =~ "ecto_repos: [PhxUmb.Repo]"
-        assert file =~ ":phx_umb_web, PhxUmbWeb.Endpoint"
-        assert file =~ "generators: [context_app: :phx_umb]\n"
+      assert_file root_path(@app, "config/dev.exs"), fn file ->
+        assert file =~ ~r/watchers: \[\s+node:/
+        assert file =~ "lib/#{@app}_web/{live,views}/.*(ex)"
+        assert file =~ "lib/#{@app}_web/templates/.*(eex)"
       end
 
-      assert_file web_path(@app, "config/prod.exs"), fn file ->
+      assert_file root_path(@app, "config/prod.exs"), fn file ->
         assert file =~ "port: 80"
         assert file =~ ":inet6"
       end
@@ -130,11 +127,6 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert_file( web_path(@app, ".gitignore"),  ~r/\n$/)
       assert_file web_path(@app, "assets/webpack.config.js"), "js/app.js"
       assert_file web_path(@app, "assets/.babelrc"), "env"
-      assert_file web_path(@app, "config/dev.exs"), fn file ->
-        assert file =~ ~r/watchers: \[\s+node:/
-        assert file =~ "lib/#{@app}_web/{live,views}/.*(ex)"
-        assert file =~ "lib/#{@app}_web/templates/.*(eex)"
-      end
       assert_file web_path(@app, "assets/static/favicon.ico")
       assert_file web_path(@app, "assets/static/images/phoenix.png")
       assert_file web_path(@app, "assets/css/app.css")
@@ -173,6 +165,10 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
 
       # Ecto
       config = ~r/config :phx_umb, PhxUmb.Repo,/
+      assert_file root_path(@app, "config/dev.exs"), config
+      assert_file root_path(@app, "config/test.exs"), config
+      assert_file root_path(@app, "config/prod.secret.exs"), config
+
       assert_file app_path(@app, "mix.exs"), fn file ->
         assert file =~ "aliases: aliases()"
         assert file =~ "ecto.setup"
@@ -180,9 +176,6 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
         assert file =~ "{:jason,"
       end
 
-      assert_file app_path(@app, "config/dev.exs"), config
-      assert_file app_path(@app, "config/test.exs"), config
-      assert_file app_path(@app, "config/prod.secret.exs"), config
       assert_file app_path(@app, "lib/#{@app}/repo.ex"), ~r"defmodule PhxUmb.Repo"
       assert_file app_path(@app, "priv/repo/seeds.exs"), ~r"PhxUmb.Repo.insert!"
       assert_file app_path(@app, "test/support/data_case.ex"), ~r"defmodule PhxUmb.DataCase"
@@ -196,7 +189,7 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert msg =~ "$ cd phx_umb"
       assert msg =~ "$ mix deps.get"
 
-      assert_received {:mix_shell, :info, ["Then configure your database in apps/phx_umb/config/dev.exs" <> _]}
+      assert_received {:mix_shell, :info, ["Then configure your database in config/dev.exs" <> _]}
       assert_received {:mix_shell, :info, ["Start your Phoenix app" <> _]}
 
       # Channels
@@ -216,9 +209,12 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       Mix.Tasks.Phx.New.run([@app, "--umbrella", "--no-html", "--no-webpack", "--no-ecto"])
 
       # No webpack
-      refute File.read!(web_path(@app, ".gitignore")) |> String.contains?("/assets/node_modules/")
-      assert_file( web_path(@app, ".gitignore"),  ~r/\n$/)
-      assert_file web_path(@app, "config/dev.exs"), ~r/watchers: \[\]/
+      assert_file web_path(@app, ".gitignore"), fn file ->
+        assert file =~ ~r/\n$/
+        refute file =~ "/assets/node_modules/"
+      end
+
+      assert_file root_path(@app, "config/dev.exs"), ~r/watchers: \[\]/
 
       # No webpack & No HTML
       refute_file web_path(@app, "priv/static/css/app.css")
@@ -234,19 +230,14 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
 
       assert_file app_path(@app, "mix.exs"), &refute(&1 =~ ~r":phoenix_ecto")
 
-      assert_file app_path(@app, "config/config.exs"), fn file ->
+      assert_file root_path(@app, "config/config.exs"), fn file ->
         refute file =~ "config :phx_blog_web, :generators"
         refute file =~ "ecto_repos:"
       end
-      assert_file web_path(@app, "config/config.exs"), fn file ->
-        refute file =~ "config :phx_blog_web, :generators"
-      end
 
-      assert_file web_path(@app, "config/dev.exs"), fn file ->
-        refute file =~ config
-      end
-      assert_file web_path(@app, "config/test.exs"), &refute(&1 =~ config)
-      assert_file web_path(@app, "config/prod.secret.exs"), &refute(&1 =~ config)
+      assert_file root_path(@app, "config/dev.exs"), &refute(&1 =~ config)
+      assert_file root_path(@app, "config/test.exs"), &refute(&1 =~ config)
+      assert_file root_path(@app, "config/prod.secret.exs"), &refute(&1 =~ config)
 
       assert_file app_path(@app, "lib/#{@app}/application.ex"), ~r/Supervisor.start_link\(/
 
@@ -292,7 +283,8 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
   test "new with binary_id" do
     in_tmp "new with binary_id", fn ->
       Mix.Tasks.Phx.New.run([@app, "--umbrella", "--binary-id"])
-      assert_file web_path(@app, "config/config.exs"), ~r/generators: \[context_app: :phx_umb, binary_id: true\]/
+      assert_file root_path(@app, "config/config.exs"),
+                  ~r/generators: \[context_app: :phx_umb, binary_id: true\]/
     end
   end
 
@@ -305,11 +297,12 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       assert_file "phxUmb_umbrella/apps/phxUmb/mix.exs", fn file ->
         assert file =~ "app: :phxUmb"
       end
+
       assert_file "phxUmb_umbrella/apps/phxUmb_web/mix.exs", fn file ->
         assert file =~ "app: :phxUmb_web"
       end
 
-      assert_file "phxUmb_umbrella/apps/phxUmb/config/dev.exs", fn file ->
+      assert_file "phxUmb_umbrella/config/dev.exs", fn file ->
         assert file =~ ~r/config :phxUmb, PhxUmb.Repo,/
         assert file =~ "database: \"phxumb_dev\""
       end
@@ -323,13 +316,13 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
 
       assert_file "custom_path_umbrella/apps/phx_umb/mix.exs", ~r/app: :phx_umb/
       assert_file "custom_path_umbrella/apps/phx_umb_web/lib/phx_umb_web/endpoint.ex", ~r/app: :#{@app}_web/
-      assert_file "custom_path_umbrella/apps/phx_umb_web/config/config.exs", ~r/namespace: PhoteuxBlogWeb/
       assert_file "custom_path_umbrella/apps/phx_umb_web/lib/#{@app}_web.ex", ~r/use Phoenix.Controller, namespace: PhoteuxBlogWeb/
       assert_file "custom_path_umbrella/apps/phx_umb/lib/phx_umb/application.ex", ~r/defmodule PhoteuxBlog.Application/
       assert_file "custom_path_umbrella/apps/phx_umb/mix.exs", ~r/mod: {PhoteuxBlog.Application, \[\]}/
       assert_file "custom_path_umbrella/apps/phx_umb_web/lib/phx_umb_web/application.ex", ~r/defmodule PhoteuxBlogWeb.Application/
       assert_file "custom_path_umbrella/apps/phx_umb_web/mix.exs", ~r/mod: {PhoteuxBlogWeb.Application, \[\]}/
-      assert_file "custom_path_umbrella/apps/phx_umb/config/config.exs", ~r/namespace: PhoteuxBlog/
+      assert_file "custom_path_umbrella/config/config.exs", ~r/namespace: PhoteuxBlogWeb/
+      assert_file "custom_path_umbrella/config/config.exs", ~r/namespace: PhoteuxBlog/
     end
   end
 
@@ -352,11 +345,12 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       Mix.Tasks.Phx.New.run([project_path, "--umbrella"])
 
       assert_file app_path(app, "mix.exs"), ":postgrex"
-      assert_file app_path(app, "config/dev.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/, ~r/hostname: "localhost"/]
-      assert_file app_path(app, "config/test.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/, ~r/hostname: "localhost"/]
-      assert_file app_path(app, "config/prod.secret.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/]
       assert_file app_path(app, "lib/custom_path/repo.ex"), "Ecto.Adapters.Postgres"
 
+      assert_file root_path(app, "config/dev.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/, ~r/hostname: "localhost"/]
+      assert_file root_path(app, "config/test.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/, ~r/hostname: "localhost"/]
+      assert_file root_path(app, "config/prod.secret.exs"), [~r/username: "postgres"/, ~r/password: "postgres"/]
+
       assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
       assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
     end
@@ -369,11 +363,12 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
       Mix.Tasks.Phx.New.run([project_path, "--umbrella", "--database", "mysql"])
 
       assert_file app_path(app, "mix.exs"), ":myxql"
-      assert_file app_path(app, "config/dev.exs"), [~r/username: "root"/, ~r/password: ""/]
-      assert_file app_path(app, "config/test.exs"), [~r/username: "root"/, ~r/password: ""/]
-      assert_file app_path(app, "config/prod.secret.exs"), [~r/username: "root"/, ~r/password: ""/]
       assert_file app_path(app, "lib/custom_path/repo.ex"), "Ecto.Adapters.MyXQL"
 
+      assert_file root_path(app, "config/dev.exs"), [~r/username: "root"/, ~r/password: ""/]
+      assert_file root_path(app, "config/test.exs"), [~r/username: "root"/, ~r/password: ""/]
+      assert_file root_path(app, "config/prod.secret.exs"), [~r/username: "root"/, ~r/password: ""/]
+
       assert_file web_path(app, "test/support/conn_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
       assert_file web_path(app, "test/support/channel_case.ex"), "Ecto.Adapters.SQL.Sandbox.checkout"
     end
@@ -474,11 +469,11 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
           assert file =~ "lockfile: \"../../mix.lock\""
         end
 
-        assert_file "another/config/config.exs", fn file ->
+        assert_file "../config/config.exs", fn file ->
           assert file =~ "ecto_repos: [Another.Repo]"
         end
 
-        assert_file "another/config/prod.exs", fn file ->
+        assert_file "../config/prod.exs", fn file ->
           assert file =~ "port: 80"
           assert file =~ ":inet6"
         end
@@ -511,7 +506,6 @@ defmodule Mix.Tasks.Phx.New.UmbrellaTest do
         assert_file "another/.gitignore",  ~r/\n$/
         assert_file "another/assets/webpack.config.js", "js/app.js"
         assert_file "another/assets/.babelrc", "env"
-        assert_file "another/config/dev.exs", ~r/watchers: \[\s+node:/
         assert_file "another/assets/static/favicon.ico"
         assert_file "another/assets/static/images/phoenix.png"
         assert_file "another/assets/css/app.css"
diff --git a/installer/test/phx_new_web_test.exs b/installer/test/phx_new_web_test.exs
index a126b66c0..93ab23f5e 100644
--- a/installer/test/phx_new_web_test.exs
+++ b/installer/test/phx_new_web_test.exs
@@ -33,7 +33,7 @@ defmodule Mix.Tasks.Phx.New.WebTest do
     in_tmp_umbrella_project "new with defaults", fn ->
       Mix.Tasks.Phx.New.Web.run([@app_name])
 
-      assert_file "#{@app_name}/config/config.exs", fn file ->
+      assert_file "../config/config.exs", fn file ->
         assert file =~ "generators: [context_app: false]"
       end
 
diff --git a/test/mix/tasks/phx.gen.context_test.exs b/test/mix/tasks/phx.gen.context_test.exs
index af3920e42..b0ba398db 100644
--- a/test/mix/tasks/phx.gen.context_test.exs
+++ b/test/mix/tasks/phx.gen.context_test.exs
@@ -36,7 +36,7 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
 
       assert String.ends_with?(context.dir, "lib/phoenix/blog")
       assert String.ends_with?(context.file, "lib/phoenix/blog.ex")
-      assert String.ends_with?(context.test_file, "test/phoenix/blog/blog_test.exs")
+      assert String.ends_with?(context.test_file, "test/phoenix/blog_test.exs")
       assert String.ends_with?(context.schema.file, "lib/phoenix/blog/post.ex")
     end
   end
@@ -63,7 +63,7 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
 
       assert String.ends_with?(context.dir, "lib/phoenix/site/blog")
       assert String.ends_with?(context.file, "lib/phoenix/site/blog.ex")
-      assert String.ends_with?(context.test_file, "test/phoenix/site/blog/blog_test.exs")
+      assert String.ends_with?(context.test_file, "test/phoenix/site/blog_test.exs")
       assert String.ends_with?(context.schema.file, "lib/phoenix/site/blog/post.ex")
     end
   end
@@ -139,7 +139,7 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
         assert file =~ "def change_post"
       end
 
-      assert_file "test/phoenix/blog/blog_test.exs", fn file ->
+      assert_file "test/phoenix/blog_test.exs", fn file ->
         assert file =~ "use Phoenix.DataCase"
         assert file =~ "describe \"posts\" do"
         assert file =~ "def post_fixture(attrs \\\\ %{})"
@@ -164,7 +164,7 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
         assert file =~ "field :title, :string"
       end
 
-      assert_file "test/phoenix/blog/blog_test.exs", fn file ->
+      assert_file "test/phoenix/blog_test.exs", fn file ->
         assert file =~ "use Phoenix.DataCase"
         assert file =~ "describe \"comments\" do"
         assert file =~ "def comment_fixture(attrs \\\\ %{})"
@@ -203,7 +203,7 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
         assert file =~ "raise \"TODO\""
       end
 
-      assert_file "test/phoenix/blog/blog_test.exs", fn file ->
+      assert_file "test/phoenix/blog_test.exs", fn file ->
         assert file =~ "use Phoenix.DataCase"
         assert file =~ "describe \"posts\" do"
         assert file =~ "def post_fixture(attrs \\\\ %{})"
diff --git a/test/mix/tasks/phx.gen.html_test.exs b/test/mix/tasks/phx.gen.html_test.exs
index 3972ef210..803dc7ca0 100644
--- a/test/mix/tasks/phx.gen.html_test.exs
+++ b/test/mix/tasks/phx.gen.html_test.exs
@@ -49,7 +49,7 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
 
       assert_file "lib/phoenix/blog/post.ex"
       assert_file "lib/phoenix/blog.ex"
-      assert_file "test/phoenix/blog/blog_test.exs", fn file ->
+      assert_file "test/phoenix/blog_test.exs", fn file ->
         assert file =~ "alarm: ~T[15:01:01]"
         assert file =~ "alarm_usec: ~T[15:01:01.000000]"
         assert file =~ "announcement_date: ~D[2010-04-17]"
diff --git a/test/mix/tasks/phx.gen.json_test.exs b/test/mix/tasks/phx.gen.json_test.exs
index 51d274bcb..f3fe7cd89 100644
--- a/test/mix/tasks/phx.gen.json_test.exs
+++ b/test/mix/tasks/phx.gen.json_test.exs
@@ -51,7 +51,7 @@ defmodule Mix.Tasks.Phx.Gen.JsonTest do
       assert_file "lib/phoenix/blog/post.ex"
       assert_file "lib/phoenix/blog.ex"
 
-      assert_file "test/phoenix/blog/blog_test.exs", fn file ->
+      assert_file "test/phoenix/blog_test.exs", fn file ->
         assert file =~ "use Phoenix.DataCase"
       end
 
diff --git a/test/mix/tasks/phx.new_test.exs b/test/mix/tasks/phx.new_test.exs
index f8a83fc94..0876aaff1 100644
--- a/test/mix/tasks/phx.new_test.exs
+++ b/test/mix/tasks/phx.new_test.exs
@@ -95,7 +95,7 @@ defmodule Mix.Tasks.Phx.NewTest do
   test "assets are in sync with installer" do
     for file <- ~w(favicon.ico phoenix.js phoenix.png) do
       assert File.read!("priv/static/#{file}") ==
-             File.read!("installer/templates/phx_assets/#{file}")
+             File.read!("installer/templates/phx_static/#{file}")
     end
   end
 end
diff --git a/test/phoenix/controller/controller_test.exs b/test/phoenix/controller/controller_test.exs
index 1837354d5..46befb329 100644
--- a/test/phoenix/controller/controller_test.exs
+++ b/test/phoenix/controller/controller_test.exs
@@ -424,6 +424,15 @@ defmodule Phoenix.Controller.ControllerTest do
              "world"
     end
 
+    test "sends file for download with custom :disposition" do
+      conn = send_download(conn(:get, "/"), {:file, @hello_txt}, disposition: :inline)
+      assert conn.status == 200
+      assert get_resp_header(conn, "content-disposition") ==
+             ["inline; filename=\"hello.txt\""]
+      assert conn.resp_body ==
+             "world"
+    end
+
     test "sends file for download with custom :offset" do
       conn = send_download(conn(:get, "/"), {:file, @hello_txt}, offset: 2)
       assert conn.status == 200
@@ -460,6 +469,27 @@ defmodule Phoenix.Controller.ControllerTest do
       assert conn.resp_body ==
              "world"
     end
+
+    test "sends binary for download with custom :disposition" do
+      conn = send_download(conn(:get, "/"), {:binary, "world"},
+                           filename: "hello.txt", disposition: :inline)
+      assert conn.status == 200
+      assert get_resp_header(conn, "content-disposition") ==
+             ["inline; filename=\"hello.txt\""]
+      assert conn.resp_body ==
+             "world"
+    end
+
+    test "raises ArgumentError for :disposition other than :attachment or :inline" do
+      assert_raise(ArgumentError, ~r"expected :disposition to be :attachment or :inline, got: :foo", fn ->
+        send_download(conn(:get, "/"), {:file, @hello_txt}, disposition: :foo)
+      end)
+
+      assert_raise(ArgumentError, ~r"expected :disposition to be :attachment or :inline, got: :foo", fn ->
+        send_download(conn(:get, "/"), {:binary, "world"},
+                           filename: "hello.txt", disposition: :foo)
+      end)
+    end
   end
 
   describe "scrub_params/2" do
diff --git a/test/phoenix/controller/flash_test.exs b/test/phoenix/controller/flash_test.exs
index 104e306fb..c0a1761ca 100644
--- a/test/phoenix/controller/flash_test.exs
+++ b/test/phoenix/controller/flash_test.exs
@@ -11,30 +11,29 @@ defmodule Phoenix.Controller.FlashTest do
 
   test "flash is persisted when status is a redirect" do
     for status <- 300..308 do
-      conn = conn(:get, "/") |> with_session |> fetch_flash()
+      conn = conn() |> fetch_flash()
                              |> put_flash(:notice, "elixir") |> send_resp(status, "ok")
       assert get_flash(conn, :notice) == "elixir"
       assert get_resp_header(conn, "set-cookie") != []
-      conn = conn(:get, "/") |> recycle_cookies(conn) |> with_session |> fetch_flash()
+      conn = conn(recycle_cookies: conn) |> fetch_flash()
       assert get_flash(conn, :notice) == "elixir"
     end
   end
 
   test "flash is not persisted when status is not redirect" do
     for status <- [299, 309, 200, 404] do
-      conn = conn(:get, "/") |> with_session |> fetch_flash()
+      conn = conn() |> fetch_flash()
                              |> put_flash(:notice, "elixir") |> send_resp(status, "ok")
       assert get_flash(conn, :notice) == "elixir"
       assert get_resp_header(conn, "set-cookie") != []
-      conn = conn(:get, "/") |> recycle_cookies(conn) |> with_session |> fetch_flash()
+      conn = conn(recycle_cookies: conn) |> fetch_flash()
       assert get_flash(conn, :notice) == nil
     end
   end
 
   test "flash does not write to session when it is empty and no session exists" do
     conn =
-      conn(:get, "/")
-      |> with_session()
+      conn()
       |> fetch_flash()
       |> clear_flash()
       |> send_resp(302, "ok")
@@ -44,16 +43,13 @@ defmodule Phoenix.Controller.FlashTest do
 
   test "flash writes to session when it is empty and a previous session exists" do
     persisted_flash_conn =
-      conn(:get, "/")
-      |> with_session()
+      conn()
       |> fetch_flash()
       |> put_flash(:info, "existing")
       |> send_resp(302, "ok")
 
     conn =
-      conn(:get, "/")
-      |> Plug.Test.recycle_cookies(persisted_flash_conn)
-      |> with_session()
+      conn(recycle_cookies: persisted_flash_conn)
       |> fetch_flash()
       |> clear_flash()
       |> send_resp(200, "ok")
@@ -63,37 +59,36 @@ defmodule Phoenix.Controller.FlashTest do
 
   test "get_flash/1 raises ArgumentError when flash not previously fetched" do
     assert_raise ArgumentError, fn ->
-      conn(:get, "/") |> with_session |> get_flash()
+      conn() |> get_flash()
     end
   end
 
   test "get_flash/1 returns the map of messages" do
-    conn = conn(:get, "/") |> with_session |> fetch_flash([]) |> put_flash(:notice, "hi")
+    conn = conn() |> fetch_flash([]) |> put_flash(:notice, "hi")
     assert get_flash(conn) == %{"notice" => "hi"}
   end
 
   test "get_flash/2 returns the message by key" do
-    conn = conn(:get, "/") |> with_session |> fetch_flash([]) |> put_flash(:notice, "hi")
+    conn = conn() |> fetch_flash([]) |> put_flash(:notice, "hi")
     assert get_flash(conn, :notice) == "hi"
     assert get_flash(conn, "notice") == "hi"
   end
 
   test "get_flash/2 returns nil for missing key" do
-    conn = conn(:get, "/") |> with_session |> fetch_flash([])
+    conn = conn() |> fetch_flash([])
     assert get_flash(conn, :notice) == nil
     assert get_flash(conn, "notice") == nil
   end
 
   test "put_flash/3 raises ArgumentError when flash not previously fetched" do
     assert_raise ArgumentError, fn ->
-      conn(:get, "/") |> with_session |> put_flash(:error, "boom!")
+      conn() |> put_flash(:error, "boom!")
     end
   end
 
   test "put_flash/3 adds the key/message pair to the flash" do
     conn =
-      conn(:get, "/")
-      |> with_session
+      conn()
       |> fetch_flash([])
       |> put_flash(:error, "oh noes!")
       |> put_flash(:notice, "false alarm!")
@@ -106,8 +101,7 @@ defmodule Phoenix.Controller.FlashTest do
 
   test "clear_flash/1 clears the flash messages" do
     conn =
-      conn(:get, "/")
-      |> with_session
+      conn()
       |> fetch_flash([])
       |> put_flash(:error, "oh noes!")
       |> put_flash(:notice, "false alarm!")
@@ -122,4 +116,17 @@ defmodule Phoenix.Controller.FlashTest do
       conn(:get, "/") |> fetch_flash([])
     end
   end
+
+  defp conn(opts \\ []) do
+    conn = conn(:get, "/")
+
+    conn = case opts[:recycle_cookies] do
+      nil -> conn
+      old_conn -> conn |> recycle_cookies(old_conn)
+    end
+
+    conn
+    |> with_session()
+  end
+
 end