Last time we used Phoenix generators to scaffold out our users and content.
In this episode, we'll start converting the User pages into Live Views. First is adding some PubSub functions to the Accounts Context for user-related LiveViews. Then we'll write a LiveView for the users index page.
Setting up PubSub
Since Phoenix Framework already includes PubSub, functionality, we can let it do the heavy lifting and just make a few helpers in our Accounts context. At the top of accounts.ex
, add the following:
@topic inspect(__MODULE__)
def subscribe do
Phoenix.PubSub.subscribe(Reactor.PubSub, @topic)
end
def subscribe(user_id) do
Phoenix.PubSub.subscribe(Reactor.PubSub, @topic <> "#{user_id}")
end
In the code above, @topic
is just the name of the module, i.e., "Accounts". Clients subscribing to the list of all users, will just use that as the PubSub channel name. Clients subscribing to a specific user, will subscribe to a channel name of "Accounts" appended by the user's id.
Then, at the bottom of the Accounts module, add a notify_subscribers
function:
defp notify_subscribers({:ok, result}, event) do
Phoenix.PubSub.broadcast(Reactor.PubSub, @topic, {__MODULE__, event, result})
Phoenix.PubSub.broadcast(
Reactor.PubSub,
@topic <> "#{result.id}",
{__MODULE__, event, result}
)
{:ok, result}
end
defp notify_subscribers({:error, reason}, _), do: {:error, reason}
This function takes a tuple returned by Ecto—either :ok
and some data or :error
and a reason—as the first argument and an event name. It broadcasts the data or error along with the event name to all subscribers. Finally, the function returns its first argument unchanged, so we can drop it into CRUD pipelines without changing their output.
Add it to the end of all context functions that involve a write:
def create_user(attrs \\ %{}) do
%User{}
|> User.changeset(attrs)
|> Repo.insert()
|> notify_subscribers([:user, :created])
end
def update_user(%User{} = user, attrs) do
user
|> User.changeset(attrs)
|> Repo.update()
|> notify_subscribers([:user, :updated])
end
def delete_user(%User{} = user) do
Repo.delete(user)
|> notify_subscribers([:user, :deleted])
end
Create a LiveView for Index
Create a new file reactor_web/live/user_live/index.ex
with the following contents:
defmodule ReactorWeb.UserLive.Index do
use Phoenix.LiveView
alias Reactor.Accounts
alias ReactorWeb.UserView
def render(assigns), do: UserView.render("index.html", assigns)
def mount(_params, _session, socket) do
if connected?(socket), do: Reactor.Accounts.subscribe()
{:ok, fetch(socket)}
end
defp fetch(socket) do
users = Accounts.list_users()
assign(socket, users: users, page_title: "Listing Users")
end
def handle_event("delete_user", %{"id" => user_id}, socket) do
Accounts.get_user!(user_id)
|> Accounts.delete_user()
{:noreply, socket}
end
def handle_info({Accounts, [:user, _], _}, socket) do
{:noreply, fetch(socket)}
end
end
The handle_info
function listens to the notifications broadcast by accounts, and regardless of whether the event, is [:user, :created]
, [:user, :updated]
, [:user, :deleted]
or :user
anything, it fetches a fresh listing of users from the database.
The handle_event
function listens to the delete_user
event, which is sent from the "index.html.leex" live template.
The full series playlist for Simple Phoenix LiveView App on YouTube is slightly ahead of what's published here at the moment.
(Source code available for premium members)
4 Comments
Very awesome episode -
defp notify_subscribers({:error, reason}, _), do: {:error, reason
should bedefp notify_subscribers({:error, reason}, _), do: {:error, reason}
:)Right you are, @jrissler! It looks like I cropped that closing brace off… just updated it. Thanks.
That's an awesome episode indeed!
Thank you! Very useful