I am setting up a function that I want to call using strings from a database.
Once I get the data from the database, it looks like this:
q = %Question{ choices_module: "Survey", choices_function: "get_choices" }
I have a function in Survey that looks something like this:
def get_choices(question) do
apply(q.choices_module, q.choices_function, [q])
end
This does not work, and I get an error that I need to send an Atom as the module name. I tried something like:
String.to_atom(q.choices_module) # => :Survey
Which is not what it expects.
Working it out in the console, I see that it wants this:
apply(Survey, :get_choices_for_question, [q])
and this call works in the console.
I think I am fundamentally not understanding how to turn "Survey"
to Survey
.
Any ideas? Thanks in advance.
In elixir module names are prefixed with Elixir.
This means when you see ModuleName
in iex they are really Elixir.ModuleName
iex> Atom.to_string(String)
"Elixir.String"
So if you want to create a “module” from a string you need to prefix it with “Elixir.” first.
Try:
apply(String.to_existing_atom("Elixir." <> q.mod), String.to_existing_atom(q.fun), [Atom])
"Elixir.Atom"
You might want to check that the module is not prefixed with “Elixir.” already.
2 Likes
It’s better and safer to use Module.concat/1
in such a case rather than manually get into the implementation details.
["String"] |> Module.concat() |> apply(:length, ["foo"])
#⇒ 3
9 Likes
Thanks! This is exactly what I needed… With a slight twist. I am writing this function inside the Elixir.Survey module, so I was getting (module Survey is not available)
.
I changed the whole thing to __MODULE__
and everything works as expected.
BUT! I will need to do something similar across modules, so this knowledge will come in handy.
Thanks!