(KeyError) key :user_id not found

Elixir noob here.

I am rewriting discussion forum tutorial from Stephen Grider using Phoenix 1.6. When associating comments with user_id, I got this error I don’t know which part of my code to fix.

Blockquote

[error] GenServer #PID<0.640.0> terminating
** (KeyError) key :user_id not found in: %{topic: %Discuss.Topics.Topic{meta: ecto.Schema.Metadata<:loaded, “topics”>, comments: , id: 19, inserted_at: ~N[2023-01-28 06:04:46], title: “Saved by The Bell Version 2.0”, updated_at: ~N[2023-01-28 06:04:57], user: ecto.Association.NotLoaded, user_id: 1}}
(discuss 0.1.0) lib/discuss_web/channels/comments_channel.ex:38: DiscussWeb.CommentsChannel.handle_in/3
(phoenix 1.6.15) lib/phoenix/channel/server.ex:315: Phoenix.Channel.Server.handle_info/2
(stdlib 3.17.2.1) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.17.2.1) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.17.2.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: %Phoenix.Socket.Message{event: “comment:add”, join_ref: “3”, payload: %{“content” => “hoioihoih”}, ref: “4”, topic: “comments:19”}
State: %Phoenix.Socket{assigns: %{topic: %Discuss.Topics.Topic{meta: ecto.Schema.Metadata<:loaded, “topics”>, comments: , id: 19, inserted_at: ~N[2023-01-28 06:04:46], title: “Saved by The Bell Version 2.0”, updated_at: ~N[2023-01-28 06:04:57], user: ecto.Association.NotLoaded, user_id: 1}}, channel: DiscussWeb.CommentsChannel, channel_pid: #PID<0.640.0>, endpoint: DiscussWeb.Endpoint, handler: DiscussWeb.UserSocket, id: nil, join_ref: “3”, joined: true, private: %{log_handle_in: :debug, log_join: :info}, pubsub_server: Discuss.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: “comments:19”, transport: :websocket, transport_pid: #PID<0.634.0>}

Here is the code where the error screaming at

Blockquote

def handle_in(name, %{“content” => content}, socket) do

topic = socket.assigns.topic
user_id = socket.assigns.user_id

changeset =
  topic
  |> Ecto.build_assoc(:comments, user_id: user_id)
  |> Discuss.Topics.Comment.changeset(%{content: content})

case Discuss.Repo.insert(changeset) do
  {:ok, comment} ->
    broadcast!(socket, "comments:#{socket.assigns.topic.id}:new",
      %{comment: comment})
    {:reply, :ok, socket}
  {:error, _reason} ->
    {:reply, {:error, %{errors: changeset}}, socket}
end

end

How should I fix this?

Learning Elixir and Phoenix can be a challenge here as you need to know two things:

  • How Phoenix channels work and allow you to assign things at the start
  • How Elixir works with maps and assigns.

Based off the code example, it’s clear that you are attempting to use Phoenix Sockets and more over, you are using channels. Before the websocket connection is established (as in before the 101 Upgrade response is sent), the web application needs to contextualise the connection with the user, which is typically provided with a signed token.

The channel documentation goes into detail about the set of phases that occur when a channel is being established, but the one that you are interested in is: Channels — Phoenix v1.7.0-rc.2. In fact, provided that you read the documentation carefully, you should be able to piece together why its missing and what set of changes you need to make. I can guess where the issue is, but you haven’t provided the code for that and I can’t simply assume what you are working from.

hey, thanks. which code do you need me to provide? there are so many files here I read back and forth to see the possible errors.

There’s only one place that you should be assigning your :user_id, and its in the documentation. user_socket.ex should exist, but before you immediately assume that you don’t know why it’s happening, take a moment and read the relevant section. It might just click. Don’t assume that since you’re a beginner that it’s outside your scope to reason.

I got it right this time.

Blockquote
def connect(%{“token” => token}, socket, _connect_info) do
case Phoenix.Token.verify(socket, “key”, token) do
{:ok, user_id} → {:ok, assign(socket, :user_id, user_id)}
{:error, _error} → :error
end
{:ok, socket}
end

It’s the {:ok, socket} that causing the error. Thanks!!!

3 Likes

Cool, there we go. All you needed to just some time to think about it. Well done!

2 Likes