Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
@bitwalker

This comment has been minimized.

Show comment
Hide comment
@bitwalker

bitwalker Aug 19, 2016

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!

Owner

bitwalker commented Aug 19, 2016

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!

@tomhouman

This comment has been minimized.

Show comment
Hide comment
@tomhouman

tomhouman Aug 31, 2016

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?

tomhouman commented Aug 31, 2016

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?

@linjunpop

This comment has been minimized.

Show comment
Hide comment
@linjunpop

linjunpop Sep 2, 2016

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

linjunpop commented Sep 2, 2016

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

@BennyHallett

This comment has been minimized.

Show comment
Hide comment
@BennyHallett

BennyHallett Sep 6, 2016

👍 For getting this change into elixir-lang/elixir

BennyHallett commented Sep 6, 2016

👍 For getting this change into elixir-lang/elixir

@edevil

This comment has been minimized.

Show comment
Hide comment
@edevil

edevil 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?

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

This comment has been minimized.

Show comment
Hide comment
@st23am

st23am 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/

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

This comment has been minimized.

Show comment
Hide comment
@whitfin

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

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

This comment has been minimized.

Show comment
Hide comment
@zweizeichen

zweizeichen Oct 16, 2016

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?

zweizeichen commented Oct 16, 2016

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

This comment has been minimized.

Show comment
Hide comment
@gmodarelli

gmodarelli Oct 20, 2016

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

gmodarelli commented Oct 20, 2016

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

This comment has been minimized.

Show comment
Hide comment
@shiroyasha

shiroyasha Nov 5, 2016

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

shiroyasha commented Nov 5, 2016

@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

This comment has been minimized.

Show comment
Hide comment
@cdegroot

cdegroot Jan 11, 2017

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.

cdegroot commented Jan 11, 2017

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

This comment has been minimized.

Show comment
Hide comment
@almassapargali

almassapargali 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

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