Search code examples
elixirphoenix-frameworkecto

How to use this specific Repo function with web socket actions


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

Solution

  • 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