Elixir Application Environment
The application environment is a settings store that holds values for an application by key. Code reads a setting by key, and the value can change without changing the code.
A missing key answers nil, and a default answers in its place
Reading a missing key gives nil, unless a default is given. Storing a value makes later reads answer with it.
IO.inspect(Application.get_env(:my_app, :missing))
IO.inspect(Application.get_env(:my_app, :missing, "fallback"))
Application.put_env(:my_app, :greeting, "hello")
IO.inspect(Application.get_env(:my_app, :greeting))
IO.inspect(Application.fetch_env(:my_app, :greeting))
IO.inspect(Application.fetch_env(:my_app, :missing))A worker reads its setting when it starts
A worker starts with one greeting, then the setting changes. The old worker keeps its greeting, and a new worker picks up the new one.
defmodule EnvW do
use GenServer
def start_link(_), do: GenServer.start_link(__MODULE__, :ok)
def init(:ok) do
greeting = Application.get_env(:my_app, :greeting, "hi")
{:ok, greeting}
end
def handle_call(:get, _from, g), do: {:reply, g, g}
end
Application.put_env(:my_app, :greeting, "hello")
{:ok, p1} = EnvW.start_link(:ok)
IO.inspect(GenServer.call(p1, :get))
Application.put_env(:my_app, :greeting, "bye")
IO.inspect(GenServer.call(p1, :get))
GenServer.stop(p1)
{:ok, p2} = EnvW.start_link(:ok)
IO.inspect(GenServer.call(p2, :get))
GenServer.stop(p2)Deleting a key brings back the default
A stored limit reads back, then the key is deleted. The next read falls back to the default, and a fetch answers with an error.
Application.put_env(:my_app, :limit, 3)
IO.inspect(Application.get_env(:my_app, :limit))
Application.delete_env(:my_app, :limit)
IO.inspect(Application.get_env(:my_app, :limit, 0))
IO.inspect(Application.fetch_env(:my_app, :limit))Example
defmodule LimitW do
use GenServer
def start_link(_), do: GenServer.start_link(__MODULE__, :ok)
def init(:ok) do
limit = Application.get_env(:my_app, :limit, 10)
{:ok, limit}
end
def handle_call({:take, items}, _from, limit), do: {:reply, Enum.take(items, limit), limit}
end
Application.put_env(:my_app, :limit, 2)
{:ok, w1} = LimitW.start_link(:ok)
IO.inspect(GenServer.call(w1, {:take, ["red", "green", "blue"]}))
GenServer.stop(w1)
Application.put_env(:my_app, :limit, 1)
{:ok, w2} = LimitW.start_link(:ok)
IO.inspect(GenServer.call(w2, {:take, ["red", "green", "blue"]}))
GenServer.stop(w2)Key points
- Read: Application.get_env reads a value by application name and key.
- Write: Application.put_env stores a value under an application name and key.
- Default: a default value answers when the key is missing.
- Fetch: a fetch answers with an ok tuple for a present key and an error atom for a missing key.
- Start time: a worker reads its settings when it starts, not on every call.
In this example, the limit setting starts at two, a worker takes the first two items, the setting changes to one, a new worker starts with the new limit, and its take answers with only the first item.
Note
settings live outside the worker, so one change can shape many workers.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What does Application.get_env answer for a missing key with no default?