I integrated PUB SUB and my Generated forms do not validate correctly on bad input now

My app is composed of a single LiveView page and 2 “Generator created” routes/data sets. The non LiveView routes go to pages that render the generic “Generator” input forms. When I fill in all fields and submit the form everything works as expected.

My LiveView is used to view “real time” updates across browser tabs when users submit data via the other non-LiveView pages. It works as expected.

The problem is that when a user goes to a non-liveview page and submits a form, if the form does not have all the required fields filled out, it does not validate as expected - with simple red text that says the data is required.

Instead it forwards the user to the page shown here: https://i.imgur.com/jrf6YcI.png

The error is:

no function clause matching in App.TestBeds.broadcast_change/2

Phoenix.PubSub.subscribe(App.PubSub, @topic)17end18192021defp broadcast_change({:ok, result}, event) do22 Phoenix.PubSub.broadcast(App.PubSub, @topic, {__MODULE__, event, result})2324 {:ok, result}25end26

The error references code in this page:


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 """
  Returns the list of testbeds.

  ## Examples

      iex> list_testbeds()
      [%TestBed{}, ...]

  """
  def list_testbeds do

    Repo.all(TestBed)
  end

  @doc """
  Gets a single test_bed.

  Raises `Ecto.NoResultsError` if the Test bed does not exist.

  ## Examples

      iex> get_test_bed!(123)
      %TestBed{}

      iex> get_test_bed!(456)
      ** (Ecto.NoResultsError)

  """
  def get_test_bed!(id), do: Repo.get!(TestBed, id)

  @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

  @doc """
  Deletes a test_bed.

  ## Examples

      iex> delete_test_bed(test_bed)
      {:ok, %TestBed{}}

      iex> delete_test_bed(test_bed)
      {:error, %Ecto.Changeset{}}

  """
  def delete_test_bed(%TestBed{} = test_bed) do
    Repo.delete(test_bed)
    |> broadcast_change([:testbed, :deleted])
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking test_bed changes.

  ## Examples

      iex> change_test_bed(test_bed)
      %Ecto.Changeset{data: %TestBed{}}

  """
  def change_test_bed(%TestBed{} = test_bed, attrs \\ %{}) do
    TestBed.changeset(test_bed, attrs)
  end
end

You’ve defined a head for broadcast_change/2 that matches when the first argument is shaped like {:ok, result} but not one that matches the error case from Repo.insert which is shaped like {:error, changeset}, so you get a crash.

You can resolve this in multiple ways, here are some:

  • add a {:error, _} head for broadcast_change that doesn’t do anything but return its input
  • relocate the broadcasts out of the context, and handle only sending the broadcast on success there

I cobbled the pub sub stuff together using tutorials online. I understand the overall problem in that there is a bad pattern match, but I don’t know what you are suggesting I do with this sentence:

  • add a {:error, _} head for broadcast_change that doesn’t do anything but return its input

I don’t know what that looks like in code.

EDIT

This seems to work

  defp broadcast_change({:ok, result}, event) do
    Phoenix.PubSub.broadcast(App.PubSub, @topic, {__MODULE__, event, result})

    {:ok, result}
  end

  defp broadcast_change({:error, result}, event) do
    {:error, result}
  end