Programming Phoenix LiveView B3.0: incorrect pattern match and other minor bugs (page 247)

I think that the second clause will never match because Presence.get_by_key/2 returns a presences() map that is defined as

    presences() :: %{required(String.t()) => %{metas: [map()]}}

so, in the second clause, instead of

  %{users: active_users_for_product} ->

we should have

  %{metas: [%{users: active_users_for_product}]} ->

this is the complete code

def track_user(pid, product, token) do
  user = Accounts.get_user_by_session_token(token)
  case Presence.get_by_key(@user_activity_topic, product.name) do 
    [] ->
      Presence.track(
        pid,
       @user_activity_topic, product.name,
       %{users: [%{email: user.email}]}
       )

  # %{users: active_users_for_product} ->
  %{metas: [%{users: active_users_for_product}]} ->
      Presence.update(pid, @user_activity_topic, product.name, %{
users: [active_users_for_product | %{email: user.email}] })
end end

I also noticed another little bug with list concatenation: using the | operator to append the new user to the previous, the list of previous users should be prepended by the map with the new user, not followed by. Instead of

  Presence.update(pid, "user_activity", product.name, %{
    users: [active_users_for_product | %{email: user.email}] })

should be

  Presence.update(pid, "user_activity", product.name, %{
    users: [%{email: user.email} | active_users_for_product] })

But now we have the last problem: Presence.update/4 will return {:error, :nopresence} when trying to add a new user when she/he is viewing the same product page. This because only the socket/PID tracking the presence object can update/modify it, as noticed here. A workaround could be registering the process with a name, as explained in this article.

Besides these errata, your book is fantastic, it explain very well not only LiveView but many other aspect of the E/OTP ecosystem, and it is superbly written, a real joy to read!

Hi @guidotripaldi!
You are right about the first issue you mentioned re: return of Presence.get_by_key/2. You’ll find the updated code in the next Beta release :slight_smile:

Also good catch re: list concatenation should add new elements, to the front, not the back! That will also be corrected in the next Beta release :rocket:

Regarding the last issue you’re experiencing:

Presence.update/4 will return {:error, :nopresence} when trying to add a new user when she/he is viewing the same product page.

I’m not able to replicate this error. I’ve tried:

  • User 1 visits /products/1 in one tab and same user 1 also visits /products/1 in another tab
  • User 1 visits /products/1 in one tab and user 2 visits /products/1 in another window

Can you share what steps you took when you saw this error?

Thanks! And so glad you’re enjoying the book :slight_smile: :slight_smile: :slight_smile:

Hi @SophieDeBenedetto,

I’m not able to replicate this error.
[…]
Can you share what steps you took when you saw this error?

mmh, very strange because I can always replicate the error.

my environment: MacOS 11.2.3, Erlang/OTP 23, IEx 1.11.4, LV 0.15.1

Some combinations tested (Browser1 = Safari on macOS, Browser2 = Firefox on macOS, Broswer3 = Safari on iOS) :

Test 1a:

  • Browser1, User1, admin-dashboard
  • Browser1, User1, /products/11
  • Browser1, User1, /products/11

Presence.update/4 result for the first /products/:id instance: {:ok, ref}
Presence.update/4 result for the second /products/:id instance: {:error, :nopresence}

Test 1b:

  • Browser1, User1, admin-dashboard
  • Browser1, User1, /products/11
  • Browser1, User1, /products/12

Presence.update/4 result for the first /products/:id instance: {:ok, ref}
Presence.update/4 result for the second /products/:id instance: {:ok, ref}

Test 2a:

  • Browser1, User1, admin-dashboard
  • Browser1, User1, /products/11
  • Browser2, User2, /products/11

Presence.update/4 result for the first /products/:id instance: {:ok, ref}
Presence.update/4 result for the second /products/:id instance: {:error, :nopresence}

Test 2b:

  • Browser1, User1, admin-dashboard
  • Browser1, User1, /products/11
  • Browser2, User2, /products/12

Presence.update/4 result for the first /products/:id instance: {:ok, ref}
Presence.update/4 result for the second /products/:id instance: {:ok, ref}

Test 3a:

  • Browser1, User1, admin-dashboard
  • Browser1, User1, /products/11
  • Browser3, User1, /products/11

Presence.update/4 result for the first /products/:id instance: {:ok, ref}
Presence.update/4 result for the second /products/:id instance: {:error, :nopresence}

Test 3b:

  • Browser1, User1, admin-dashboard
  • Browser1, User1, /products/11
  • Browser3, User1, /products/12

Presence.update/4 result for the first /products/:id instance: {:ok, ref}
Presence.update/4 result for the second /products/:id instance: {:ok, ref}

Test 4:

  • Browser1, User1, admin-dashboard
  • Browser2, User2, /products/11
  • Browser3, User3, /products/11

Presence.update/4 result for the first /products/:id instance: {:ok, ref}
Presence.update/4 result for the second /products/:id instance: {:error, :nopresence}

the code:

defmodule PentoWeb.Presence do
  use Phoenix.Presence,
  otp_app: :pento,
  pubsub_server: Pento.PubSub

  alias PentoWeb.Presence
  alias Pento.Accounts
  @user_activity_topic "user_activity"

  def track_user(pid, product, token) do
    user = Accounts.get_user_by_session_token(token)

    case Presence.get_by_key(@user_activity_topic, product.name) do
      [] ->
        Presence.track(
          pid,
          @user_activity_topic,
          product.name,
          %{users: [%{email: user.email}]}
        )
        |> IO.inspect(label: "\n---> Presence.track_user, Presence.track result")

      # %{users: active_users_for_product} ->
      #   Presence.update(pid, @user_activity_topic, product.name, %{
      #     users: [active_users_for_product | %{email: user.email}]
      #   })

      %{metas: [%{users: active_users_for_product}]} ->
        update_params = %{users: [%{email: user.email} | active_users_for_product]}
        |> IO.inspect(label: "\n---> Presence.track_user, update_params")

        Presence.update(
            pid, 
            @user_activity_topic, 
            product.name, 
            update_params
            )
        |> IO.inspect(label: "\n---> Presence.track_user, Presence.update result")

    end
  end
end