Repository: phoenixframework/phoenix
Pull request #4112 (2020-12-20 14:55:11)

Update runtime configuratin guides


Patch:
diff --git a/Earthfile b/Earthfile
index 4013d05b7..9982bb703 100644
--- a/Earthfile
+++ b/Earthfile
@@ -9,6 +9,7 @@ all-test:
 
 test:
     FROM +test-setup
+    RUN MIX_ENV=test mix deps.compile
     COPY --dir assets config installer lib integration_test priv test ./
     RUN mix test
 
@@ -74,18 +75,16 @@ integration-test:
     END
 
 npm:
-    ARG ELIXIR=1.10.4
-    ARG OTP=23.0.3
-    FROM node:12
-    COPY +npm-setup/assets /assets
-    WORKDIR assets
-    RUN npm install && npm test
-
-npm-setup:
     FROM +test-setup
-    COPY assets assets
-    RUN mix deps.get
-    SAVE ARTIFACT assets
+    # Use Node 12 + matching NPM
+    RUN apk add "nodejs>12.0" && apk add "npm>12.0"
+    RUN mkdir assets
+    # Copy package.json + lockfile separatelly to improve caching (JS changes don't trigger `npm install` anymore)
+    COPY assets/package* assets
+    WORKDIR assets
+    RUN npm install
+    COPY assets/ .
+    RUN npm test
 
 setup-base:
    ARG ELIXIR=1.11.2
diff --git a/guides/deployment/deployment.md b/guides/deployment/deployment.md
index aa3f02205..5c3ae4fea 100644
--- a/guides/deployment/deployment.md
+++ b/guides/deployment/deployment.md
@@ -16,7 +16,7 @@ Let's explore those steps above one by one.
 
 ## Handling of your application secrets
 
-All Phoenix applications have data that must be kept secure, for example, the username and password for your production database, and the secret Phoenix uses to sign and encrypt important information. The general recommendation is to keep those in environment variables and load them into your application. This is done in `config/runtime.exs` (formerly `config/prod.secret.exs`), which is responsible for loading secrets and configuration from environment variables.
+All Phoenix applications have data that must be kept secure, for example, the username and password for your production database, and the secret Phoenix uses to sign and encrypt important information. The general recommendation is to keep those in environment variables and load them into your application. This is done in `config/runtime.exs` (formerly `config/prod.secret.exs` or `config/releases.exs`), which is responsible for loading secrets and configuration from environment variables.
 
 Therefore, you need to make sure the proper relevant variables are set in production:
 
@@ -29,7 +29,7 @@ $ export DATABASE_URL=ecto://USER:PASS@HOST/database
 
 Do not copy those values directly, set `SECRET_KEY_BASE` according to the result of `mix phx.gen.secret` and `DATABASE_URL` according to your database address.
 
-If for some reason you do not want to rely on environment variables, you can hard code the secrets in your `config/runtime.exs` (formerly `config/prod.secret.exs`), but make sure not to check the file into your version control system.
+If for some reason you do not want to rely on environment variables, you can hard code the secrets in your `config/runtime.exs` (formerly `config/prod.secret.exs` or `config/releases.exs`), but make sure not to check the file into your version control system.
 
 With your secret information properly secured, it is time to configure assets!
 
diff --git a/guides/deployment/releases.md b/guides/deployment/releases.md
index 37d2b8adc..8a96852bb 100644
--- a/guides/deployment/releases.md
+++ b/guides/deployment/releases.md
@@ -10,11 +10,11 @@ Our main goal for this guide is to package your Phoenix application into a self-
 
 ## Releases, assemble!
 
