Sometimes you might need to execute code dynamically based - for instance - on the name of a module, akin to code reflection on other languages.
In Elixir one way this can be achieved is by using Erlang's :code
module. As an example, say you want to run a function in a module named MyProject.Foo
.
Find that module
The first thing we need to do is to find the module:
def find_module(name) do
:code.all_loaded()
|> Enum.map(&elem(&1, 0))
|> Enum.find(fn module ->
module_name = Atom.to_string(module)
String.starts_with?(module_name, "Elixir.#{name}") and
!String.ends_with?(module_name, "Test")
end)
end
You might notice two things there: 1. all modules in Elixir start with Elixir.
and 2. i am excluding a potential test module that might have the same name as the module we want to run the code on.
Run them codes
Now we need to be able to execute functions on our module. For that we can use the apply
function from the Kernel:
case find_module(module_name) do
nil -> {:error, "module #{module_name} not found"}
module -> apply(module, function, args)
end
A concrete example
defmodule MyProject.Foo do
def bar(message) do
IO.puts("Hello #{message}")
end
end
defmodule MyProject.Runtime do
def find_module(name) do
:code.all_loaded()
|> Enum.map(&elem(&1, 0))
|> Enum.find(fn module ->
module_name = Atom.to_string(module)
String.starts_with?(module_name, "Elixir.#{name}") and
!String.ends_with?(module_name, "Test")
end)
end
def execute_module_function(module_name, function, args \\ []) do
case find_module(module_name) do
nil -> {:error, "module #{module_name} not found"}
module -> apply(module, function, args)
end
end
end
MyProject.Runtime.execute_module_function("MyProject.Foo", :bar, ["world"])
MyProject.Runtime.execute_module_function("MyProject.NonExisting", :bar, ["world"])
Happy coding!