Handling send(self(), ...) in another Module

I’m currently trying to figure out how to handle a send(self(), ...) call in another Module within THAT module?

Let me elaborate. My setup would be the following:

defmodule Module.A do
  use Phoenix.LiveView # Could also be Phoenix.Socket since they have the same API, I believe
  alias Module.B

  def handle_event("do_stuff", socket) do
    B.send_to_self(socket)
  end
end

defmodule Module.B do
  def send_to_self(socket) do
    send(self(), :my_event)
    {:no_reply, socket}
  end

  def handle_info(:my_event, socket) do
    IO.puts("Event handled!")
    {:no_reply, socket}
  end
end

So, from Module.A I want to call a function of Module.B, which sends a Kernel message to the same process that Module.A is running in. However, instead of writing the handle_info/2 handler into the Module.A module, I want to keep everything in Module.B in order to make Module.B reusable. Unfortunately, when I run the setup, I receive the error: function Module.A.handle_info/2 is undefined or private. Do you know how to solve this Error, please?

There is only a single way to receive a message, Kernel.SpecialForms.receive/1.

Either you do this manually (you usually do not want to do not want to do this in “managed” processes), or if you have a managed process, it will eventually receive that message and call the registered callback for that case.

I do not use LiveView, but my assumption here is, that it is similar to a GenServer and therefore received the message you didn’t selectively took of the queue earlier. So it will try to call is associated modules handle_info/2 callback, which you haven’t defined. Obviously.

Remember though, that when you do a “manual” receive, your process gets blocked and wont do anything until it receives a message matching the pattern.

:wave:

Maybe

defmodule Module.A do
  use Phoenix.LiveView # Could also be Phoenix.Socket since they have the same API, I believe
  alias Module.B

  def handle_event("do_stuff", socket) do
    B.send_to_self(socket)
  end
  
  def handle_info(msg, state) do
    B.handle_info(msg, state)
  end

  # or defdelegate handle_info(msg, socket), to: B
end

Phoenix.LiveView is part of a behaviour (Erlang Behaviors … and how to behave around them)

Phoenix.LiveView is the behaviour module - your Module.A is the callback module. All callbacks required by the behaviour module must be satisfied by the callback module - it follows that there can only be one callback module per behaviour implementation.

Therefore any “reusable” code has to be called explicitly from the callback module. To that end the defdelegate/2 syntax sugar could be helpful.

1 Like

Thanks for the tip about receive. If possible, I’d like to use a managed process indeed since I don’t want to block the receiving process. I’ll look more into how Phoenix.Channels handles this.

Oh, I see! That makes sense! Thank you! I guess that for now, the only option would be to delegate the functions to Module.B with defdelegate/2 then. I’ll try that althought it isn’t really pretty unfortunately :frowning: But thanks!

Not a recommended practice as with OTP behaviours the receive is typically handled by the OTP behaviour module. The implementor of the callback module should focus on implementing the callbacks.

2 Likes

Thanks all for your lightning fast responses! I ran into this problem while developing a State Management library for Phoenix LiveView. I made another topic about its first implementation here. Feel free to check my implementation there as well :slight_smile: