Repository: phoenixframework/phoenix (elixir)

Issue:
Convert subscribe_and_join payload to string keys
<!--
### Precheck

* 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.)
* All checked? Be nice and have fun!
-->

### Environment

* Elixir version (elixir -v):
* Phoenix version (mix deps):
* NodeJS version (node -v):
* NPM version (npm -v):
* Operating system:

### Expected behavior

Using `Phoenix.ChannelTest.subscribe_and_join` a given payload is sent as is.

### Actual behavior

For `Phoenix.ConnTest.post` and others the params are automatically modified to be string keys. I'm wondering if the same would be useful for channel tests.


Pull request description:
Convert subscribe_and_join payload to string keys
Close #3443

Patch:
diff --git a/guides/deployment/deployment.md b/guides/deployment/deployment.md
index 63f55e7f0..e31c3f5a5 100644
--- a/guides/deployment/deployment.md
+++ b/guides/deployment/deployment.md
@@ -73,6 +73,12 @@ $ PORT=4001 MIX_ENV=prod mix phx.server
 10:59:19.136 [info] Running MyApp.Endpoint with Cowboy on http://example.com
 ```
 
+To run in detached mode so that the Phoenix server does not stop and continues to run even if you close the terminal:
+
+```console
+$ PORT=4001 MIX_ENV=prod elixir --erl "-detached" -S mix phx.server
+```
+
 In case you get an error message, please read it carefully, and open up a bug report if it is still not clear how to address it.
 
 You can also run your application inside an interactive shell:
diff --git a/guides/deployment/releases.md b/guides/deployment/releases.md
index 69ed183a0..9345a8949 100644
--- a/guides/deployment/releases.md
+++ b/guides/deployment/releases.md
@@ -50,15 +50,15 @@ Generated my_app app
 * assembling my_app-0.1.0 on MIX_ENV=prod
 * skipping runtime configuration (config/releases.exs not found)
 
-Release created at _build/dev/rel/my_app!
+Release created at _build/prod/rel/my_app!
 
     # To start your system
-    _build/dev/rel/my_app/bin/my_app start
+    _build/prod/rel/my_app/bin/my_app start
 
 ...
 ```
 
-You can start the release by calling `_build/dev/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.
+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/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:
 
@@ -74,13 +74,13 @@ Generated my_app app
 * assembling my_app-0.1.0 on MIX_ENV=prod
 * skipping runtime configuration (config/releases.exs not found)
 
-Release created at _build/dev/rel/my_app!
+Release created at _build/prod/rel/my_app!
 
     # To start your system
-    _build/dev/rel/my_app/bin/my_app start
+    _build/prod/rel/my_app/bin/my_app start
 ```
 