-To assemble a release, you will need Elixir v1.9 or later:
+To assemble a release, you will need at least Elixir v1.9, however this guide assumes you are using Elixir v1.11 or later to take advantage of latest releases improvements:
 
 ```console
 $ elixir -v
-1.9.0
+1.11.0
 ```
 
 If you are not familiar with Elixir releases yet, we recommend you to read [Elixir's excellent docs](https://hexdocs.pm/mix/Mix.Tasks.Release.html) before continuing.
@@ -53,7 +53,7 @@ And now run `mix release`:
 $ MIX_ENV=prod mix release
 Generated my_app app
 * assembling my_app-0.1.0 on MIX_ENV=prod
-* skipping runtime configuration (config/releases.exs not found)
+* using config/runtime.exs to configure the release at runtime
 
 Release created at _build/prod/rel/my_app!
 
@@ -65,7 +65,7 @@ Release created at _build/prod/rel/my_app!
 
 You can start the release by calling `_build/prod/rel/my_app/bin/my_app start`, where you have to replace `my_app` by your current application name. If you do so, your application should start but you will notice your web server does not actually run! That's because we need to tell Phoenix to start the web servers. When using `mix phx.server`, the `phx.server` command does that for us, but in a release we don't have Mix (which is a *build* tool), so we have to do it ourselves.
 
-Open up `config/runtime.exs` (formerly `config/prod.secret.exs`) and you should find a section about "Using releases" with a configuration to set. Go ahead and uncomment that line or manually add the line below, adapted to your application names:
+Open up `config/runtime.exs` (formerly `config/prod.secret.exs` or `config/releases.exs`) and you should find a section about "Using releases" with a configuration to set. Go ahead and uncomment that line or manually add the line below, adapted to your application names:
 
 ```elixir
 config :my_app, MyAppWeb.Endpoint, server: true
@@ -77,7 +77,7 @@ Now assemble the release once again:
 $ MIX_ENV=prod mix release
 Generated my_app app
 * assembling my_app-0.1.0 on MIX_ENV=prod
-* skipping runtime configuration (config/releases.exs not found)
+* using config/runtime.exs to configure the release at runtime
 
 Release created at _build/prod/rel/my_app!
 
@@ -87,32 +87,11 @@ Release created at _build/prod/rel/my_app!
 
 And starting the release now should also successfully start the web server! Now you can get all of the files under the `_build/prod/rel/my_app` directory, package it, and run it in any production machine with the same OS and architecture as the one that assembled the release. For more details, check the [docs for `mix release`](https://hexdocs.pm/mix/Mix.Tasks.Release.html).
 
-But before we finish this guide, there are two features from releases most Phoenix applications will use, so let's talk about those.
-
-## Runtime configuration
-
-You may have noticed that, in order to assemble our release, we had to set both `SECRET_KEY_BASE` and `DATABASE_URL`. That's because `config/config.exs`, `config/prod.exs`, and friends are executed when the release is assembled (or more generally speaking, whenever you run a `mix` command).
-
-However, in many cases, we don't want to set the values for `SECRET_KEY_BASE` and `DATABASE_URL` when assembling the release but only when starting the system in production. In particular, you may not even have those values easily accessible, and you may have to reach out to another system to retrieve those. Luckily, for such use cases, `mix release` provides runtime configuration, which we can enable in three steps:
-
-1. Rename `config/runtime.exs` (formerly `config/prod.secret.exs`) to `config/releases.exs`
-
-2. Change `config/prod.exs` to no longer call `import_config "runtime.exs"` (formerly `prod.secret.exs`) at the bottom
-
-Now if you assemble another release, you should see this:
-
-```console
-$ MIX_ENV=prod mix release
-Generated my_app app
-* assembling my_app-0.1.0 on MIX_ENV=prod
-* using config/releases.exs to configure the release at runtime
-```
-
-Notice how it says you are using runtime configuration. Now you no longer need to set those environment variables when assembling the release, only when you run `_build/prod/rel/my_app/bin/my_app start` and friends.
+But before we finish this guide, there is one more feature from releases that most Phoenix application will use, so let's talk about that.
 
 ## Ecto migrations and custom commands
 
-Another common need in production systems is to execute custom commands required to set up the production environment. One of such commands is precisely migrating the database. Since we don't have `Mix`, a *build* tool, inside releases, which are a production artifact, we need to bring said commands directly into the release.
+A common need in production systems is to execute custom commands required to set up the production environment. One of such commands is precisely migrating the database. Since we don't have `Mix`, a *build* tool, inside releases, which are a production artifact, we need to bring said commands directly into the release.
 
 Our recommendation is to create a new file in your application, such as `lib/my_app/release.ex`, with the following:
 
diff --git a/guides/plug.md b/guides/plug.md
index e8aa98e62..47ef0bbf6 100644
--- a/guides/plug.md
+++ b/guides/plug.md
@@ -167,7 +167,7 @@ In the middle of the endpoint, there is also a conditional block:
   end
 ```
 
-This block is only executed in development. It enables live reloading (if you change a CSS file, they are updated in-browser without refreshing the page), code reloading (so we can see changes to our application without restarting the server), and check repo status (which makes sure our database is up to date, raising readable and actionable error otherwise).
+This block is only executed in development. It enables live reloading (if you change a CSS file, they are updated in-browser without refreshing the page), code reloading (so we can see changes to our application without restarting the server), and check repo status (which makes sure our database is up to date, raising a readable and actionable error otherwise).
 
 ### Router plugs
 
diff --git a/guides/request_lifecycle.md b/guides/request_lifecycle.md
index e252eedab..a27ff073d 100644
--- a/guides/request_lifecycle.md
+++ b/guides/request_lifecycle.md
@@ -18,7 +18,7 @@ When your browser accesses [http://localhost:4000/](http://localhost:4000/), it
 
 There are other HTTP verbs. For example, submitting a form typically uses the POST verb.
 
-Web applications typically handle requests by mapping each verb/path pair into a specific part of your application. This matching in Phoenix is done by the router. For example, we may map "/articles" to a portion of our application that shows all articles. Therefore, to add a new page, our first task is to add new route.
+Web applications typically handle requests by mapping each verb/path pair into a specific part of your application. This matching in Phoenix is done by the router. For example, we may map "/articles" to a portion of our application that shows all articles. Therefore, to add a new page, our first task is to add a new route.
 
 ### A new route
 
diff --git a/guides/routing.md b/guides/routing.md
index d4d16e82a..7e618c608 100644
--- a/guides/routing.md
+++ b/guides/routing.md
@@ -492,7 +492,7 @@ Phoenix defines two pipelines by default, `:browser` and `:api`, which can be us
 
 ### The `:browser` and `:api` Pipelines
 
-As their names suggest, the `:browser` pipeline prepares for routes which render requests for a browser. The `:api` pipeline prepares for routes which produce data for an api.
+As their names suggest, the `:browser` pipeline prepares for routes which render requests for a browser, and the `:api` pipeline prepares for routes which produce data for an api.
 
 The `:browser` pipeline has five plugs: `plug :accepts, ["html"]` which defines the request format or formats which will be accepted, `:fetch_session`, which, naturally, fetches the session data and makes it available in the connection, `:fetch_flash` which retrieves any flash messages which may have been set, as well as `:protect_from_forgery` and `:put_secure_browser_headers`, which protects form posts from cross site forgery.
 
diff --git a/installer/lib/mix/tasks/local.phx.ex b/installer/lib/mix/tasks/local.phx.ex
index ee9e52deb..63de0fe16 100644
--- a/installer/lib/mix/tasks/local.phx.ex
+++ b/installer/lib/mix/tasks/local.phx.ex
@@ -10,6 +10,8 @@ defmodule Mix.Tasks.Local.Phx do
 
   Accepts the same command line options as `archive.install hex phx_new`.
   """
+
+  @impl true
   def run(args) do
     Mix.Task.run("archive.install", ["hex", "phx_new" | args])
   end
diff --git a/installer/lib/mix/tasks/phx.new.ecto.ex b/installer/lib/mix/tasks/phx.new.ecto.ex
index 86ae6a26e..f6c1b1f42 100644
--- a/installer/lib/mix/tasks/phx.new.ecto.ex
+++ b/installer/lib/mix/tasks/phx.new.ecto.ex
@@ -49,9 +49,11 @@ defmodule Mix.Tasks.Phx.New.Ecto do
   use Mix.Task
   import Phx.New.Generator
 
+  @impl true
   def run([]) do
     Mix.Tasks.Help.run(["phx.new.ecto"])
   end
+
   def run([path | _] = args) do
     unless in_umbrella?(path) do
       Mix.raise "The ecto task can only be run within an umbrella's apps directory"
diff --git a/installer/lib/mix/tasks/phx.new.ex b/installer/lib/mix/tasks/phx.new.ex
index 7df5c58d9..509f18140 100644
--- a/installer/lib/mix/tasks/phx.new.ex
+++ b/installer/lib/mix/tasks/phx.new.ex
@@ -110,6 +110,7 @@ defmodule Mix.Tasks.Phx.New do
              gettext: :boolean, umbrella: :boolean, verbose: :boolean,
              live: :boolean, dashboard: :boolean, install: :boolean]
 
+  @impl true
   def run([version]) when version in ~w(-v --version) do
     Mix.shell().info("Phoenix v#{@version}")
   end
@@ -126,6 +127,7 @@ defmodule Mix.Tasks.Phx.New do
     end
   end
 
+  @doc false
   def run(argv, generator, path) do
     elixir_version_check!()
     case parse_opts(argv) do
@@ -134,7 +136,7 @@ defmodule Mix.Tasks.Phx.New do
     end
   end
 
-  def generate(base_path, generator, path, opts) do
+  defp generate(base_path, generator, path, opts) do
     base_path
     |> Project.new(opts)
     |> generator.prepare_project()
diff --git a/installer/lib/mix/tasks/phx.new.web.ex b/installer/lib/mix/tasks/phx.new.web.ex
index 052c4766d..20d8fc5be 100644
--- a/installer/lib/mix/tasks/phx.new.web.ex
+++ b/installer/lib/mix/tasks/phx.new.web.ex
@@ -29,9 +29,11 @@ defmodule Mix.Tasks.Phx.New.Web do
 
   use Mix.Task
 
+  @impl true
   def run([]) do
     Mix.Tasks.Help.run(["phx.new.web"])
   end
+
   def run([path | _] = args) do
     unless Phx.New.Generator.in_umbrella?(path) do
       Mix.raise "The web task can only be run within an umbrella's apps directory"
diff --git a/installer/templates/phx_single/config/runtime.exs b/installer/templates/phx_single/config/runtime.exs
index 6c6708b61..a0e59cdcf 100644
--- a/installer/templates/phx_single/config/runtime.exs
+++ b/installer/templates/phx_single/config/runtime.exs
@@ -18,6 +18,8 @@ if config_env() == :prod do
     http: [
       # Enable IPv6 and bind on all interfaces.
       # Set it to  {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
+      # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
+      # for details about using IPv6 vs IPv4 and loopback vs public addresses.
       ip: {0, 0, 0, 0, 0, 0, 0, 0},
       port: String.to_integer(System.get_env("PORT") || "4000")
     ],
diff --git a/installer/templates/phx_single/lib/app_name/application.ex b/installer/templates/phx_single/lib/app_name/application.ex
index d51d20741..8f171bac7 100644
--- a/installer/templates/phx_single/lib/app_name/application.ex
+++ b/installer/templates/phx_single/lib/app_name/application.ex
@@ -5,6 +5,7 @@ defmodule <%= @app_module %>.Application do
 
   use Application
 
+  @impl true
   def start(_type, _args) do
     children = [<%= if @ecto do %>
       # Start the Ecto repository
@@ -27,6 +28,7 @@ defmodule <%= @app_module %>.Application do
 
   # Tell Phoenix to update the endpoint configuration
   # whenever the application is updated.
+  @impl true
   def config_change(changed, _new, removed) do
     <%= @endpoint_module %>.config_change(changed, removed)
     :ok
diff --git a/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex b/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex
index 1ff3db00c..9d3bb0934 100644
--- a/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex
+++ b/installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex
@@ -5,6 +5,7 @@ defmodule <%= @app_module %>.Application do
 
   use Application
 
+  @impl true
   def start(_type, _args) do
     children = [<%= if @ecto do %>
       # Start the Ecto repository
diff --git a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex
index 496df9b9d..98a1d6d1c 100644
--- a/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex
+++ b/installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex
@@ -5,6 +5,7 @@ defmodule <%= @web_namespace %>.Application do
 
   use Application
 
+  @impl true
   def start(_type, _args) do
     children = [
       # Start the Telemetry supervisor
@@ -23,6 +24,7 @@ defmodule <%= @web_namespace %>.Application do
 
   # Tell Phoenix to update the endpoint configuration
   # whenever the application is updated.
+  @impl true
   def config_change(changed, _new, removed) do
     <%= @endpoint_module %>.config_change(changed, removed)
     :ok
diff --git a/lib/mix/tasks/phx.gen.auth.ex b/lib/mix/tasks/phx.gen.auth.ex
index 4b99ba8cd..4ca8e5d23 100644
--- a/lib/mix/tasks/phx.gen.auth.ex
+++ b/lib/mix/tasks/phx.gen.auth.ex
@@ -81,7 +81,8 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
   alias Mix.Tasks.Phx.Gen
   alias Mix.Tasks.Phx.Gen.Auth.{HashingLibrary, Injector, Migration}
 
-  @switches [web: :string, binary_id: :boolean, hashing_lib: :string, table: :string]
+  @switches [web: :string, binary_id: :boolean, hashing_lib: :string,
+             table: :string, merge_with_existing_context: :boolean]
 
   @doc false
   def run(args) do
@@ -254,6 +255,7 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
 
     paths
     |> Mix.Phoenix.eval_from("priv/templates/phx.gen.auth/context_functions.ex", binding)
+    |> prepend_newline()
     |> inject_before_final_end(file)
   end
 
@@ -264,6 +266,7 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
 
     paths
     |> Mix.Phoenix.eval_from("priv/templates/phx.gen.auth/test_cases.exs", binding)
+    |> prepend_newline()
     |> inject_before_final_end(test_file)
   end
 
@@ -600,6 +603,8 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
   defp pad(i) when i < 10, do: <<?0, ?0 + i>>
   defp pad(i), do: to_string(i)
 
+  defp prepend_newline(string) when is_binary(string), do: "\n" <> string
+
   defp get_ecto_adapter!(%Schema{repo: repo}) do
     if Code.ensure_loaded?(repo) do
       repo.__adapter__()
diff --git a/lib/mix/tasks/phx.gen.context.ex b/lib/mix/tasks/phx.gen.context.ex
index 6b6bdeb3d..e41bb7590 100644
--- a/lib/mix/tasks/phx.gen.context.ex
+++ b/lib/mix/tasks/phx.gen.context.ex
@@ -63,6 +63,14 @@ defmodule Mix.Tasks.Phx.Gen.Context do
 
   Read the documentation for `phx.gen.schema` for more information on
   attributes.
+
+  ## Skipping prompts
+
+  This generator will prompt you if there is an existing context with the same
+  name, in order to provide more instructions on how to correctly use phoenix contexts.
+  You can skip this prompt and automatically merge the new schema access functions and tests into the
+  existing context using `--merge-with-existing-context`. To prevent changes to
+  the existing context and exit the generator, use `--no-merge-with-existing-context`.
   """
 
   use Mix.Task
@@ -71,7 +79,8 @@ defmodule Mix.Tasks.Phx.Gen.Context do
   alias Mix.Tasks.Phx.Gen
 
   @switches [binary_id: :boolean, table: :string, web: :string,
-             schema: :boolean, context: :boolean, context_app: :string]
+             schema: :boolean, context: :boolean, context_app: :string,
+             merge_with_existing_context: :boolean]
 
   @default_opts [schema: true, context: true]
 
@@ -256,11 +265,17 @@ defmodule Mix.Tasks.Phx.Gen.Context do
 
   def prompt_for_code_injection(%Context{generate?: false}), do: :ok
   def prompt_for_code_injection(%Context{} = context) do
-    if Context.pre_existing?(context) do
+    if Context.pre_existing?(context) && !merge_with_existing_context?(context) do
+      System.halt()
+    end
+  end
+
+  defp merge_with_existing_context?(%Context{} = context) do
+    Keyword.get_lazy(context.opts, :merge_with_existing_context, fn ->
       function_count = Context.function_count(context)
       file_count = Context.file_count(context)
 
-      Mix.shell().info """
+      Mix.shell().info("""
       You are generating into an existing context.
 
       The #{inspect context.module} context currently has #{function_count} functions and \
@@ -276,10 +291,9 @@ defmodule Mix.Tasks.Phx.Gen.Context do
       to the same context.
 
       If you are not sure, prefer creating a new context over adding to the existing one.
-      """
-      unless Mix.shell().yes?("Would you like to proceed?") do
-        System.halt()
-      end
-    end
+      """)
+
+      Mix.shell().yes?("Would you like to proceed?")
+    end)
   end
 end
