Always get that my join/3 function is undefined(join crash). What am I missing?

I have these two channels: UserChannel and PrivateChannel. UserChannel is the user’s own lobby and an example of joining that channel is user:3 where 3 is the user’s id. An example of joining private channel is private:3:23 where 3 is the id of the first user and 23 is the id of the second user.

The series of events that a user would take is as follows: In the client side when the user login, I create the socket and send in the user’s id as the socket’s param and try to connect to the UserChannel; this occurs successfully. When the user wants to initiate a private chat, they search by name and a series of profiles appear. Once they click on it, a function called startPrivateChat fires and calls the connectToPrivateChat defined in my chat.js. What happens is that I get a join crash because my join/3 function in App.PrivateChannel is undefined.

Now in the phoenix documentation and from multiple other sources, they say that all I need is one socket and I can use that socket to multiplex to different channels. So what I did is create a chat.js service that references things like the socket, the two channels(this.user_channel and this.private_channel) and another methods like connectToSocket, connectToUserChannel, connectToPrivateChannel etc. So why is this happening and what exactly am I doing wrong?

P.S - I’m using Phoenix 1.2 and Elixir 1.6.5

user_socket.ex

defmodule AppChat.UserSocket do
 use Phoenix.Socket

 channel "user:*", AppChat.UserChannel
 channel "private:*", AppChat.PrivateChannel

 transport :websocket, Phoenix.Transports.WebSocket

 def connect(%{ "user_id" => id }, socket) do
   current_user = AppChat.Repo.get(AppChat.User, String.to_integer(id))
   {:ok, assign(socket, :current_user, current_user)}
 end

 def id(socket), do: "user_socket:#{socket.assigns.current_user.id}"
end

user_channel.ex

defmodule AppChat.UserChannel do
  use AppChat.Web, :channel
  require Logger

  def join("user:" <> user_id, _payload, socket) do
    if socket.assigns.current_user.id == String.to_integer(user_id) do
      {:ok, socket}
    else
      {:error, %{reason: "unauthorized"}}
    end
  end
end

private_channel.ex

defmodule AppChat.PrivateChannel do
  use AppChat.Web, :channel
  require Logger

  def join("private:" <> users_string, _payload, socket) do
    if users_string |> String.split(":") |> Enum.member?(socket.assigns.current_user.id) do
	{:ok, %{}, socket}
    else
	{:error, %{reason: "unauthorized"}}
    end
  end
end

This will never be true, because id is an Int, while the list contains Strings.

It would be nice to see the join crash error log. I guess it might be js crashing, not channels crashing.

1 Like

Yes, you are right. But even after correcting it(to_string(socket.assigns.current_user.id)), I still get the same error. Here is what the error log says.

[info] JOIN private:3:3 to AppChat.PrivateChannel
  Transport:  Phoenix.Transports.WebSocket
  Parameters: %{}
