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 |
This comment has been minimized.
This comment has been minimized.
I'd like to use this in order to support configuring a dynamic host at runtime for my phoenix app, which is built using distillery. For example, I'd like to be able to do this in my config/prod.exs: config :my_app, MyApp.Endpoint,
http: [port: {:system, "PORT"}],
url: [host: {:system, "MYAPP_HOST"}] It looks like your module is exactly what I need, but I'm not sure how to pull it into my project. Can I use the syntax as above, or do I need to use Config.get explicitly? Is this even going to work inside a mix config file? |
This comment has been minimized.
This comment has been minimized.
Maybe this should be a feature of |
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
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? |
This comment has been minimized.
This comment has been minimized.
When I try and use this in my umbrella project and import into my config.exs How should I use this? Is there some strange umbrella thing going on here? I put the file in |
This comment has been minimized.
This comment has been minimized.
@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. |
This comment has been minimized.
This comment has been minimized.
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? |
This comment has been minimized.
This comment has been minimized.
Thank you very much @bitwalker. I've added a third function https://gist.github.com/gmodarelli/946235798a1678c0ac31a3a8fcabdf16 |
This comment has been minimized.
This comment has been minimized.
@bitwalker awesome gist! I noticed that we share a similar boilerplate code on a lot of our projects. I also encourage @bitwalker to open a PR for elixirlang. I would really like it if this would be the default in elixir. |
This comment has been minimized.
This comment has been minimized.
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. |
This comment has been minimized.
This comment has been minimized.
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 config :my_app, MyApp.MailingService.Client,
api_key: {:system, "MAILGUN_API_KEY"},
domain: {:system, "MAILGUN_DOMAIN"} I'd do following inside def process_url(path) do
"https://api.mailgun.net/v3/" <> get_config(:my_app, [__MODULE__, :domain]) <> path
end |
This comment has been minimized.
In case anyone is curious about licensing of this code, it shares the same license as pretty much all of my open source projects, the MIT license. Feel free to use as you see fit!