Skip to content

Instantly share code, notes, and snippets.

@chrismccord
Last active August 29, 2015 14:25
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save chrismccord/931373940f320bf41a50 to your computer and use it in GitHub Desktop.
Save chrismccord/931373940f320bf41a50 to your computer and use it in GitHub Desktop.
Phoenix upgrade instructions 0.14.x to 0.15.0

Sockets and Channels

A new socket behaviour has been introduced to handle socket authentication in a single place, wire up default channel assigns, and disconnect a user's multiplex connection as needed.

First things first, create a UserSocket module in web/channels/user_socket.ex and move all your channel routes from web/route.ex to the user socket: (replace MyApp with your application module)

0.14.x - web/router.ex:

defmodule MyApp.Router do
   ...
  socket "/ws", MyApp do
    channel "rooms:*", RoomChannel
  end
end

0.15.0 - web/channels/user_socket.ex:

defmodule MyApp.UserSocket do
  use Phoenix.Socket

  ## Channels
  channel "rooms:*", MyApp.RoomChannel

  ## Transports
  transport :websocket, Phoenix.Transports.WebSocket
  transport :longpoll, Phoenix.Transports.LongPoll

  # Socket params are passed from the client and can
  # be used to verify and authenticate a user. After
  # verification, you can put default assigns into
  # the socket that will be set for all channels, ie
  #
  #     {:ok, assign(socket, :user_id, verified_user_id)}
  #
  #  To deny connection, return `:error`.
  def connect(_params, socket) do
    {:ok, socket}
  end

  # Socket id's are topics that allow you to identify all sockets for a given user:
  #
  #     def id(socket), do: "users_socket:#{socket.assigns.user_id}"
  #
  # Would allow you to broadcast a "disconnect" event and terminate
  # all active sockets and channels for a given user:
  #
  #     MyApp.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
  #
  # Returning `nil` makes this socket anonymous.
  def id(_socket), do: nil
end

Leaving the default implementations of connect/2 and id/1 will give you the same behavour you had with 0.14.x.

Next, remove the socket mount from your Router and add it to your lib/my_app/endpoint.ex and add a mount for phoenix live reload if using :phoenix_live_reload:

defmodule MyApp.Endpoint do
  use Phoenix.Endpoint, otp_app: :my_app

  socket "/ws", MyApp.UserSocket

  plug Plug.Static, ...

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
    plug Phoenix.LiveReloader
    plug Phoenix.CodeReloader
  end
  
  ...
end

Endpoint changes

Also in your endpoint, replace plug :router, MyApp.Router with plug MyApp.Router, and remove the :encryption_salt option from your plug Plug.Session, ... options.

Channel handle_out callbacks

If using handle_out/3 callbacks in your channels, you now must explicit list which events you want your channels to intercept and pass through handle_out. This change brings large performance improvements by caching encodings across all subscribers if the event is not intercepted by the channel:

0.14.x:

defmodule MyApp.RoomChannel do
  use MyApp.Web, :channel
  ...
  
  def handle_out("new_msg", payload, socket) do
    push socket, "new_msg", %{role: socket.assigns.role, body: payload["body"]}
    {:noreply, socket}
  end
end

0.15.0:

defmodule MyApp.RoomChannel do
  use MyApp.Web, :channel
  
  intercept ["new_msg"]
  ...
  
  def handle_out("new_msg", payload, socket) do
    push socket, "new_msg", %{role: socket.assigns.role, body: payload["body"]}
    {:noreply, socket}
  end
end

Bump your deps

Next update your :phoenix, :phoenix_html, :phoenix_ecto, and :phoenix_live_reload deps in mix.exs:

def deps do
  [...,
   {:phoenix, "~> 0.15"},
   {:phoenix_ecto, "~> 0.8"},
   {:phoenix_html, "~> 1.4"},
   {:phoenix_live_reload, "~> 0.5", only: :dev},
  ...]
end

Next, run $ mix deps.update

Grab the latest phoenix.js

Upgrade to the latest phoenix.js client by replacing web/static/vendor/phoenix.js with the latest version: https://raw.githubusercontent.com/phoenixframework/phoenix/f6ffac8e040f764bd365325fd1fcf54db06f7749/priv/static/phoenix.js

Ecto changes (can skip if not using Ecto)

We are updating two versions fo Ecto from version 0.12 to 0.14/0.15-dev most of the changes you need to make will be reflected in this section taken from the Ecto Changelog. If you have further issues or compiler errors please check the Ecto Changelog for further details.

Below is taken from the change for v0.13 backwards incompatible changes.

  • Ecto.Repo.update!/2 no longer invokes callbacks if there were no changes, avoiding writing to the database at all (use :force to force callback execution)
  • Ecto.Repo.transaction/2 is now flattened. This means that multiple transaction calls will no longer use savepoints, instead it will be considered as a single transaction, where a failure in any transaction block will trigger the outmost transaction to rollback, even if failures are rescued. This should only affect users that were explicitly relying on the savepoints.
  • :date, :time and :datetime were removed in favor of Ecto.Date, Ecto.Time and Ecto.DateTime
  • Ecto.Changeset.errors now return {"must be less than %{count}", count: 3} instead of {"must be less than %{count}", 3}

phoenix_pubsub_redis (skip if not using redis adapter)

Update your :phoenix_pubsub_redis dep to the latest release:

def deps do
  ...
  {:phoenix_pubsub_redis, "~> 0.2.0"}
  ...
end
@sevenseacat
Copy link

Couple of quick notes:

  • I had to use mix deps.update --all else I got an error because the dependencies to update weren't specified.
  • I think it should be made clearer that "Also in your endpoint, replace plug :router, MyApp.Router with plug MyApp.Router" should be done even if you aren't using sockets or channels - I accidentally skipped over that part.
  • If Repo.delete! has also been deprecated, that should be listed in the section talking about insert!/update!
  • Lastly, I also had to update my text.exs file to add a pool option for my Repo config: pool: Ecto.Adapters.SQL.Sandbox.

@sevenseacat
Copy link

That last line should be test.exs, but GitHub won't let me edit my comment \o/

@elliotcm
Copy link

Did you mean signing_salt instead of encryption_salt?

You'll also want mix deps.update --all or specifying the packages to update.

@jrimmer
Copy link

jrimmer commented Jul 28, 2015

@elliotcm No, you leave signing_salt in. With 0.15 I generated a test application and the default included the signing_salt line:

...
plug Plug.Session,
    store: :cookie,
    key: "_test_15_key",
    signing_salt: "ag2Iy6Au"
...

@mgwidmann
Copy link

I can't seem to get my channels to work... I see the following on the JS console:

WebSocket connection to 'ws://localhost:4000/proxy/websocket' failed: Error during WebSocket handshake: Unexpected response code: 400

But am not sure what I've done wrong...
MarocStream/phoenix_ws_proxy@9d6b22b

UPDATE
Think I found my problem... was a bug in my own code...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment