I have put together a small web socket todo app based on reading on-line tutorials and the like. I have a working app that submits data to the database and retrieves data, all with web socket functionality. Phoenix uses a module named Repo to make it convenient to invoke database actions, one Repo function, specifically Repo.update_all does not work when I attempt to set it to broadcast changes. I know what the problem is and my problem is that I don't understand pattern matching well enough to write the function properly. I would like to know/see how to fix this. My code is below with more commentary.
defmodule App.TestBeds do
@moduledoc """
The TestBeds context.
"""
import Ecto.Query, warn: false
alias App.Repo
alias App.TestBeds.TestBed
@topic inspect(__MODULE__)
def subscribe do
Phoenix.PubSub.subscribe(App.PubSub, @topic)
end
defp broadcast_change({:ok, result}, event) do
Phoenix.PubSub.broadcast(App.PubSub, @topic, {__MODULE__, event, result})
{:ok, result}
end
@doc """
Creates a test_bed.
## Examples
iex> create_test_bed(%{field: value})
{:ok, %TestBed{}}
iex> create_test_bed(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_test_bed(attrs \\ %{}) do
%TestBed{}
|> TestBed.changeset(attrs)
|> Repo.insert()
|> broadcast_change([:testbed, :created])
end
@doc """
Updates a test_bed.
## Examples
iex> update_test_bed(test_bed, %{field: new_value})
{:ok, %TestBed{}}
iex> update_test_bed(test_bed, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_test_bed(%TestBed{} = test_bed, attrs) do
test_bed
|> TestBed.changeset(attrs)
|> Repo.update()
|> broadcast_change([:testbed, :updated])
end
# This function does not work (Wrong number of arguments)
def reset_testbeds do
Repo.update_all(TestBed, set: [status: "Available"])
|> broadcast_change([:testbed, :reset])
end
end
Stripped down version of the LiveView I am calling the function from
defmodule AppWeb.PageLive do
use AppWeb, :live_view
alias App.TestBeds
alias App.StatusActions
def mount(_params, _session, socket) do
TestBeds.subscribe()
{:ok, socket}
end
def handle_event("sandbox", params, socket) do
TestBeds.reset_testbeds()
{:noreply, socket}
end
def render(assigns) do
~H"""
<body class="bg-blue_body">
<form phx-click="sandbox">
<button
class="bg-pink-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"
name="sandbox"
>
CLICK ME
</button>
</form>
</body>
"""
end
end
I am trying to invoke reset_testbeds from a live view so that all testbeds update their status property to "Available". I tried invoking the function like this: TestBeds.reset_testbeds()
As I mentioned at the top of the submission, the number of arguments does not match. I tried rewriting it different ways to no avail. My mental model of pattern matching and functions isn't developed yet.
For clarity the code below will change the data and update but it does not do it across browser tabs per the websocket functionality:
def reset_testbeds do
Repo.update_all(TestBed, set: [status: "Available"])
|> broadcast_change([:testbed, :reset])
end
This is the bulk of pub/sub code
@topic inspect(__MODULE__)
def subscribe do
Phoenix.PubSub.subscribe(App.PubSub, @topic)
end
defp broadcast_change({:ok, result}, event) do
Phoenix.PubSub.broadcast(App.PubSub, @topic, {__MODULE__, event, result})
{:ok, result}
end
My liveview handle info function is:
def handle_info({TestBeds, [:testbed | _], _}, socket) do
{:noreply, assign(socket, testbeds: TestBeds.list_testbeds())}
end
I get this error
(FunctionClauseError) no function clause matching in App.TestBeds.broadcast_change/2
(app 0.1.0) lib/app/test_beds.ex:17: App.TestBeds.broadcast_change({4, nil}, [:testbed, :reset])
(app 0.1.0) lib/app_web/live/page_live.ex:117: AppWeb.PageLive.handle_event/3
(phoenix_live_view 0.18.16) lib/phoenix_live_view/channel.ex:396: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
(telemetry 1.2.1) c:/Users/Bill/Desktop/app/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
(phoenix_live_view 0.18.16) lib/phoenix_live_view/channel.ex:216: Phoenix.LiveView.Channel.handle_info/2
(stdlib 4.0.1) gen_server.erl:1120: :gen_server.try_dispatch/4
(stdlib 4.0.1) gen_server.erl:1197: :gen_server.handle_msg/6
(stdlib 4.0.1) proc_lib.erl:250: :proc_lib.wake_up/3
Repo.update_all/3
returns a tuple {non_neg_integer(), nil | [term()]}
, while you are trying to pass it to the function having the only clause broadcast_change({:ok, result}, event)
.
In handle_info({TestBeds, [:testbed | _], _}, socket)
clause you discard this result
whatever it is supposed to be, hence you might simply declare another clause of broadcast_change/2
for this particular case. Just add this:
defp broadcast_change({count, _}, [:testbed, :reset]) do
Phoenix.PubSub.broadcast(
App.PubSub, @topic, {__MODULE__, [:testbed, :reset], count})
{:ok, count}
end