[error] an exception was raised:
    ** (UndefinedFunctionError) function AppChat.PrivateChannel.join/3 is undefined (module AppChat.PrivateChannel is not available)
        AppChat.PrivateChannel.join("private:3:3", %{}, %Phoenix.Socket{assigns: %{current_user: %AppChat.User{__meta__: #Ecto.Schema.Metadata<:loaded, "user_user">, date_joined: ~N[2018-05-22 02:04:54.000000], email: "testbot1@mail.com", first_name: "", id: 3, is_active: true, is_staff: false, is_superuser: true, last_login: ~N[2018-06-21 13:39:17.111225], last_name: "", password: "pbkdf2_sha256$30000$cisWeRJFh94O$jTspzHNklJr+WKd5q1iPTzLz++eW1plIUoI+3DoZJEI=", user_message: #Ecto.Association.NotLoaded<association :user_message is not loaded>, username: "testbot1@mail.com"}}, channel: AppChat.PrivateChannel, channel_pid: #PID<0.391.0>, endpoint: AppChat.Endpoint, handler: AppChat.UserSocket, id: "user_socket:3", joined: false, pubsub_server: AppChat.PubSub, ref: nil, serializer: Phoenix.Transports.WebSocketSerializer, topic: "private:3:3", transport: Phoenix.Transports.WebSocket, transport_name: :websocket, transport_pid: #PID<0.388.0>})
        (phoenix) lib/phoenix/channel/server.ex:170: Phoenix.Channel.Server.init/1
        (stdlib) gen_server.erl:365: :gen_server.init_it/2
        (stdlib) gen_server.erl:333: :gen_server.init_it/6
        (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
[info] Replied private:3:3 :error

Which version of Phoenix?

It looks like Phoenix 1.2 because with newer version, AppChat should be named AppChatWeb.

Yes, I’m using phoenix 1.2. Forgot to mention that sorry.

Phoenix 1.2 is quite back in time… in some of my old channels (Phoenix 1.2.1), I have

defmodule Chatroom.LobbyChannel do
  use Phoenix.Channel

  def join("lobby", _payload, socket) do
    {:ok, socket}
  end
end

You see I use use Phoenix.Channel instead of use AppChat.Web, :channel

I guess You might mix newer syntax with old version.

BTW Your syntax looks good

Ok, but do you have another other insight as to what the problem might be? You said that the phoenix side is fine and that it may be something on client side right?

No, error message is from server side… I was just guessing

Ok then the main question is, why is that I’m able to join the user channel and not the private channel when they are nearly the same code wise? What is getting to me is that it says undefined join/3 function when I have exactly 3 arguments present in the private channel?

After you have added the file/module for the private channel, have you restarted the dev-server?

If that does not work, try rebuilding the app from scratch

Also it’s not saying join/3 where undefined, it says the module is not available.

Phoenix 1.2 vs 1.3 isn’t a problem here. While we added AppWeb naming, it’s just a module name. What the elixir stack trace is telling us is that AppChat.PrivateChannel does not exist. From your provided snippets, the user socket channel matches the defmodule AppChat.PrivateChannel, but is it possible you renamed these in your example? Can you include your, current, exact, user_socket.ex and private_channel.ex, along with a current elixir stack trace? Thanks!

1 Like

Yep, I stopped and restarted the phoenix server to make sure all the files were compiled and up to date. I restarted my angularjs app, was able to connect to my user channel but not my private channel. Still says module is not available and join/3 undefined.

We need to find out why the module is unavailable before we try to delve into the undefined function. Well I do assume, that the undefined function is just a follow up of the missing module and will just work after we fixed the problem with the module.

Can you extract a minimal version that shows the problem and put it on github?

The 3 files you see there are exact. All I’ve been trying to do so far is to make sure that I can connect to these two channel before I can proceed to other stuff. About the elixir stack trace, I added the truncate: infinity option in the config.exs and ran phoenix like this: iex -S mix phoenix.server and the stack trace that I got in the console when trying to start a private chat was this:

[info] JOIN private:3:3 to AppChat.PrivateChannel
  Transport:  Phoenix.Transports.WebSocket
  Parameters: %{}
[error] an exception was raised:
    ** (UndefinedFunctionError) function AppChat.PrivateChannel.join/3 is undefined (module AppChat.PrivateChannel is not available)
        AppChat.PrivateChannel.join("private:3:3", %{}, %Phoenix.Socket{assigns: %{current_user: %AppChat.User{__meta__: #Ecto.Schema.Metadata<:loaded, "user_user">, date_joined: ~N[2018-05-22 02:04:54.000000], email: "testbot1@mail.com", first_name: "", id: 3, is_active: true, is_staff: false, is_superuser: true, last_login: ~N[2018-06-21 13:39:17.111225], last_name: "", password: "pbkdf2_sha256$30000$cisWeRJFh94O$jTspzHNklJr+WKd5q1iPTzLz++eW1plIUoI+3DoZJEI=", user_message: #Ecto.Association.NotLoaded<association :user_message is not loaded>, username: "testbot1@mail.com"}}, channel: AppChat.PrivateChannel, channel_pid: #PID<0.391.0>, endpoint: AppChat.Endpoint, handler: AppChat.UserSocket, id: "user_socket:3", joined: false, pubsub_server: AppChat.PubSub, ref: nil, serializer: Phoenix.Transports.WebSocketSerializer, topic: "private:3:3", transport: Phoenix.Transports.WebSocket, transport_name: :websocket, transport_pid: #PID<0.388.0>})
        (phoenix) lib/phoenix/channel/server.ex:170: Phoenix.Channel.Server.init/1
        (stdlib) gen_server.erl:365: :gen_server.init_it/2
        (stdlib) gen_server.erl:333: :gen_server.init_it/6
        (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
[info] Replied private:3:3 :error

Which is matches the stacktrace from my second post? Is this what you wanted?

Here is something strange, If I start up elixir interactive and type AppChat.PrivateChannel, it finds the module. What does that tell you?

Something basic must be wrong here. If you can’t push your code to GitHub for us to look at, can you double check that you named your AppChat.PrivateChannel with “.ex” and not something like “.exs” ? If your code is as provided, then that’s the only obvious thing that could be going wrong. The error is a basic one to fix, we just need to find out why AppChat.PrivateChannel is undefined. If the code is as you claim, then the module isn’t being compiled for another reason, such as extension.

Ok, I’ll make an example on github and post it here shortly.

Yes I’m convince that it is something on the client side.

I changed my user_channel.ex to this:

defmodule AppChat.UserChannel do
  use AppChat.Web, :channel

  def join("user:" <> user_id, _payload, socket) do
    if socket.assigns.current_user.id == String.to_integer(user_id) do
      {:ok, socket}
    else
      {:error, %{reason: "unauthorized"}}
    end
  end

  def join("private:" <> users_string, _payload, socket) do
    if users_string |> String.split(":") |> Enum.member?(to_string(socket.assigns.current_user.id)) do
      {:ok, %{}, socket}
    else
      {:error, %{reason: "unauthorized"}}
    end
  end
end

and my user_socket to this:

defmodule AppChat.UserSocket do
  use Phoenix.Socket

  channel "user:*", AppChat.UserChannel
  channel "private:*", AppChat.UserChannel

  transport :websocket, Phoenix.Transports.WebSocket

  def connect(%{ "user_id" => id }, socket) do
    current_user = AppChat.Repo.get(AppChat.User, String.to_integer(id))
    {:ok, assign(socket, :current_user, current_user)}
  end

  def id(socket), do: "user_socket:#{socket.assigns.current_user.id}"
end

and when I do user:3 it’s fine and says that it joined and when I do private:3:3 or even private:3 it says that UserChannel module is not available. So how it can say that a module is available in one instance and then the same module not available in another instance?

  1. Can you provide a minified project for us to reproduce the issue?
  2. Are you able to reproduce the issue with phoenix 1.3?