How do I write a handle_call for this function?

I have a gen server function like this



  def check_online(a) do
    GenServer.call(a, :check_online)
  end

and I have defined a handle call function like this

 def handle_call(:check_online, _from, state) do
    {:ok, check}  = Conn.send(pid, Stanza.presence)
    {:reply, check, state}
  end

But the Conn.send function accepts two arguments. pid and some function. I’m confused how do I pass the above
pid in handle call function

What is Conn? Is this Plug.Conn?

No. https://github.com/scrogson/romeo I’m using this library

so it has this function

alias Romeo.Stanza
alias Romeo.Connection, as: Conn

While I don’t know the details of that library, at some point you likely called GenServer.start_link. This would have returned a pid.

Yes Here’s the full code.

def start_link(xmpp_config) do
    GenServer.start_link(__MODULE__, xmpp_config, name: @name)
  end

  def init(xmpp_config) do
    opts = [jid: xmpp_config[:jid], password: xmpp_config[:password]]
    {:ok, conn} = Conn.start_link(opts)
    {:ok, %{conn: conn, user_name: xmpp_config[:user_name], rooms: []}}
  end

  def check_online(a) do
    GenServer.call(a, {:check_online, pid})
  end

  def handle_call({:check_online, pid}, _from, state) do
     {:ok, check} = Conn.send(pid, Stanza.presence)
    {:reply, check, state}
  end
end

The pid which is returned I want to use that pid in check_online function. But the pid which is returned is a tuple {:ok, pid}. So that’s why I’m passing a variable in check_online function “a”. Is this approach correct ?

I’m a bit rusty, but here’s my take.

The documentation you referred shows that Conn.start_link returns a pid. So I’d change the last two lines of your init def to look like this:

  def init(xmpp_config) do
    opts = [jid: xmpp_config[:jid], password: xmpp_config[:password]]
    {:ok, pid} = Conn.start_link(opts)
    {:ok, %{pid: pid user_name: xmpp_config[:user_name], rooms: []}}
  end

The last line of the init will set the state and the pid will now come in as part of that state in handle_call. You can extract the pid using pattern matching:

def handle_call(:check_online, _from, %{pid: pid} = state) do
  ...
end

Thanks. I did this as you suggested. But I’m getting an error

def start_link(xmpp_config) do
    GenServer.start_link(__MODULE__, xmpp_config, name: @name)
  end

  def init(xmpp_config) do
    opts = [jid: xmpp_config[:jid], password: xmpp_config[:password]]
    {:ok, pid} = Conn.start_link(opts)
    {:ok, %{pid: pid, user_name: xmpp_config[:user_name], rooms: []}}
  end

  def check_online(a) do
    GenServer.call(a, :check_online)
  end

  def handle_call(:check_online, _from, %{pid: pid} = state) do
     {:ok, check} = Conn.send(pid, Stanza.presence)
    {:reply, check, state}
  end
[error] GenServer ChatApiWeb.Client terminating
** (MatchError) no match of right hand side value: :ok
    (chat_api 0.1.0) lib/chat_api_web/channels/client.ex:24: ChatApiWeb.Client.handle_call/3
    (stdlib 3.12.1) gen_server.erl:661: :gen_server.try_handle_call/4
    (stdlib 3.12.1) gen_server.erl:690: :gen_server.handle_msg/6
    (stdlib 3.12.1) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.1128.0>): :check_online
** (EXIT from #PID<0.1128.0>) shell process exited with reason: an exception was raised:
    ** (MatchError) no match of right hand side value: :ok
        (chat_api 0.1.0) lib/chat_api_web/channels/client.ex:24: ChatApiWeb.Client.handle_call/3
        (stdlib 3.12.1) gen_server.erl:661: :gen_server.try_handle_call/4
        (stdlib 3.12.1) gen_server.erl:690: :gen_server.handle_msg/6
        (stdlib 3.12.1) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Is this related to handle_call function where I’ve set the {:ok, check} because according to the library I have to do this

:ok = Conn.send(pid, Stanza.presence)

Just to be clear, which line is 24? Just trying to understand where this is coming from.

 (chat_api 0.1.0) lib/chat_api_web/channels/client.ex:24: ChatApiWeb.Client.handle_call/3

Nevermind, I think it’s pretty clear what the error is. Yes it’s what you said about the documentation.

The documentation says to use:

# Send presence to the server
:ok = Conn.send(pid, Stanza.presence)

On the other hand, your code says:

{:ok, check} = Conn.send(pid, Stanza.presence)

This is not a match. {:ok, check} does not match :ok.

If this is still not clear, read through this: https://elixir-lang.org/getting-started/pattern-matching.html

1 Like

But I have a doubt in this case. Because the start_link function expects a variable which has
this

xmpp_config = [jid: "example@localhost", password: "example"]

In init function, it accepts those arguments and return the {ok, pid} but not this

{:ok, %{pid: pid, user_name: xmpp_config[:user_name], rooms: []}}

Is anything I’m doing wrong here?

Sorry, I’m really trying here, but I’m struggling to understand your question.

The def start_link(xmpp_config) is a defintion you have given. I’m not clear what you mean when you say “it expects” a variable. It only expects it because you’ve said it should be expected in this definition.

What I think you’re trying to do here is define a worker. Your application likely has a Supervisor and you’re defining this as one of the workers it needs to start (this is normally defined in the file application.ex). You can tell the Supervisor what initial arguments to pass into the start_link method (refer to this https://hexdocs.pm/elixir/Supervisor.Spec.html#worker/3).

So the steps look like this:

  1. Your worker is started, presumably passing in xmpp_config as an argument. The supervisor kicks off the process of staring your worker calling your start_link method.
  2. Now within this method, you call GenServer.start_link. Here you’re telling it to to call the init method of the same module (the fact that you wrote __MODULE__ tells it you want it to call init within this same module). Since you are using xmpp_config as the second argument to GenServer.start_link it knows to pass this as the argument to init.
  3. Now we enter your init function. You’re defining the opts on the first line. On the second line you’re calling Conn.start_link which is defined by that Romeo package.
    1. Romeo returns a tuple containing {:ok} and the pid.
    2. As the last step, you’re telling init to return {:ok, %{pid: pid, user_name: xmpp_config[:user_name], rooms: []}}. Whatever is returned from init will be the initial state.
    3. The state is passed into handle_call and you can use that state however you want. You can also change that state by returning a new state from handle_call (refer to https://hexdocs.pm/elixir/GenServer.html#c:handle_call/3)

Is that clear?

1 Like

Thanks