Skip to content

Instantly share code, notes, and snippets.

@bitwalker
Created July 19, 2016 23:00
Show Gist options
  • Save bitwalker/a4f73b33aea43951fe19b242d06da7b9 to your computer and use it in GitHub Desktop.
Save bitwalker/a4f73b33aea43951fe19b242d06da7b9 to your computer and use it in GitHub Desktop.
Useful config wrapper for Elixir
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
@linjunpop
Copy link

Maybe this should be a feature of Application.get_env/3, considering submit a PR to elixir-lang/elixir ?

@BennyHallett
Copy link

👍 For getting this change into elixir-lang/elixir

@edevil
Copy link

edevil commented Sep 15, 2016

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?

@st23am
Copy link

st23am commented Sep 27, 2016

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/

@whitfin
Copy link

whitfin commented Sep 29, 2016

@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.

@zweizeichen
Copy link

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?

@gmodarelli
Copy link

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

@shiroyasha
Copy link

@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.

@cdegroot
Copy link

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.

@almassapargali
Copy link

almassapargali commented Mar 1, 2017

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment