使用存在将数据从 model/database 传递到通道

passing data from a model/database to a channel using presence

我构建了一个简单的聊天应用程序,我希望能够在频道 html 页面上的用户名旁边显示用户上传的图像(本地托管)。目前,我正在使用状态来跟踪登录到频道等的用户。我能够覆盖 fetch/2 函数,因为它允许我向 [=15= 添加几个映射字段] 符号与用户模型数据。

根据我对每个功能的不同部分的广泛 IO.inspecting 的了解; fetch/2handle_info/2 和我的 JS 层上的一些 console.logging,fetch/2 函数实际上并没有从数据库中获取任何数据,也没有将其分配给 :metas 地图。

这是我当前的 fetch/2 函数:

def fetch(_topic, entries) do
  query =
    from u in User,
      where: u.id in ^Map.keys(entries),
      select: {u.id, u}

  users = query |> Repo.all |> Enum.into(%{})

  for {key, %{metas: metas}} <- entries, into: %{} do 
    {key, %{metas: metas, user: users[key]}}
end

基本上是直接从文档里翻出来的。从理论上讲,上面的函数应该查询我的用户模型并根据通过条目映射传递给它的 User.id 获取所有用户数据。尽管 users 是我的用户模型的完整地图,但 Users[keys] 返回为空。

此外,根据文档,查询只应 运行 加入,以免使数据库过载,但每次刷新时似乎 运行 4-5 次页。另外需要注意的是,条目中的 user.id 似乎是字符串类型。我不确定这是否重要,我尝试从 JS 层传递一个整数,并使用实际 fetch/2 函数中的 Interger.parse 来更改它,但无济于事。

当我检查用户地图时,我得到了这个:

{"1" => %MyApp.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, 
email: "test@test.com", encrypt_pass: "$pbkdf2-
sha5120000$ebfY956TgIXhEAF.mqLJAg$QWzBubfeiy4Xrf‌​.EsFiU0jEZAuKvV4ZO5a‌​
8QpeFr817C61DuaNfyo5‌​6WWzj6jak2homCFWAINb‌​PrFtCSXUPWTw", gravatar: %
{file_name: "logo.png", updated_at: #Ecto.DateTime<2017-04-20 22:00:08>}, 
id: 1, inserted_at: ~N[2017-04-20 22:00:09.071000], password: nil, 
updated_at: ~N[2017-04-20 22:00:09.090000]}}

我的用户[key] returns 像这样的空映射 %{} 并将输入转换为整数会抛出错误,(Poison.EncodeError) unable to encode value: {nil, "users"} 如果我在 elixir 代码中转换它, 和 where: u.id in ^["undefined"], select: {u.id, u} (elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3 – 如果我从 JS 层转换它。

原始 fetch/2 输出是一个数组,在 :metas 映射中包含 online_at: 1492764577562phx_ref: "OAyzaGE82xc=",在 users var 中包含我的用户 ID 或电子邮件.

我在这里缺少什么?我知道 fetch/2 函数仅作为对我在 handle_info/2 通道函数中调用的 Presence.list/1 函数的回调执行。我还在我的 JS 层中调用 Presence.list 并将其映射到我的状态,以便我可以在 HTML 中生成用户名列表。我只是误解了它是如何工作的,还是有其他更简单的方法可以解决这个问题?如果您需要查看更多代码,我可以提供更多。

编辑:我对这里发生的事情有了更好的理解。我的条目地图实际上是这样的:

%{"1" => %{metas: [%{online_at: 1492798247818, phx_ref: "ELHwA+gWF+0="}]}} 

所以基本上,用户 ID 的字符串 "1" 被映射到 metas 映射上。当我尝试使用 Map.keys(entries) 函数将该键从地图中取出时,它无法从数据库中取出任何东西,因为它是一个字符串,但是,当我将它从 JavaScript 端它会抛出错误,因为无论出于何种原因,phoenix 都希望该键是字符串类型。奇怪的是,如果我将 idid 更改为 email 并尝试使用电子邮件查询数据库,它也不起作用。尽管数据库中的电子邮件是字符串,并且 metas 映射需要 entries 映射的字符串键。

我打算从头开始重建应用程序的这个频道部分,看看是什么导致了这个问题。然后我会回来看看我是否能够修复错误。

您应该首先验证条目映射中的键。

ids = Map.keys(entries)
true = Enum.all?(ids, &is_integer/1)

Ecto 会在插入查询时将字符串转换为整数:

iex(40)> Ecto.Query.from(u in Users, where: u.id in ^[1, 2, "3", nil], select: u.id) |> Repo.all()

输出以下调试日志:

[debug] QUERY OK source="users" db=0.8ms
SELECT u0."id" FROM "users" AS r0 WHERE (r0."id" = ANY()) [[1, 2, 3, nil]]

注意它将字符串“3”强制转换为整数并允许 nil。

不过地图不会这么厚道:

iex(42)> users = %{1 => %{name: "joe"}, 2 => %{name: "jill"}}
%{1 => %{name: "joe"}, 2 => %{name: "jill"}}
iex(43)> users["1"]
nil

因此,在您使用 entries 中的键进行数据库查找和地图查找的代码中,它可能会产生不同的结果。

我已经发现这个问题与我的 fetch/2 函数本身关系不大,而是与我在这种情况下的存在模块和通道的实现有关。基本上,每次有人进入聊天室时,fetch/2 函数都会被调用 4 次,四次中有两次是用空列表值 [] 调用的。

显然,您不能查询带有空列表的 Ecto 模型,因此在这种情况下它也会抛出错误。我尝试在 fetch 函数上放置守卫以过滤掉空列表调用,但即使查询成功,它也不会向我显示我正在寻找的 metas 地图数据。

此外,另一个主要问题是我实施或未实施 token。如果我使用令牌加入聊天室而不是 user(也就是用户名),我就不必通过获取函数 metas 映射传递用户模型数据。实现之后,我就可以成功的把用户模型数据和通道对接起来,通过JS层展示出来,最终放到客户端。

无论如何,谢谢大家的建议。你可能没有回答这个问题(问错问题是我的错),但你确实帮助我做到了。并且还给了我工具,让我在整个过程中更好地理解框架。

If/When 我还有其他问题,我会确保在将问题发布到堆栈溢出之前提出正确的问题,这样我就不会浪费时间。