-
-
Save bitwalker/a4f73b33aea43951fe19b242d06da7b9 to your computer and use it in GitHub Desktop.
defmodule Config do | |
@moduledoc """ | |
This module handles fetching values from the config with some additional niceties | |
""" | |
@doc """ | |
Fetches a value from the config, or from the environment if {:system, "VAR"} | |
is provided. | |
An optional default value can be provided if desired. | |
## Example | |
iex> {test_var, expected_value} = System.get_env |> Enum.take(1) |> List.first | |
...> Application.put_env(:myapp, :test_var, {:system, test_var}) | |
...> ^expected_value = #{__MODULE__}.get(:myapp, :test_var) | |
...> :ok | |
:ok | |
iex> Application.put_env(:myapp, :test_var2, 1) | |
...> 1 = #{__MODULE__}.get(:myapp, :test_var2) | |
1 | |
iex> :default = #{__MODULE__}.get(:myapp, :missing_var, :default) | |
:default | |
""" | |
@spec get(atom, atom, term | nil) :: term | |
def get(app, key, default \\ nil) when is_atom(app) and is_atom(key) do | |
case Application.get_env(app, key) do | |
{:system, env_var} -> | |
case System.get_env(env_var) do | |
nil -> default | |
val -> val | |
end | |
{:system, env_var, preconfigured_default} -> | |
case System.get_env(env_var) do | |
nil -> preconfigured_default | |
val -> val | |
end | |
nil -> | |
default | |
val -> | |
val | |
end | |
end | |
@doc """ | |
Same as get/3, but returns the result as an integer. | |
If the value cannot be converted to an integer, the | |
default is returned instead. | |
""" | |
@spec get_integer(atom(), atom(), integer()) :: integer | |
def get_integer(app, key, default \\ nil) do | |
case get(app, key, nil) do | |
nil -> default | |
n when is_integer(n) -> n | |
n -> | |
case Integer.parse(n) do | |
{i, _} -> i | |
:error -> default | |
end | |
end | |
end | |
end |
👍 For getting this change into elixir-lang/elixir
Does System.get_env/1 imply a getenv() system call? If so, isn't that a performance problem if you're doing it in a function that's called frequently?
When I try and use this in my umbrella project and import into my config.exs * (Mix.Config.LoadError) could not load config config/config.exs ** (UndefinedFunctionError) function MobileApi.ConfigurationUtils.get/3 is undefined (module MobileApi.ConfigurationUtils is not available)
How should I use this? Is there some strange umbrella thing going on here? I put the file in apps/mobile_api/lib/mobile_api/
@st23am You don't call it from your config.exs file, you call it at runtime. Your config doesn't change, this just allows overriding at runtime.
FWIW I also believe this should be in core, seems a good thing to make standard.
Seems reasonable to have this in core, configurations like this are part of many production deployments and a continuous source of confusion due to custom hand-rolled solutions. Having a standardised solution would simplify these things a lot (especially if it gains wider adoption in libs). @bitwalker what do you think about opening an issue/PR in elixir-lang/elixir?
Thank you very much @bitwalker. I've added a third function get!/2
to mimic the Application.fetch_env!/2
behavior.
https://gist.github.com/gmodarelli/946235798a1678c0ac31a3a8fcabdf16
@bitwalker awesome gist! 👏
I noticed that we share a similar boilerplate code on a lot of our projects.
To avoid copy/paste errors, I've tried to to wrap it into a installable dependancy.
I hope someone else will find it useful as well: https://github.com/renderedtext/ex-config.
I also encourage @bitwalker to open a PR for elixirlang. I would really like it if this would be the default in elixir.
FWIW - I'd prefer to see this land in the equivalent Erlang code (http://erlang.org/doc/apps/kernel/application.html#get_env-1) which should enable 12FA-style config of any app, whether it's running Elixir or Erlang modules.
I often group configs with common domain, so I also had to get nested config values, use following code:
def get_config(app, path, default \\ nil) do
[root | subkeys] = List.wrap(path)
root_val = Application.get_env(app, root)
val = Enum.reduce(subkeys, root_val, fn (subkey, val) ->
case val do
nil -> nil
keyword when is_list(keyword) -> Keyword.get(keyword, subkey)
map when is_map(map) -> Map.get(map, subkey)
end
end)
get_config_val(val || default)
end
def get_config_val({:system, varname}), do: System.get_env(varname)
def get_config_val(val), do: val
So, to get :domain
in this config:
config :my_app, MyApp.MailingService.Client,
api_key: {:system, "MAILGUN_API_KEY"},
domain: {:system, "MAILGUN_DOMAIN"}
I'd do following inside MyApp.MailingService.Client
:
def process_url(path) do
"https://api.mailgun.net/v3/" <> get_config(:my_app, [__MODULE__, :domain]) <> path
end
Maybe this should be a feature of
Application.get_env/3
, considering submit a PR to elixir-lang/elixir ?