diff --git a/lib/phoenix.ex b/lib/phoenix.ex
index 59f0185cc..c18721ea1 100644
--- a/lib/phoenix.ex
+++ b/lib/phoenix.ex
@@ -25,9 +25,19 @@ defmodule Phoenix do
       for building `Phoenix.View` and working with template languages such
       as Elixir's own `EEx`
 
+    * [Phoenix LiveView](https://hexdocs.pm/phoenix_live_view) - rich,
+      real-time user experiences with server-rendered HTML
+
+    * [Phoenix LiveDashboard](https://hexdocs.pm/phoenix_live_dashboard) -
+      real-time performance monitoring and debugging tools for Phoenix
+      developers
+
     * [Plug](https://hexdocs.pm/plug) - a specification and conveniences
       for composable modules in between web applications
 
+    * [Telemetry Metrics](https://hexdocs.pm/telemetry_metrics) - common
+      interface for defining metrics based on Telemetry events
+
     * [Gettext](https://hexdocs.pm/gettext) - Internationalization and
       localization through [`gettext`](https://www.gnu.org/software/gettext/)
 
diff --git a/lib/phoenix/channel.ex b/lib/phoenix/channel.ex
index b6172c7bd..ea60fccba 100644
--- a/lib/phoenix/channel.ex
+++ b/lib/phoenix/channel.ex
@@ -300,7 +300,8 @@ defmodule Phoenix.Channel do
   alias Phoenix.Socket
   alias Phoenix.Channel.Server
 
-  @type reply :: status :: atom | {status :: atom, response :: map}
+  @type payload :: map | {:binary, binary}
+  @type reply :: status :: atom | {status :: atom, response :: payload}
   @type socket_ref ::
           {transport_pid :: Pid, serializer :: module, topic :: binary, ref :: binary,
            join_ref :: binary}
@@ -322,9 +323,9 @@ defmodule Phoenix.Channel do
       end
 
   """
-  @callback join(topic :: binary, payload :: map, socket :: Socket.t()) ::
+  @callback join(topic :: binary, payload :: payload, socket :: Socket.t()) ::
               {:ok, Socket.t()}
-              | {:ok, reply :: map, Socket.t()}
+              | {:ok, reply :: payload, Socket.t()}
               | {:error, reason :: map}
 
   @doc """
@@ -337,7 +338,7 @@ defmodule Phoenix.Channel do
       end
 
   """
-  @callback handle_in(event :: String.t(), payload :: map, socket :: Socket.t()) ::
+  @callback handle_in(event :: String.t(), payload :: payload, socket :: Socket.t()) ::
               {:noreply, Socket.t()}
               | {:noreply, Socket.t(), timeout | :hibernate}
               | {:reply, reply, Socket.t()}
@@ -349,7 +350,7 @@ defmodule Phoenix.Channel do
 
   See `intercept/1`.
   """
-  @callback handle_out(event :: String.t(), payload :: map, socket :: Socket.t()) ::
+  @callback handle_out(event :: String.t(), payload :: payload, socket :: Socket.t()) ::
               {:noreply, Socket.t()}
               | {:noreply, Socket.t(), timeout | :hibernate}
               | {:stop, reason :: term, Socket.t()}
@@ -504,13 +505,17 @@ defmodule Phoenix.Channel do
   @doc """
   Broadcast an event to all subscribers of the socket topic.
 
-  The event's message must be a serializable map.
+  The event's message must be a serializable map or a tagged `{:binary, data}`
+  tuple where `data` is binary data.
 
   ## Examples
 
       iex> broadcast(socket, "new_message", %{id: 1, content: "hello"})
       :ok
 
+      iex> broadcast(socket, "new_message", {:binary, "hello"})
+      :ok
+
   """
   def broadcast(socket, event, message) do
     %{pubsub_server: pubsub_server, topic: topic} = assert_joined!(socket)
@@ -529,13 +534,17 @@ defmodule Phoenix.Channel do
   Broadcast event from pid to all subscribers of the socket topic.
 
   The channel that owns the socket will not receive the published
-  message. The event's message must be a serializable map.
+  message. The event's message must be a serializable map or a tagged
+  `{:binary, data}` tuple where `data` is binary data.
 
   ## Examples
 
       iex> broadcast_from(socket, "new_message", %{id: 1, content: "hello"})
       :ok
 
+      iex> broadcast_from(socket, "new_message", {:binary, "hello"})
+      :ok
+
   """
   def broadcast_from(socket, event, message) do
     %{pubsub_server: pubsub_server, topic: topic, channel_pid: channel_pid} =
@@ -557,13 +566,17 @@ defmodule Phoenix.Channel do
   @doc """
   Sends event to the socket.
 
-  The event's message must be a serializable map.
+  The event's message must be a serializable map or a tagged `{:binary, data}`
+  tuple where `data` is binary data.
 
   ## Examples
 
       iex> push(socket, "new_message", %{id: 1, content: "hello"})
       :ok
 
+      iex> push(socket, "new_message", {:binary, "hello"})
+      :ok
+
   """
   def push(socket, event, message) do
     %{transport_pid: transport_pid, topic: topic} = assert_joined!(socket)
diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex
index 374279d86..e34af70bf 100644
--- a/lib/phoenix/endpoint.ex
+++ b/lib/phoenix/endpoint.ex
@@ -226,7 +226,7 @@ defmodule Phoenix.Endpoint do
 
   @type topic :: String.t
   @type event :: String.t
-  @type msg :: map
+  @type msg :: map | {:binary, binary}
 
   require Logger
 
diff --git a/lib/phoenix/router.ex b/lib/phoenix/router.ex
index a2f716931..491cf326c 100644
--- a/lib/phoenix/router.ex
+++ b/lib/phoenix/router.ex
@@ -129,7 +129,7 @@ defmodule Phoenix.Router do
   If the route contains glob-like patterns, parameters for those have to be given as
   list:
 
-      MyAppWeb.Router.Helpers.pages_path(conn_or_endpoint, :show, ["hello", "world"])
+      MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, ["hello", "world"])
       "/pages/hello/world"
 
   The URL generated in the named URL helpers is based on the configuration for


Test patch:
diff --git a/integration_test/test/code_generation/app_with_mssql_adapter_test.exs b/integration_test/test/code_generation/app_with_mssql_adapter_test.exs
index 7c65cfda2..ab084fe67 100644
--- a/integration_test/test/code_generation/app_with_mssql_adapter_test.exs
+++ b/integration_test/test/code_generation/app_with_mssql_adapter_test.exs
@@ -84,12 +84,25 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithMSSQLAdapterTest do
     end
   end
 
-  describe "phx.gen.auth + pbkdf2" do
+  describe "phx.gen.auth + pbkdf2 + existing context" do
     test "has no compilation or formatter warnings" do
       with_installer_tmp("new with defaults", fn tmp_dir ->
         {app_root_path, _} = generate_phoenix_app(tmp_dir, "phx_blog", ["--database", "mssql", "--live"])
 
-        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib pbkdf2), app_root_path)
+        mix_run!(~w(phx.gen.html Accounts Group groups name), app_root_path)
+
+        modify_file(Path.join(app_root_path, "lib/phx_blog_web/router.ex"), fn file ->
+          inject_before_final_end(file, """
+
+            scope "/", PhxBlogWeb do
+              pipe_through [:browser]
+
+              resources "/groups", GroupController
+            end
+          """)
+        end)
+
+        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib pbkdf2 --merge-with-existing-context), app_root_path)
 
         assert_no_compilation_warnings(app_root_path)
         assert_passes_formatter_check(app_root_path)
@@ -99,9 +112,22 @@ defmodule Phoenix.Integration.CodeGeneration.AppWithMSSQLAdapterTest do
     @tag database: :mssql
     test "has a passing test suite" do
       with_installer_tmp("app_with_defaults", fn tmp_dir ->
-        {app_root_path, _} = generate_phoenix_app(tmp_dir, "default_app", ["--database", "mssql", "--live"])
+        {app_root_path, _} = generate_phoenix_app(tmp_dir, "phx_blog", ["--database", "mssql", "--live"])
+
+        mix_run!(~w(phx.gen.html Accounts Group groups name), app_root_path)
+
+        modify_file(Path.join(app_root_path, "lib/phx_blog_web/router.ex"), fn file ->
+          inject_before_final_end(file, """
+
+            scope "/", PhxBlogWeb do
+              pipe_through [:browser]
+
+              resources "/groups", GroupController
+            end
+          """)
+        end)
 
-        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib pbkdf2), app_root_path)
+        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib pbkdf2 --merge-with-existing-context), app_root_path)
 
         drop_test_database(app_root_path)
         assert_tests_pass(app_root_path)
diff --git a/test/mix/tasks/phx.gen.context_test.exs b/test/mix/tasks/phx.gen.context_test.exs
index 6452b408c..a8bc0cf29 100644
--- a/test/mix/tasks/phx.gen.context_test.exs
+++ b/test/mix/tasks/phx.gen.context_test.exs
@@ -211,6 +211,34 @@ defmodule Mix.Tasks.Phx.Gen.ContextTest do
     end
   end
 
+  test "generates into existing context without prompt with --merge-with-existing-context", config do
+    in_tmp_project config.test, fn ->
+      Gen.Context.run(~w(Blog Post posts title))
+
+      assert_file "lib/phoenix/blog.ex", fn file ->
+        assert file =~ "def get_post!"
+        assert file =~ "def list_posts"
+        assert file =~ "def create_post"
+        assert file =~ "def update_post"
+        assert file =~ "def delete_post"
+        assert file =~ "def change_post"
+      end
+
+      Gen.Context.run(~w(Blog Comment comments message:string --merge-with-existing-context))
+
+      refute_received {:mix_shell, :info, ["You are generating into an existing context" <> _notice]}
+
+      assert_file "lib/phoenix/blog.ex", fn file ->
+        assert file =~ "def get_comment!"
+        assert file =~ "def list_comments"
+        assert file =~ "def create_comment"
+        assert file =~ "def update_comment"
+        assert file =~ "def delete_comment"
+        assert file =~ "def change_comment"
+      end
+    end
+  end
+
   test "when more than 50 attributes are given", config do
     in_tmp_project config.test, fn ->
       long_attribute_list = 0..55 |> Enum.map(&("attribute#{&1}:string")) |> Enum.join(" ")
diff --git a/test/mix/tasks/phx.gen.html_test.exs b/test/mix/tasks/phx.gen.html_test.exs
index 360fcb963..86a8b734f 100644
--- a/test/mix/tasks/phx.gen.html_test.exs
+++ b/test/mix/tasks/phx.gen.html_test.exs
@@ -199,6 +199,34 @@ defmodule Mix.Tasks.Phx.Gen.HtmlTest do
     end
   end
 
+  test "generates into existing context without prompt with --merge-with-existing-context", config do
+    in_tmp_project config.test, fn ->
+      Gen.Html.run(~w(Blog Post posts title))
+
+      assert_file "lib/phoenix/blog.ex", fn file ->
+        assert file =~ "def get_post!"
+        assert file =~ "def list_posts"
+        assert file =~ "def create_post"
+        assert file =~ "def update_post"
+        assert file =~ "def delete_post"
+        assert file =~ "def change_post"
+      end
+
+      Gen.Html.run(~w(Blog Comment comments message:string --merge-with-existing-context))
+
+      refute_received {:mix_shell, :info, ["You are generating into an existing context" <> _notice]}
+
+      assert_file "lib/phoenix/blog.ex", fn file ->
+        assert file =~ "def get_comment!"
+        assert file =~ "def list_comments"
+        assert file =~ "def create_comment"
+        assert file =~ "def update_comment"
+        assert file =~ "def delete_comment"
+        assert file =~ "def change_comment"
+      end
+    end
+  end
+
   test "with --web namespace generates namedspaced web modules and directories", config do
     in_tmp_project config.test, fn ->
       Gen.Html.run(~w(Blog Post posts title:string --web Blog))
diff --git a/test/mix/tasks/phx.gen.json_test.exs b/test/mix/tasks/phx.gen.json_test.exs
index 5cd660615..73028d4fd 100644
--- a/test/mix/tasks/phx.gen.json_test.exs
+++ b/test/mix/tasks/phx.gen.json_test.exs
@@ -117,6 +117,34 @@ defmodule Mix.Tasks.Phx.Gen.JsonTest do
     end
   end
 
+  test "generates into existing context without prompt with --merge-with-existing-context", config do
+    in_tmp_project config.test, fn ->
+      Gen.Json.run(~w(Blog Post posts title))
+
+      assert_file "lib/phoenix/blog.ex", fn file ->
+        assert file =~ "def get_post!"
+        assert file =~ "def list_posts"
+        assert file =~ "def create_post"
+        assert file =~ "def update_post"
+        assert file =~ "def delete_post"
+        assert file =~ "def change_post"
+      end
+
+      Gen.Json.run(~w(Blog Comment comments message:string --merge-with-existing-context))
+
+      refute_received {:mix_shell, :info, ["You are generating into an existing context" <> _notice]}
+
+      assert_file "lib/phoenix/blog.ex", fn file ->
+        assert file =~ "def get_comment!"
+        assert file =~ "def list_comments"
+        assert file =~ "def create_comment"
+        assert file =~ "def update_comment"
+        assert file =~ "def delete_comment"
+        assert file =~ "def change_comment"
+      end
+    end
+  end
+
   test "when more than 50 arguments are given", config do
     in_tmp_project config.test, fn ->
       long_attribute_list = 0..55 |> Enum.map(&("attribute#{&1}:string")) |> Enum.join(" ")
diff --git a/test/mix/tasks/phx.gen.live_test.exs b/test/mix/tasks/phx.gen.live_test.exs
index 8a4d81e3d..061d2b184 100644
--- a/test/mix/tasks/phx.gen.live_test.exs
+++ b/test/mix/tasks/phx.gen.live_test.exs
@@ -181,6 +181,34 @@ defmodule Mix.Tasks.Phx.Gen.LiveTest do
     end
   end
 
+  test "generates into existing context without prompt with --merge-with-existing-context", config do
+    in_tmp_live_project config.test, fn ->
+      Gen.Live.run(~w(Blog Post posts title))
+
+      assert_file "lib/phoenix/blog.ex", fn file ->
+        assert file =~ "def get_post!"
+        assert file =~ "def list_posts"
+        assert file =~ "def create_post"
+        assert file =~ "def update_post"
+        assert file =~ "def delete_post"
+        assert file =~ "def change_post"
+      end
+
+      Gen.Live.run(~w(Blog Comment comments message:string --merge-with-existing-context))
+
+      refute_received {:mix_shell, :info, ["You are generating into an existing context" <> _notice]}
+
+      assert_file "lib/phoenix/blog.ex", fn file ->
+        assert file =~ "def get_comment!"
+        assert file =~ "def list_comments"
+        assert file =~ "def create_comment"
+        assert file =~ "def update_comment"
+        assert file =~ "def delete_comment"
+        assert file =~ "def change_comment"
+      end
+    end
+  end
+
   test "with --web namespace generates namespaced web modules and directories", config do
     in_tmp_live_project config.test, fn ->
       Gen.Live.run(~w(Blog Post posts title:string --web Blog))