Can "Application.get_env(..)" be used as a module attribute along with "runtime.exs"?

Somewhat a legacy project. There’re Application.get_env(...) and Application.get_env(...)[:some_key] as the module attributes here and there. I’ve replaced releases.exs with runtime.exs.

Yet I’m confused: will Application.get_env(...) as the module attributes work properly then? Will they point to the compile time config values or the runtime ones?

Compile time. Module attributes are compile time constructs and don’t actually exist at runtime.

You could replace those module attributes by simple functions to have runtime evalution… and benefit from runtime.exs

Additionally the same already applied with config/releases.exs.

What do you mean? What “the same”, “applied” what?

config/releases.exs also ran after your code was compiled, so it also couldn‘t change any config you compiled into module attributes.

Another option is to use Application.compile_env!/3 if you want to keep the module attributes with compile time config values instead of replacing them with runtime evaluated functions.

Is there a way to use both - compile-time and runtime values - such that the runtime ones will have the preferance? If a runtime value has been provided, use it, otherwise - fallback to the one specified statically, in config/{dev,prod,qa,test,etc}.exs

The following should work, but I would not encourage it. Now your code is linked to 2 configs and in the future it’s hard to know which env returns the value.

def get_value() do
  Application.get_env(runtime_value...) || Application.compile_env!(compiletime_value...)
end

Just define the value in both config/{dev,test,prod}.exs and config/runtime.exs. Runtime will win if the key is the same. Consider:

If the key/value exists only in compile time config:

# config/dev.exs
config :my_app, :some_key, "Compile time!"

Application.get_env(:my_app, :some_key) => "Compile time!"

If you define it in both:

# config/dev.exs
config :my_app, :some_key, "Compile time!"

# config/runtime.exs
config :my_app, :some_key, "Runtime!"

and then in IEx:

Application.get_env(:my_app, :some_key) => "Runtime!"

That said, you cannot expect the module attributes to respect this, because they are generated at compile time and the runtime config does not yet exist.

I would switch to functions to save on disk and memory usage, anyways. See this from Sara Juric about how module attributes as constants have a potential gotcha: Blog Post: 10 Elixir gotchas - #16 by sasajuric

1 Like

And one step futher: if the same section contains some keys-values as compile-time ones, others as runtime ones?

# config/dev.exs
config :my_app, MyModule, 
  k1: "Compile time!"
  k2: "compile time2!"

and

# config/runtime.exs
config :my_app, MyModule, 
  k2: "runtime time2!"
  k3: "runtime time3!"

Will Application.get_env(:my_app, :MyModule)[:kN] work as intended always?

Yes.

You can think of Config values as sort of a Map.merge. The last defined value always wins (runtime.exs by default).

In your example, with both of those defined in config/dev.exs and config/runtime.exs, you will get:

iex(1)> Application.get_env(:my_app, MyModule)
[k1: "Compile time!", k2: "runtime time2!", k3: "runtime time3!"]
1 Like

What to resolve this issue

a) I have to use a module attribute – in order to embed it into other modules via a macro.
b) Yet I also want to be able to pass a variable that a said module attribute reads from config dynamically, at runtime.

?

defmodule MyMod1 do
  defmacro __using__(_) do
    quote do

      # @a1 Application.compile_env(:my_app, My.App)[:key1]
      @a1 Application.get_env(:my_app, My.App)[:key1]

      # ............

Only by replacing an attibute with a function?

For someone writing custom macros this is sure a strange question.

Yes, module attributes are always evaluated at compile-time, either use a function or an anonymous function in your attribute.