4

I have a simple event handler in elixir using GenEvent:

defmodule myHandler do
    use GenEvent
    #Callback
    def handle_event {:message, x}, state do
        IO.puts("Message value is #{x}")
        {:ok, [x|state]}
    end
end

I can start one handler and a manager in the usual way:

{:ok, mgr} = GenEvent.start_link

myServer.start_link(mgr)

GenEvent.add_handler(mgr,myHandler, [])

However, I would like to start a supervision tree where there are N handlers, each with a different id, using the same manager.

I tried:

Gen.Event.add_handler({mgr, :id1},myHandler, [])

, with no luck! In stead I get the following error:

** (Mix) Could not start application : exited in: myApp.start(:normal, [])
** (EXIT) no connection to :id1

I'm a newbie to Elixir and so am struggling with the documentation a bit. I'd be grateful if someone can show me how! Thanks.

Anthony W
  • 1,289
  • 2
  • 15
  • 28

2 Answers2

4

You can always have a more complex state in MyHandler:

defmodule MyHandler do
  use GenEvent

  def handle_event({:message, id, message}, {id, messages}) do
    IO.puts "[ID: #{inspect id}] Message value is #{inspect message}."
    {:ok, {id, [message | messages]}}
  end

  def handle_event(_, state) do
    {:ok, state}
  end
end

To filter messages by id, I would change the message structure to:

{:message, id, message}

If you don't do this, every handler will print the same message. I guess that's why you want the ID.

Then having an id, you could do something like:

{:ok, manager} = GenEvent.start_link
MyServer.start_link manager
GenEvent.add_handler manager, MyHandler, {id, []}

As you can see the new state is {id :: atom, messages :: list} instead of a simple list of messages.

Then its just a matter of sending the message:

GenServer.sync_notify manager, {:message, id, message}

Example:

Initialize the manager:

iex(1)>  {:ok, manager} = GenEvent.start_link
{:ok, #PID<0.75.0>}

Add the handler:

iex(2)> GenEvent.add_handler manager, MyHandler, {:id0, []}
:ok

Test a message with ID :id0 and prints the message:

iex(3)> GenEvent.sync_notify manager, {:message, :id0, "Hello"} 
[ID: :id0] Message value is "Hello".
:ok

Test a message with the inexistent ID :id1 and it doesn't print anything:

iex(4)> GenEvent.sync_notify manager, {:message, :id1, "Hello"}
:ok

There you go. I hope this helps :)

P.S: If your state is too complex, you can always use a map:

%{id: id, messages: []}
Alex de Sousa
  • 1,531
  • 12
  • 14
  • thanks for this. Maybe I don't understand your answer too well, but I'm trying to broadcast a single event to multiple handlers (that are all the same); a pubsub mechanism. So I'm not concerned with filtering out specific messages. To do this I need to use the same module as a event handler and pass it in as {Module, id}...explained here: https://github.com/elixir-lang/elixir/issues/3760 But it doesn't make any sense to me! – Anthony W Dec 18 '15 at 16:17
  • 1
    Ok, I think I get it. The above code, with the filtering and all, also could solve your problem. I guess you're using the `id` as a name for a channel. In that case, every time a process needs to subscribe to an event of a specific `id`, just add a new handler with that ID. The trick is to give the handler also an ID different from the other handlers: `GenEvent.add_handler manager {MyHandler, handler_id} {id, []}`. That will prevent the return `{:error, :already_present}` – Alex de Sousa Dec 18 '15 at 17:04
  • Yes. I think our messages just crossed! – Anthony W Dec 18 '15 at 17:14
4

So it turns out that to add multiple handlers to the same manager you need something along the lines of:

GenEvent.add_handler(:myManager, {myHandler, :id1}, [])

I had the argument all messed up - thanks to the wonderful @true_droid on the Elixir slack channel.

Anthony W
  • 1,289
  • 2
  • 15
  • 28