# Settings

[![Build Status](](

`Settings` is a generic settings API for top-level applications, intended to
solve a set of real-world issues with other approaches to run-time application

`Settings` provides an abstraction for the intricacies of settings, while
deferring the practicalities of persistence to the client via the
`Settings.Backend` behaviour.

### Get Started

#### Installation

{:settings, "~> 0.1.0"},

#### Backend

`Settings` currently ships with just an in-memory backend, which was required in
order to test Settings.  You will need to write a module that implements the
`Settings.Backend` behavior.

#### Use

See [the `SimpleApp` example][simpleapp_readme] for very basic use.
A larger example of an umbrella project is in the works.

### Important concepts

#### `:app`

Apps are a grouping/namespacing construct for settings, most useful in the umbrella
app domain.  When querying settings, queries may be restricted by app, e.g.
`Settings.all(:myapp)`, which returns all settings for the app `:myapp`

#### `:scope`

The return value from `get/1,2` is constrained by 'scope'. First, a simple

iex> Settings.create(:http_timeout_ms, 15_000)
...> Settings.get(:http_timeout_ms) # :scope omitted, defaults to :__global
15000 # this is the :__default scope value, because :__global has not been set
...> Settings.set(:http_timeout_ms, 10_000) # :scope omitted, defaults to :__global
...> Settings.get(:http_timeout_ms)
10000 # this is the global scope value
...> Settings.set(:http_timeout_ms, 5_000, scope: :lan_requests)
...> Settings.get(:http_timeout_ms, scope: :lan_requests)
...> Settings.get(:http_timeout_ms, scope: :wifi_requests)
10000 # falls back to :__global, as the :wifi_requests scope has not been set

As the example shows, a Setting may have multiple values, each known by their
`:scope`.  `:__default` is the scope set during `Settings.create`, and can only
be changed by calling `Settings.create` again (usually when the app is
restarted). `:__global` is the scope used whenever the `:scope` opt is omitted,
and takes precedence over `:__default`.  Finally, specific scopes may be set by
using the `:scope` opt in `get/2` and `set/3`, and accept any Elixir term.

During retrieval, the value for the requested scope will be returned if it
exists, followed by the `:__global` scope if it exists, with `:__default` as the
ultimate fallback.

### `create/3`

The `create` method **must** be called each time the app is started, prior to
any other calls which access the setting. While this may seem like a stifling
requirement, it is a byproduct of a common pattern in the development process:

1. identify that a hardcoded value should be a run-time setting
2. create the setting, and assign it the value of, e.g. 5
3. test and troubleshoot, then realize that the value should have been 7

We cannot use `set` at this moment, because the value needs to be changed for
the production system, not just in the persistence layer on this developer's
machine.  If the developer now changes the value in the create method to 7, the
source code will reflect that change and can be propagated via source control.

A useful pattern is to create a module/method to be called during app startup
that contains all of the calls to `Settings.create`, which forms a convenient
place to go to see the available configuration for an app.

### API by Example

# This allows us to omit these opts from the remaining calls, and affects the
# entire node.
Settings.set_defaults(app: :my_webapp, backend: MyEctoBackend)

# gets app and backend from `set_defaults`
Settings.create(:site_url, "")

# still uses backend from `set_defaults`, but overrides the app
Settings.create(:gps_device, "ttyUSB0", app: :my_serial_app)


### Design

This library was designed using [Artifact][artifact_repo].  The raw design files are located
in `./design`, and they have been rendered [here][design_docs].