Let me see if I can clarify this a bit. If we have this code:
config :my_lib, MyRepo,
adapter: Sandbox # value is determined at compile time
defmodule MyRepo do
defp adapter do
# Even if next line is called during app boot, it uses a compile time value
Application.get_env(:my_lib, MyRepo)[:adapter]
end
end
The adapter isn’t actually being “compiled” into the MyRepo module. Its always being fetched from the application env. That means its possible to do something like this:
# The adapter is set from when `config.exs` was initially run
Sandbox = MyRepo.adapter()
# Update the config...
Application.put_env(:my_lib, MyRepo, [adapter: DBAdapter])
# Now the adapter has changed
DBAdapter = MyRepo.adapter()
Its possible to compile the adapter directly into the module doing something like this:
defmodule Repo do
defmacro __using__(_opts) do
quote do
@before_compile unquote(__MODULE__)
end
end
defmacro __before_compile__(env) do
a = Application.get_env(:my_lib, Repo)[:adapter]
quote do
def adapter do
unquote(a)
end
end
end
end
defmodule MyRepo do
use Repo
end
This will cause the adapter function to be hard-coded with the adapter module into MyRepo.
Hopefully that provides some clarity :).
Everything else seems spot on to me
.
In my applications and services I’ll often use this pattern:
defmodule Server do
def start_link(opts) do
GenServer.start_link(__MODULE__, state, name: opts[:name] || __MODULE__)
end
def foo(server \\ __MODULE__, args) do
GenServer.cast(server, {:foo, args})
end
end
This gives me a lot of flexibility when testing the server and provides reuse. But, I don’t tend to use this pattern in libraries because it can create name conflicts if 2 different users try to use the default name. Passing the name around can definitely be annoying, but I haven’t found a better pattern that still provides the same level of flexibility. At this point I’m used to passing the name or a pid as the first argument to my function calls.
Likewise ![]()






















