{"_id":"558338ea870ff41900de4849","tags":[],"project":"54348ec95b10711400c6c445","user":{"_id":"5435b410495d5d0800f3a603","username":"","name":"Lance Halvorsen"},"initVersion":{"_id":"5558c642eb56ae2f00f714fc","version":"0.13.1"},"__v":0,"createdAt":"2015-06-18T21:32:26.325Z","changelog":[],"body":"https://gist.github.com/chrismccord/3603fd2735019f86c74b\n\n## Generators\n  * `mix phoenix.gen.resource` renamed to `mix phoenix.gen.html`\n\n## Views\n`use Phoenix.HTML` no longer imports controller functions. You must add `import Phoenix.Controller, only: [get_flash: 2]` manually to your views or your `web.ex`, ie:\n\n```elixir\n# your_app/web/web.ex\ndefmodule MyApp.Web do\n  ...\n  def view do\n    quote do\n      ...\n      import Phoenix.Controller, only: [get_flash: 2]\n    end\n  end\nend\n```\n\n## Endpoints\n\nThe endpoint now requires a `:root` entry in your `config/config.exs`:\n\n```elixir\nconfig :my_app, MyApp.Endpoint,\n  ...\n  root: Path.expand(\"..\", __DIR__),\n```\n\nCode reloader must now be configured in your endpoint instead of Phoenix. Therefore, upgrade your `config/dev.exs` replacing\n\n```elixir\nconfig :phoenix, :code_reloader, true\n```\n\nby\n\n```elixir\nconfig :your_app, Your.Endpoint, code_reloader: true\n```\n\n## Live Reload\nTh live reloader is now a dependency instead of being shipped with Phoenix. Please add `{:phoenix_live_reload, \"~> 0.3\"}` to your dependencies in `mix.exs`\n\nAdditionally, the `live_reload` configuration has changed to allow a `:url` option and to work with `:patterns` instead of paths:\n\n```elixir\nconfig :your_app, Your.Endpoint,\n  code_reloader: true,\n  live_reload: [\n    # url is optional\n    url: \"ws://localhost:4000\", \n    # `:patterns` replace `:paths` and are required for live reload\n    patterns: [~r{priv/static/.*(js|css|png|jpeg|jpg|gif)$},\n               ~r{web/views/.*(ex)$},\n               ~r{web/templates/.*(eex)$}]]\n```\n\nNext, the Code and live reloader must now be explicitly plugged in your endpoint. Wrap them inside `lib/your_app/endpoint.ex` in a `code_reloading?` block:\n\n```elixir\nif code_reloading? do\n  plug Phoenix.LiveReloader\n  plug Phoenix.CodeReloader\nend\n```\n\n## Test Helpers\n\n1) Load test helpers in `test/support` by changing config in `mix.exs`.\n\n    # Change your elixirc_paths to use this function\n    elixirc_paths: elixirc_paths(Mix.env)\n\n    # Add these functions\n    # Specifies which paths to compile per environment\n    defp elixirc_paths(:test), do: [\"lib\", \"web\", \"test/support\"]\n    defp elixirc_paths(_),     do: [\"lib\", \"web\"]\n    \nSee a full example from the [default Phoenix installer](https://github.com/phoenixframework/phoenix/blob/d17d8ca390fa9b8375fa188eef7faea3adeea321/installer/templates/new/mix.exs)\n\n2) If you're using Ecto, add this to the bottom of your `test/test_helper.exs`. Replace `MyApp` with your app name.\n\n    # Create the database, run migrations, and start the test transaction.\n    Mix.Task.run \"ecto.create\", [\"--quiet\"]\n    Mix.Task.run \"ecto.migrate\", [\"--quiet\"]\n    Ecto.Adapters.SQL.begin_test_transaction(MyApp.Repo)\n\n\n## Channels - Server\nChannels received major updates in functionality and tweaking of the APIs and return signatures. Most notably, each channel now runs in its own process, supporthing `handle_info/2` and more closely matching GenServer APIs. Additionally \"synchronous\" messaging is now supported from client to server. By synchronous, I mean being able to reply to an incoming event directly, while ensuring messaging ordering for the same incoming events. This not only lets you do proper request/response messaging where necessary, but it also fixes issues we have in our <= 0.10 apis where joins were not synchronous and messages could be dropped if you fired them before you were fully joined. \n\nChanges:\n\n- The `leave/2` callback has been removed. If you need to cleanup/teardown when a client disconnects, trap exits and handle in `terminate/2`, ie:\n\n```elixir\ndef join(topic, auth_msg, socket) do\n  Process.flag(:trap_exit, true)\n  {:ok, socket}\nend\n\ndef terminate({:shutdown, :client_left}, socket) do\n  # client left intentionally\nend\ndef terminate(reason, socket) do\n  # terminating for another reason (connection drop, crash, etc)\nend\n```\n\n- `reply` has been renamed to `push` to better signify we are only push a message down the socket, not replying to a specific request. Update your function calls accordingly.\n\n- The return signatures for `handle_in/3` and `handle_out/3` have changed, ie:\n\n```elixir\nhandle_in(event :: String.t, msg :: map, Socket.t) ::\n  {:noreply, Socket.t} |\n  {:reply, {status :: atom, response :: map}, Socket.t} |\n  {:reply, status :: atom, Socket.t} |\n  {:stop, reason :: term, Socket.t} |\n  {:stop, reason :: term, reply :: {status :: atom, response :: map}, Socket.t} |\n  {:stop, reason :: term, reply :: status :: atom, Socket.t}\n\nhandle_out(event :: String.t, msg :: map, Socket.t) ::\n  {:ok, Socket.t} |\n  {:noreply, Socket.t} |\n  {:error, reason :: term, Socket.t} |\n  {:stop, reason :: term, Socket.t}\n```\nFor existing applications, you can simply change the return signatures of handle_in/handle_out from `{:ok, socket}` to `{:noreply, socket}`.\nFor code moving forward, you can now reply directly to an incoming event and pick up the reply on the client using the `{:reply, {status, response}, socket}` or `{:reply, status, socket}`. More examples below.\n\n\n## Channels - Client\n- update your `phoenix.js` to the new version: (https://github.com/phoenixframework/phoenix/blob/v0.11.0/priv/static/phoenix.js)\n- `chan.send(...)` has been renamed to `chan.push(...)` to match the server messaging command\n- `Phoenix.Socket` no longer connects automatically. You need to explicitly call `socket.connect()` to start the connection, ie:\n\n```javascript\nvar socket = new Phoenix.Socket(\"/ws\")\nsocket.connect()\n```\n\n- `socket.close()` has been renamed to `socket.disconnect()`\n\n- `socket.join(..)` api has changed. See the examples below for more details, but all it means is your js goes from:\n\n```javascript\nsocket.join(\"foo:bar\", {}, function(chan){\n})\n```\nto\n```javascript\nsocket.join(\"foo:bar\", {}).receive(\"ok\", function(chan){\n})\n// or\nvar chan = socket.join(\"foo:bar\", {})\nchan.receive(\"ok\", function(){\n})\n```\n\n### Sync Messaging\nWe've overhauled the channel API to allow \"synchronous\" messaging, and I really love the results. By synchronous, I mean being able to reply to an incoming event directly, while ensuring messaging ordering for the same incoming events. This not only lets you do proper request/response messaging where necessary, but it also fixes issues we have in our <= 0.10 apis where joins were not synchronous and messages could be dropped if you fired them before you were fully joined. With these changes, we have a few high-level concepts which make up channels:\n\n1. The client and server push messages down the socket to communicate\n2. The server can reply directly to a pushed message\n3. The server can broadcast events to be pushed to all subscribers\n\nThe flows looks like this:\n- client `push(\"ev1\")` -> server `handle_in(\"ev1\")` -> `server push(\"ev2\")` -> client `on(\"ev2\")`\n- client `push(\"ev1\")` -> server `handle_in(\"ev1\")` -> `server broadcast(\"ev2\")` -> N subscribers `handle_out(\"ev2\")` -> N subscribers `push(\"ev2\") -> N clients `on(\"ev2\")`\n- client `push(\"ev1\")` -> server `handle_in(\"ev\")` -> server `{:reply, :ok, ...}` -> client `receive(\"ok\", ...)`\n\nNow let's see some cli/server code:\n```javascript\n    socket.join(\"rooms:lobby\", {})\n      .after(5000, () => console.log(\"We're having trouble connecting...\") )\n      .receive(\"ignore\", () => console.log(\"auth error\") )\n      .receive(\"ok\", chan => {\n\n        // can now bind to channel crash/close events since channels are own processes\n        chan.onClose( () => console.log(\"The channel disconnected\") )\n        chan.onError( () => console.log(\"The channel crashed!\") )\n\n        $input.onEnter( e => {\n          // push without response\n          chan.push(\"new_msg\", {body: e.text, user: currentUser}) \n        })\n        \n        chan.on(\"status_change\", ({status}) => $status.html(status) )\n        \n        chan.on(\"new_msg\", msg => $messages.append(msg) )\n        \n        // push with `receive`'d response, and optional `after` hooks\n        $createNotice.onClick( e => {\n          chan.push(\"create_notice\", e.data)\n              .receive(\"ok\", notice =>  console.log(\"notice created\", notice) )\n              .receive(\"error\", reasons =>  console.log(\"creation failed\", reasons) )\n              .after(5000, () => console.log(\"network interruption\") )\n        })\n    })\n```\n\n```elixir\ndefmodule Chat.RoomChannel do\n  use Phoenix.Channel\n\n  def join(\"rooms:lobby\", message, socket) do\n    send(self, {:after_join, message})\n\n    {:ok, socket}\n  end\n  def join(\"rooms:\" <> _private_subtopic, _message, _socket) do\n    :ignore\n  end\n\n  def handle_info({:after_join, msg}, socket) do\n    broadcast! socket, \"user_entered\", %{user: msg[\"user\"]}\n    push socket, \"status_change\", %{status: \"waiting for users\"}\n    {:noreply, socket}\n  end\n\n  def handle_in(\"create_notice\", attrs, socket) do\n    changeset = Notice.changeset(%Notice{}, attrs)\n\n    if changeset.valid? do\n      Repo.insert(changeset)\n      {:reply, {:ok, changeset}, socket}\n    else\n      {:reply, {:error, changeset.errors}, socket}\n    end\n  end\n  \n  def handle_in(\"new_msg\", msg, socket) do\n    broadcast! socket, \"new_msg\", %{user: msg[\"user\"], body: msg[\"body\"]}\n    {:noreply, socket}\n  end\n  \n  # this is forward by the default `handle_out`, but show here for clarity\n  def handle_out(\"new_msg\", msg, socket) do\n    push socket, \"new_msg\", msg\n    {:noreply, socket}\n  end\nend\n```\n\nNote that `{:reply, {:ok, resp}, socket}` on the server, triggers `.receive(\"ok\", resp => {})` on the client. The \"status\" of the reply can be anything, ie `{:reply, {:queued, resp}, socket}` on the server, triggers `.receive(\"queued\", resp => { })` on the client.\nAlso note that client joining, push, and receiving replies all have the same semantics and API now, which is quite nice.","slug":"upgrading-from-v0100-to-v0110","title":"Upgrading from v0.10.0 to v0.11.0"}

