Add these functions to the bottom of mix.exs
:
# Specifies which paths to compile per environment
defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
defp elixirc_paths(_), do: ["lib", "web"]
Next, add these entries to your def project
config:
elixirc_paths: elixirc_paths(Mix.env),
compilers: [:phoenix] ++ Mix.compilers,
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
mix phoenix.gen.resource
renamed tomix phoenix.gen.html
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:
# your_app/web/web.ex
defmodule MyApp.Web do
...
def view do
quote do
...
import Phoenix.Controller, only: [get_flash: 2]
end
end
end
The endpoint now requires a :root
entry in your config/config.exs
:
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
config :phoenix, :code_reloader, true
by
config :your_app, Your.Endpoint, code_reloader: true
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:
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:
if code_reloading? do
plug Phoenix.LiveReloader
plug Phoenix.CodeReloader
end
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 interminate/2
, ie:
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 topush
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
andhandle_out/3
have changed, ie:
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.
- update your
phoenix.js
to the new version: (https://raw.githubusercontent.com/phoenixframework/phoenix/918afc1a4f2d1c40154f156e101acf668b5ec93e/priv/static/phoenix.js) chan.send(...)
has been renamed tochan.push(...)
to match the server messaging commandPhoenix.Socket
no longer connects automatically. You need to explicitly callsocket.connect()
to start the connection, ie:
var socket = new Phoenix.Socket("/ws")
socket.connect()
-
socket.close()
has been renamed tosocket.disconnect()
-
socket.join(..)
api has changed. See the examples below for more details, but all it means is your js goes from:
socket.join("foo:bar", {}, function(chan){
})
to
socket.join("foo:bar", {}).receive("ok", function(chan){
})
// or
var chan = socket.join("foo:bar", {})
chan.receive("ok", function(){
})
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:
- The client and server push messages down the socket to communicate
- The server can reply directly to a pushed message
- The server can broadcast events to be pushed to all subscribers
The flows looks like this:
- client
push("ev1")
-> serverhandle_in("ev1")
->server push("ev2")
-> clienton("ev2")
- client
push("ev1")
-> serverhandle_in("ev1")
->server broadcast("ev2")
-> N subscribershandle_out("ev2")
-> N subscriberspush("ev2") -> N clients
on("ev2")` - client
push("ev1")
-> serverhandle_in("ev")
-> server{:reply, :ok, ...}
-> clientreceive("ok", ...)
Now let's see some cli/server code:
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") )
})
})
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.