Track Modules that `use` a specific module

I’m trying to keep track of all modules that use another module at compile-time and get a list of all these module at runtime:

defmodule BaseModule do
  defmacro __using__(_) do
    quote do
      # common behaviour
    end
  end
end


defmodule A do
  use BaseModule
end

defmodule B do
  use BaseModule
end

defmodule C do
  use BaseModule
end

And get them at runtime by calling something like this:

BaseModule.children()
#=> [A, B, C]

I have been trying to find a way to accomplish this but still have absolutely no idea on how to do it. Going through this thread on the elixir-lang mailing list, @josevalim recommends using Protocols to do this. But after struggling this for about an hour, I can’t get it to work with Protocols either.

I’ve also been looking in to the Registry module to see if I can accomplish this using that, but it looks like it’s designed to work with processes mainly.

Any help would be highly appreciated. Thanks in advance!

If you write your common behaviour as a protocol, and implement this for your datatypes, then you can use Protocol.extract_impls/2 to obtain the list of struct module names the protocol has been implemented for.

if you build something manually, you can run arbitrary code inside the quote-block that is returned by __using,__/1. You might for instance append the module name to a list inside the application environment settings using Application.get_env and Application. put_env.

But what is the reason you desire this functionality? children() makes it seem like you are trying to mimic something object-oriented. Maybe there is another solution to your problem.

I have a Worker behaviour that other modules can use. When the application supervisor starts I want to spawn a GenServer for each defined worker. Of course, I can (and was) manually specifying GenServers for each worker in the supervision tree, but I am trying to make it more generic and dynamically start GenServers for all workers that use the Worker module using the simple_one_for_one strategy.

Of course, I can (and was) manually specifying GenServers for each worker in the supervision tree

This is how it should be done.

The whole point of a supervision tree is to have visibility into your application. You know the order children are started, the order they are shutdown and their relationship in case of failures. Why are you going to hide it all behind some generic code? Keep it simple.

1 Like