Upgrading from v0.10.0 to v0.11.0


https://gist.github.com/chrismccord/3603fd2735019f86c74b ## Generators * `mix phoenix.gen.resource` renamed to `mix phoenix.gen.html` ## Views `use Phoenix.HTML` no longer imports controller functions. You must add `import Phoenix.Controller, only: [get_flash: 2]` manually to your views or your `web.ex`, ie: ```elixir # your_app/web/web.ex defmodule MyApp.Web do ... def view do quote do ... import Phoenix.Controller, only: [get_flash: 2] end end end ``` ## Endpoints The endpoint now requires a `:root` entry in your `config/config.exs`: ```elixir config :my_app, MyApp.Endpoint, ... root: Path.expand("..", __DIR__), ``` Code reloader must now be configured in your endpoint instead of Phoenix. Therefore, upgrade your `config/dev.exs` replacing ```elixir config :phoenix, :code_reloader, true ``` by ```elixir config :your_app, Your.Endpoint, code_reloader: true ``` ## Live Reload Th live reloader is now a dependency instead of being shipped with Phoenix. Please add `{:phoenix_live_reload, "~> 0.3"}` to your dependencies in `mix.exs` Additionally, the `live_reload` configuration has changed to allow a `:url` option and to work with `:patterns` instead of paths: ```elixir config :your_app, Your.Endpoint, code_reloader: true, live_reload: [ # url is optional url: "ws://localhost:4000", # `:patterns` replace `:paths` and are required for live reload patterns: [~r{priv/static/.*(js|css|png|jpeg|jpg|gif)$}, ~r{web/views/.*(ex)$}, ~r{web/templates/.*(eex)$}]] ``` Next, the Code and live reloader must now be explicitly plugged in your endpoint. Wrap them inside `lib/your_app/endpoint.ex` in a `code_reloading?` block: ```elixir if code_reloading? do plug Phoenix.LiveReloader plug Phoenix.CodeReloader end ``` ## Test Helpers 1) Load test helpers in `test/support` by changing config in `mix.exs`. # Change your elixirc_paths to use this function elixirc_paths: elixirc_paths(Mix.env) # Add these functions # Specifies which paths to compile per environment defp elixirc_paths(:test), do: ["lib", "web", "test/support"] defp elixirc_paths(_), do: ["lib", "web"] See a full example from the [default Phoenix installer](https://github.com/phoenixframework/phoenix/blob/d17d8ca390fa9b8375fa188eef7faea3adeea321/installer/templates/new/mix.exs) 2) If you're using Ecto, add this to the bottom of your `test/test_helper.exs`. Replace `MyApp` with your app name. # Create the database, run migrations, and start the test transaction. Mix.Task.run "ecto.create", ["--quiet"] Mix.Task.run "ecto.migrate", ["--quiet"] Ecto.Adapters.SQL.begin_test_transaction(MyApp.Repo) ## Channels - Server Channels received major updates in functionality and tweaking of the APIs and return signatures. Most notably, each channel now runs in its own process, supporthing `handle_info/2` and more closely matching GenServer APIs. Additionally "synchronous" messaging is now supported from client to server. By synchronous, I mean being able to reply to an incoming event directly, while ensuring messaging ordering for the same incoming events. This not only lets you do proper request/response messaging where necessary, but it also fixes issues we have in our <= 0.10 apis where joins were not synchronous and messages could be dropped if you fired them before you were fully joined. Changes: - The `leave/2` callback has been removed. If you need to cleanup/teardown when a client disconnects, trap exits and handle in `terminate/2`, ie: ```elixir def join(topic, auth_msg, socket) do Process.flag(:trap_exit, true) {:ok, socket} end def terminate({:shutdown, :client_left}, socket) do # client left intentionally end def terminate(reason, socket) do # terminating for another reason (connection drop, crash, etc) end ``` - `reply` has been renamed to `push` to better signify we are only push a message down the socket, not replying to a specific request. Update your function calls accordingly. - The return signatures for `handle_in/3` and `handle_out/3` have changed, ie: ```elixir handle_in(event :: String.t, msg :: map, Socket.t) :: {:noreply, Socket.t} | {:reply, {status :: atom, response :: map}, Socket.t} | {:reply, status :: atom, Socket.t} | {:stop, reason :: term, Socket.t} | {:stop, reason :: term, reply :: {status :: atom, response :: map}, Socket.t} | {:stop, reason :: term, reply :: status :: atom, Socket.t} handle_out(event :: String.t, msg :: map, Socket.t) :: {:ok, Socket.t} | {:noreply, Socket.t} | {:error, reason :: term, Socket.t} | {:stop, reason :: term, Socket.t} ``` For existing applications, you can simply change the return signatures of handle_in/handle_out from `{:ok, socket}` to `{:noreply, socket}`. For code moving forward, you can now reply directly to an incoming event and pick up the reply on the client using the `{:reply, {status, response}, socket}` or `{:reply, status, socket}`. More examples below. ## Channels - Client - update your `phoenix.js` to the new version: (https://github.com/phoenixframework/phoenix/blob/v0.11.0/priv/static/phoenix.js) - `chan.send(...)` has been renamed to `chan.push(...)` to match the server messaging command - `Phoenix.Socket` no longer connects automatically. You need to explicitly call `socket.connect()` to start the connection, ie: ```javascript var socket = new Phoenix.Socket("/ws") socket.connect() ``` - `socket.close()` has been renamed to `socket.disconnect()` - `socket.join(..)` api has changed. See the examples below for more details, but all it means is your js goes from: ```javascript socket.join("foo:bar", {}, function(chan){ }) ``` to ```javascript socket.join("foo:bar", {}).receive("ok", function(chan){ }) // or var chan = socket.join("foo:bar", {}) chan.receive("ok", function(){ }) ``` ### Sync Messaging We've overhauled the channel API to allow "synchronous" messaging, and I really love the results. By synchronous, I mean being able to reply to an incoming event directly, while ensuring messaging ordering for the same incoming events. This not only lets you do proper request/response messaging where necessary, but it also fixes issues we have in our <= 0.10 apis where joins were not synchronous and messages could be dropped if you fired them before you were fully joined. With these changes, we have a few high-level concepts which make up channels: 1. The client and server push messages down the socket to communicate 2. The server can reply directly to a pushed message 3. The server can broadcast events to be pushed to all subscribers The flows looks like this: - client `push("ev1")` -> server `handle_in("ev1")` -> `server push("ev2")` -> client `on("ev2")` - client `push("ev1")` -> server `handle_in("ev1")` -> `server broadcast("ev2")` -> N subscribers `handle_out("ev2")` -> N subscribers `push("ev2") -> N clients `on("ev2")` - client `push("ev1")` -> server `handle_in("ev")` -> server `{:reply, :ok, ...}` -> client `receive("ok", ...)` Now let's see some cli/server code: ```javascript socket.join("rooms:lobby", {}) .after(5000, () => console.log("We're having trouble connecting...") ) .receive("ignore", () => console.log("auth error") ) .receive("ok", chan => { // can now bind to channel crash/close events since channels are own processes chan.onClose( () => console.log("The channel disconnected") ) chan.onError( () => console.log("The channel crashed!") ) $input.onEnter( e => { // push without response chan.push("new_msg", {body: e.text, user: currentUser}) }) chan.on("status_change", ({status}) => $status.html(status) ) chan.on("new_msg", msg => $messages.append(msg) ) // push with `receive`'d response, and optional `after` hooks $createNotice.onClick( e => { chan.push("create_notice", e.data) .receive("ok", notice => console.log("notice created", notice) ) .receive("error", reasons => console.log("creation failed", reasons) ) .after(5000, () => console.log("network interruption") ) }) }) ``` ```elixir defmodule Chat.RoomChannel do use Phoenix.Channel def join("rooms:lobby", message, socket) do send(self, {:after_join, message}) {:ok, socket} end def join("rooms:" <> _private_subtopic, _message, _socket) do :ignore end def handle_info({:after_join, msg}, socket) do broadcast! socket, "user_entered", %{user: msg["user"]} push socket, "status_change", %{status: "waiting for users"} {:noreply, socket} end def handle_in("create_notice", attrs, socket) do changeset = Notice.changeset(%Notice{}, attrs) if changeset.valid? do Repo.insert(changeset) {:reply, {:ok, changeset}, socket} else {:reply, {:error, changeset.errors}, socket} end end def handle_in("new_msg", msg, socket) do broadcast! socket, "new_msg", %{user: msg["user"], body: msg["body"]} {:noreply, socket} end # this is forward by the default `handle_out`, but show here for clarity def handle_out("new_msg", msg, socket) do push socket, "new_msg", msg {:noreply, socket} end end ``` Note that `{:reply, {:ok, resp}, socket}` on the server, triggers `.receive("ok", resp => {})` on the client. The "status" of the reply can be anything, ie `{:reply, {:queued, resp}, socket}` on the server, triggers `.receive("queued", resp => { })` on the client. Also note that client joining, push, and receiving replies all have the same semantics and API now, which is quite nice.