Fix stale PubSub subscriptions on record navigation

Subscription management was only done in mount/3, meaning
navigating to a different record via handle_params/3 left the
old subscription active. A background update for the old record
would then overwrite the currently displayed record.

Fix: manage subscriptions in handle_params/3 (unsubscribe old,
subscribe new) via shared RecordActions.manage_subscription/2,
and guard handle_info({:update,...}) against mismatched IDs.
This commit is contained in:
Claudio Ortolina
2026-05-07 10:14:10 +01:00
parent 365195ff75
commit 5f240fe573
4 changed files with 41 additions and 15 deletions
+8
View File
@@ -110,6 +110,14 @@ defmodule MusicLibrary.Records do
Phoenix.PubSub.subscribe(MusicLibrary.PubSub, "records:#{record_id}") Phoenix.PubSub.subscribe(MusicLibrary.PubSub, "records:#{record_id}")
end end
@doc """
Unsubscribes the calling process from updates for a given record.
"""
@spec unsubscribe(String.t()) :: :ok | {:error, term()}
def unsubscribe(record_id) do
Phoenix.PubSub.unsubscribe(MusicLibrary.PubSub, "records:#{record_id}")
end
@spec notify_update(Record.t()) :: :ok | {:error, term()} @spec notify_update(Record.t()) :: :ok | {:error, term()}
def notify_update(record) do def notify_update(record) do
Phoenix.PubSub.broadcast( Phoenix.PubSub.broadcast(
@@ -348,11 +348,7 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
end end
@impl true @impl true
def mount(%{"id" => record_id}, _session, socket) do def mount(_params, _session, socket) do
if connected?(socket) do
Records.subscribe(record_id)
end
{:ok, {:ok,
socket socket
|> assign(:current_section, :collection) |> assign(:current_section, :collection)
@@ -362,6 +358,8 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
@impl true @impl true
def handle_params(%{"id" => id}, _, socket) do def handle_params(%{"id" => id}, _, socket) do
RecordActions.manage_subscription(socket, id)
record = Records.get_record!(id) record = Records.get_record!(id)
last_listened_track = ListeningStats.get_last_listened_track(record) last_listened_track = ListeningStats.get_last_listened_track(record)
play_count = ListeningStats.play_count(record) play_count = ListeningStats.play_count(record)
@@ -474,10 +472,14 @@ defmodule MusicLibraryWeb.CollectionLive.Show do
@impl true @impl true
def handle_info({:update, record}, socket) do def handle_info({:update, record}, socket) do
{:noreply, if record.id == socket.assigns.record.id do
socket {:noreply,
|> RecordActions.handle_record_updated(record) socket
|> assign_similar_records()} |> RecordActions.handle_record_updated(record)
|> assign_similar_records()}
else
{:noreply, socket}
end
end end
defp page_title(action, record) do defp page_title(action, record) do
@@ -284,13 +284,9 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
end end
@impl true @impl true
def mount(%{"id" => record_id}, _session, socket) do def mount(_params, _session, socket) do
current_date = DateTime.utc_now() |> DateTime.to_date() current_date = DateTime.utc_now() |> DateTime.to_date()
if connected?(socket) do
Records.subscribe(record_id)
end
{:ok, {:ok,
socket socket
|> assign(current_section: :wishlist) |> assign(current_section: :wishlist)
@@ -299,6 +295,8 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
@impl true @impl true
def handle_params(%{"id" => id}, _, socket) do def handle_params(%{"id" => id}, _, socket) do
RecordActions.manage_subscription(socket, id)
record = Records.get_record!(id) record = Records.get_record!(id)
online_store_templates = OnlineStoreTemplates.list_enabled_templates() online_store_templates = OnlineStoreTemplates.list_enabled_templates()
@@ -367,7 +365,11 @@ defmodule MusicLibraryWeb.WishlistLive.Show do
@impl true @impl true
def handle_info({:update, record}, socket) do def handle_info({:update, record}, socket) do
{:noreply, RecordActions.handle_record_updated(socket, record)} if record.id == socket.assigns.record.id do
{:noreply, RecordActions.handle_record_updated(socket, record)}
else
{:noreply, socket}
end
end end
defp page_title(action, record) do defp page_title(action, record) do
@@ -97,6 +97,20 @@ defmodule MusicLibraryWeb.LiveHelpers.RecordActions do
end end
end end
@doc """
Manages PubSub subscriptions when navigating between records.
Unsubscribes from the previous record (if any) and subscribes to the new one,
but only when the socket is connected.
"""
def manage_subscription(socket, new_id) do
if Phoenix.LiveView.connected?(socket) do
if socket.assigns[:record], do: Records.unsubscribe(socket.assigns.record.id)
Records.subscribe(new_id)
end
:ok
end
def handle_chats_changed(socket) do def handle_chats_changed(socket) do
{:noreply, {:noreply,
assign(socket, :chat_count, Chats.count_chats(:record, socket.assigns.record.musicbrainz_id))} assign(socket, :chat_count, Chats.count_chats(:record, socket.assigns.record.musicbrainz_id))}