Repository: phoenixframework/phoenix
Pull request #3506 (2019-08-01 20:03:45)

Allow using a keyword list/map for socket assigns
This reflects the capabilities of Live View, allow multiple vallues to
be assigned at once.


Patch:
diff --git a/guides/deployment/releases.md b/guides/deployment/releases.md
index e30868537..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,7 +145,7 @@ 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!
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/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