-And starting the release now should also successfully start the web server! Now you can get all of the files under the `_build/dev/rel/my_app` directory, package it, and run it in any production machine with the same OS and archictecture as the one that assembled the release. For more details, check the [docs for `mix release`](https://hexdocs.pm/mix/Mix.Tasks.Release.html).
+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 archictecture 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.
 
@@ -105,7 +105,7 @@ Generated my_app app
 * 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/dev/rel/my_app/bin/my_app start` and friends.
+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.
 
 ## Ecto migrations and custom commands
 
@@ -145,11 +145,23 @@ Where you replace the first two lines by your application names.
 Now you can assemble a new release with `MIX_ENV=prod mix release` and you can invoke any code, including the functions in the module above, by calling the `eval` command:
 
 ```
-$ _build/dev/rel/my_app/bin/my_app eval "MyApp.Release.migrate"
+$ _build/prod/rel/my_app/bin/my_app eval "MyApp.Release.migrate"
 ```
 
 And that's it!
 
+You can use this approach to create any custom command to run in production. In this case, we used `load_app`, which calls `Application.load/1` to load the current application without starting it. However, you may want to write a custom command that starts the whole application. In such cases, `Application.ensure_all_started/1` must be used. Keep in mind starting the application will start all processes for the current application, including the Phoenix endpoint. This can be circumvented by changing your supervision tree to not start certain children under certain conditions. For example, in the release commands file you could do:
+
+```elixir
+defp start_app do
+  load_app()
+  Application.put_env(@app, :minimal, true)
+  Application.ensure_all_started(@app)
+end
+```
+
+And then in your application you check `Application.get_env(@app, :minimal)` and start only part of the children when it is set.
+
 ## Containers
 
 Elixir releases work well with container technologies, such as Docker. The idea is that you assemble the release inside the Docker container and then build an image based on the release artifacts.
diff --git a/guides/routing.md b/guides/routing.md
index 6691a900b..9d1f2a6eb 100644
--- a/guides/routing.md
+++ b/guides/routing.md
@@ -161,7 +161,7 @@ comment_path  PATCH  /comments/:id       HelloWeb.CommentController :update
               PUT    /comments/:id       HelloWeb.CommentController :update
 ```
 
-The `Phoenix.Router.resources/4` function describes additional options for customizing resource routes.
+The `Phoenix.Router.resources/4` macro describes additional options for customizing resource routes.
 
 ## Forward
 
diff --git a/lib/phoenix/socket.ex b/lib/phoenix/socket.ex
index ee0ffd689..4525b2690 100644
--- a/lib/phoenix/socket.ex
+++ b/lib/phoenix/socket.ex
@@ -303,19 +303,24 @@ defmodule Phoenix.Socket do
   ## USER API
 
   @doc """
-  Adds a key/value pair to socket assigns.
+  Adds key value pairs to socket assigns.
 
-  ## Examples
+  A single key value pair may be passed, a keyword list or map
+  of assigns may be provided to be merged into existing socket
+  assigns.
 
-      iex> socket.assigns[:token]
-      nil
-      iex> socket = assign(socket, :token, "bar")
-      iex> socket.assigns[:token]
-      "bar"
+  ## Examples
 
+  iex> assign(socket, :name, "Elixir")
+  iex> assign(socket, name: "Elixir", logo: "💧")
   """
-  def assign(socket = %Socket{}, key, value) do
-    put_in socket.assigns[key], value
+  def assign(%Socket{} = socket, key, value) do
+    assign(socket, [{key, value}])
+  end
+
+  def assign(%Socket{} = socket, attrs)
+  when is_map(attrs) or is_list(attrs) do
+    %{socket | assigns: Map.merge(socket.assigns, Map.new(attrs))}
   end
 
   @doc """


Test patch:
diff --git a/lib/phoenix/test/channel_test.ex b/lib/phoenix/test/channel_test.ex
index 42afc9ded..f048edf91 100644
--- a/lib/phoenix/test/channel_test.ex
+++ b/lib/phoenix/test/channel_test.ex
@@ -362,7 +362,7 @@ defmodule Phoenix.ChannelTest do
       when is_atom(channel) and is_binary(topic) and is_map(payload) do
     message = %Message{
       event: "phx_join",
-      payload: payload,
+      payload: __stringify__(payload),
       topic: topic,
       ref: System.unique_integer([:positive])
     }
diff --git a/test/phoenix/socket/socket_test.exs b/test/phoenix/socket/socket_test.exs
index be0302461..8d789d5c0 100644
--- a/test/phoenix/socket/socket_test.exs
+++ b/test/phoenix/socket/socket_test.exs
@@ -37,4 +37,30 @@ defmodule Phoenix.SocketTest do
       assert socket.assigns[:foo] == :bar
     end
   end
+
+  describe "assign/2" do
+    test "assigns a map socket" do
+      socket = %Phoenix.Socket{}
+      assert socket.assigns[:foo] == nil
+      socket = assign(socket, %{foo: :bar, abc: :def})
+      assert socket.assigns[:foo] == :bar
+      assert socket.assigns[:abc] == :def
+    end
+
+    test "merges if values exist" do
+      socket = %Phoenix.Socket{}
+      socket = assign(socket, %{foo: :bar, abc: :def})
+      socket = assign(socket, %{foo: :baz})
+      assert socket.assigns[:foo] == :baz
+      assert socket.assigns[:abc] == :def
+    end
+
+    test "merges keyword lists" do
+      socket = %Phoenix.Socket{}
+      socket = assign(socket, %{foo: :bar, abc: :def})
+      socket = assign(socket, [foo: :baz])
+      assert socket.assigns[:foo] == :baz
+      assert socket.assigns[:abc] == :def
+    end
+  end
 end
diff --git a/test/phoenix/test/channel_test.exs b/test/phoenix/test/channel_test.exs
index 442e2d048..1f5b47aaa 100644
--- a/test/phoenix/test/channel_test.exs
+++ b/test/phoenix/test/channel_test.exs
@@ -58,6 +58,10 @@ defmodule Phoenix.Test.ChannelTest do
       :unknown
     end
 
+    def join("foo:payload", %{"string" => _payload}, socket) do
+      {:ok, socket}
+    end
+
     def handle_in("broadcast", broadcast, socket) do
       broadcast_from!(socket, "broadcast", broadcast)
       {:noreply, socket}
@@ -482,4 +486,9 @@ defmodule Phoenix.Test.ChannelTest do
     socket = subscribe_and_join!(socket, "foo:ok")
     assert socket.assigns.user_socket_assigns
   end
+
+  test "converts payload on join to string keyed" do
+    {:ok, socket} = connect(UserSocket, %{})
+    assert {:ok, _, _socket} = subscribe_and_join(socket, "foo:payload", %{string: nil})
+  end
 end