Greetings!
I’m still in the learning process and I’m trying to piece together a basic script to fetch user data from a database to use with Phoenix.Presence. I am starting with the example in the documentation: Phoenix.Presence — Phoenix v1.6.2 . I almost have a fully working example, but I hit an error that I can’t figure out, and I need some advice on whether I’m using “import”, “use” and “alias” to tie together my modules correctly.
Here is what I got so far:
/lib/exchat_web/channels/presence.ex:
defmodule ExchatWeb.Presence do
alias ExchatWeb.Accounts
use Phoenix.Presence, otp_app: :exchat,
pubsub_server: Exchat.PubSub
def fetch(_topic, presences) do
users = presences |> Map.keys() |> Accounts.get_users_map()
for {key, %{metas: metas}} <- presences, into: %{} do
{key, %{metas: metas, user: users[String.to_integer(key)]}}
end
end
end
/lib/exchat_web/accounts/accounts.ex:
defmodule ExchatWeb.Accounts do
import Ecto.Query
alias Exchat.Repo
alias Accounts
@derive Jason.Encoder
def get_users_map(ids) do
query =
from u in Profiles, # use lib/exchat_web/schemas/profiles.ex
where: u.id in ^ids,
select: {u.id, u}
query |> Repo.all() |> Enum.into(%{})
end
end
/lib/exchat_web/schemas/profiles.ex:
defmodule Profiles do
use Ecto.Schema
schema "members" do
field :username, :string
end
end
Background:
Phoenix.Presence will track registered users that have integer user ids (also found in database), as well as anonymous readers that have randomly generated alphanumeric characters stored in cookies (not found in database). The anonymous readers can enter public chat rooms and read, but they won’t be able to chat. I’ll be using a simple “fetch” function to retrieve the user’s username, profile image, age, etc from the database - as recommended in the Phoenix.Presence documentation. We won’t need (nor want) to fetch anything in the database for these anonymous users. We’ll just count up those anonymous users in the presence.list using client javascript and display a count (# of viewers).
Question 1: The first thing I don’t know how to do is when “fetch” function is called, how would I be able to exclude the non-integer user ids (the anonymous users), as these won’t be found in the database, and sometimes the database query will come up empty if everyone is anonymous in a room? What would be a good way to handle this? I would need a simple code example to look at.
The Error Message: When I run the app above, I eventually end up with errors related to Jason.Encoder (I’m using an API setup for my app). I’m assuming the data coming from the database needs to be encoded to JSON, or does this happen at a different step in the “fetch” function or presence module? Or do I have to set this up in the Schema? I’m not really familiar with this:
[error] Task #PID<0.506.0> started from ExchatWeb.Presence_shard0 terminating
** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for %Profiles{__meta__: #Ecto.Schema.Metadata<:loaded, "members">, id: 1, username: "John"} of type Profiles (a struct), Jason.Encoder protocol must always be explicitly implemented.
If you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:
@derive {Jason.Encoder, only: [....]}
defstruct ...
It is also possible to encode all fields, although this should be used carefully to avoid accidentally leaking private information when new fields are added:
@derive Jason.Encoder
defstruct ...
Finally, if you don't own the struct you want to encode to JSON, you may use Protocol.derive/3 placed outside of any module:
Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])
Protocol.derive(Jason.Encoder, NameOfTheStruct)
. This protocol is implemented for the following type(s): Ecto.Association.NotLoaded, Ecto.Schema.Metadata, DateTime, Atom, Float, Integer, Jason.Fragment, Any, Map, Date, NaiveDateTime, Time, BitString, Decimal, List
(jason 1.2.2) lib/jason.ex:199: Jason.encode_to_iodata!/2
(phoenix 1.5.12) lib/phoenix/socket/serializers/v2_json_serializer.ex:29: Phoenix.Socket.V2.JSONSerializer.fastlane!/1
(phoenix 1.5.12) lib/phoenix/channel/server.ex:101: anonymous fn/5 in Phoenix.Channel.Server.dispatch/3
(elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix 1.5.12) lib/phoenix/channel/server.ex:86: Phoenix.Channel.Server.dispatch/3
(elixir 1.12.2) lib/registry.ex:474: Registry.dispatch/4
(phoenix_pubsub 2.0.0) lib/phoenix/pubsub.ex:286: Phoenix.PubSub.dispatch/5
(phoenix 1.5.12) lib/phoenix/presence.ex:364: anonymous fn/4 in Phoenix.Presence.Tracker.handle_diff/2
(stdlib 3.15.2) maps.erl:410: :maps.fold_1/3
(phoenix 1.5.12) lib/phoenix/presence.ex:363: anonymous fn/3 in Phoenix.Presence.Tracker.handle_diff/2
(elixir 1.12.2) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
(stdlib 3.15.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Function: #Function<0.58243068/0 in Phoenix.Presence.Tracker.handle_diff/2>
Args: []
Any other advice and tips would be greatly appreciated.
Thanks!