UnifiedSettings
A simple and unified way to get any setting in your code regardless of where it is defined.
Installation
Install the gem and add it to the application's Gemfile by executing:
$ bundle add unified_settings
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install unified_settings
Usage
Basic Usage
To get the value of some_setting
, simply do:
UnifiedSettings.get('some_setting')
Or to check if the key has been defined:
UnifiedSettings.defined?('some_setting')
This will search for some_setting
in the following locations in the following order:
- ENV
- Rails Credentials (if using Rails)
- Constants
When using UnifiedSettings.get('some_setting')
, the first setting that matches the provided key will be returned. As such, even if the same key is defined in ENV, Credentials and as a constant, it will return the value defined in ENV as that is what will take precedence.
If one wants to change the search order or limit what is searched, one should provide the handlers to search explicitly. For example:
UnifiedSettings.get(
'some_setting',
handlers: [
UnifiedSettings::Handlers::Credentials,
UnifiedSettings::Handlers::Env
]
)
This will first search the Credentials, then ENV, and completely ignore any values that might be defined in Settings or as a constant. If there are any other places that need to be searched, a new custom handler can be created and then provided. To do this, just create a class that inherits from UnifiedSettings::Handlers::Base
and add it to the list of handlers. For details on how to configure UnifiedSettings
to use different handlers by default, see the Configuration section below.
Note, by default the search is also done in a "case insensitive" manner. This means, that for each setting source, it will first try to match the key as provided (e.g. Some_Setting
). If nothing is found it will then attempt an uppercase version of the key (SOME_SETTING
) and a lowercase version (some_setting
). If this is not desired, one can do
UnifiedSettings.get('some_setting', case_sensitive: true)
Predefined Handlers
Built-in are 4 pre-defined handlers that look for setting keys in predefined locations
UnifiedSettings::Handlers::ConfigGem
: Look for settings via the interface provided by ConfigUnifiedSettings::Handlers::Constants
: Look for a setting defined by a constantUnifiedSettings::Handlers::Credentials
: Look for a setting in a Rails Credentials fileUnifiedSettings::Handlers::Env
: Look for a setting defined inENV
Coercing strings to objects
In many instances, it is only possible to define strings as the value of a setting. For example, when setting an ENV
var SUPER_IMPORTANT_IDS
with the value of 1,2,3,4,5
, what the user really wants is a list of numbers and not a comma-separated string. As such, UnifiedSettings
will automatically try to coerce things that look like arrays into arrays. Furthermore, it will convert things that look like floats to floats, ints to ints, booleans to booleans, etc. This way one does not have to worry about converting the values of settings to be easily used within the application. For example, if some_setting
had the value of ' string, tRue, false,1, 2.2, NiL '
, the following be returned ['string', true, false, 1, 2.2, nil]
.
There are times when coercion is not desired (e.g. for things like long passcodes that may look like arrays since they may contain commas/numbers, etc.). For situations like this, simply disable the coercion:
UnifiedSettings.get(`some_setting`, coerce: false)
Handling Missing Keys
Setting a Default Value
In many cases, there might be a default value that should be provided if a key is missing. This can be supplied as follows:
UnifiedSettings.get('some_setting', default: 'some_value')
Logging/Raising Error
By default, when there is a missing key, a message will be logged with the severity of error
. Depending on the situation one may want a different behavior. As such, different error handlers can be passed to meet those needs. The following handlers are predefined: :log_debug
, :log_info
, :log_warn
, :log_error
, :log_fatal
, :raise
. For example:
UnifiedSettings.get('some_setting', on_missing_key: :raise)
If these do not suffice, one can pass an anonymous function that takes key
as a parameter. For example:
UnifiedSettings.get(
'some_setting',
on_missing_key: ->(key) { puts "Something is wrong with #{key}" }
)
Furthermore, one can pass a list of hanlders, so that multiple things can happen. For example
UnifiedSettings.get(
'some_setting',
on_missing_key: [
:log_fatal,
->(key) { puts "Something is wrong with #{key}" },
:raise
]
)
This will run the handlers in the order they were defined.
Nested Settings
In many cases, it is advantageous to have settings nested when defining them in, for example, the Rails Credentials file. For example, if we had the following defined in the Credentials file:
aws:
client_id: SOME_ID
client_secret: SOME_SECRET
Then the corresponding setting keys would be:
aws.client_id
aws.client_secret
As you can see, for nested settings, the convention to be used is to separate the elements using a '.' .
The separator for ENV variables follows a slightly different way as there can be issues when using a .
in ENV variable names. As such the convention used in UnifiedSettings
is modeled after the Config gem. When defining a value via an environment variable, the separator is a double underscore (__
). Continuing with the above example, the ENV keys would be
AWS__CLIENT_ID
AWS__CLIENT_SECRET
This means, if you do
UnifiedSettings.get('aws.client_id')
it will look for an ENV var of the form AWS__CLIENT_ID
.
Configuration
Most of the settings that can be set at the individual call level can also be globally configured.
IMPORTANT FOR RAILS: If one is planning on using UnifiedSettings
during the initialization for other Rails gems, then this configuration MUST be done before we run application.initialize!
.
Configuring the Handlers
By default, the search order for a setting is:
- ENV
- Rails Credentials (if using Rails)
- Constants
For example, if one is using the Config gem, and would also like to search for settings there, one can add that handler.
UnifiedSettings.configure do |config|
config.handlers = [
UnifiedSettings::Handlers::Env,
UnifiedSettings::Handlers::Credentials,
UnifiedSettings::Handlers::ConfigGem,
UnifiedSettings::Handlers::Constants
]
end
If one needs to supply some extra parameters while initializing the handler, for example, if a non-default constant is used for the Config
gem, one needs only to pass a hash as follows:
UnifiedSettings.configure do |config|
config.handlers = [
UnifiedSettings::Handlers::Env,
UnifiedSettings::Handlers::Credentials,
{
handler: UnifiedSettings::Handlers::ConfigGem,
params: {
const_name: 'ConfigSettings'
}
},
UnifiedSettings::Handlers::Constants
]
end
All Other Options
Instead of going through all the various options, here is a fully worked-out example with inline comments.
UnifiedSettings.configure do |config|
config.handlers = [
UnifiedSettings::Handlers::Env,
UnifiedSettings::Handlers::Credentials,
{
handler: UnifiedSettings::Handlers::ConfigGem,
params: {
const_name: 'Settings'
}
},
UnifiedSettings::Handlers::Constants
]
# Whether or not keys should be case sensitive. For example, let's assume the
# key foo.bar.baz is defined in one of the locations the handlers are
# configured to search. If we use case_sensitive = false then any of the
# following examples will match
# UnifiedSettings.get('FOO.BAR.BAZ')
# UnifiedSettings.get('foo.bar.bar')
# UnifiedSettings.get('Foo.BaR.baZ')
# If we set case_sensitive = true, then only if the case exactly matches
# the key is a result returned.
# Default is: config.case_sensitive = false
config.case_sensitive = false
# This can be any of the following pre-defined handlers:
# :log_debug, :log_info, :log_warn, :log_error, :log_fatal, :raise
# or you can pass an anonymous function that takes `key` as a parameter. E.g.
# config.on_missing_key = ->(key) { puts "Something is wrong with #{key}" }
# If you need multiple things to happen, simply use an Array here. E.g.
# config.on_missing_key = [
# :log_fatal,
# ->(key) { puts "Something is wrong with #{key}" },
# :raise
# ]
# This will run the handlers in the order they were defined.
#
# Default is: config.on_missing_key = :log_error
config.on_missing_key = :log_error
# The value that should be returned if no key was found. This can be set
# to a particular value (e.g. `nil`, or `{}`), or can be an anonymous function
# that takes `key` as a parameter. E.g.
# config.default_value = ->(key) { key.starts_with?('a') ? 'AA' : 'ZZ'}
#
# Default is: config.default_value = nil
config.default_value = nil
# The types that should be coerced from strings to their Ruby type.
# (e.g. "true" to the boolean true or "1.2" to the float 1.2).
# Default is: config.coercions = %i[nil boolean integer float]
config.coercions = %i[nil boolean integer float]
# Whether or not to coerce strings that look like arrays to arrays.
# E.g. "a, B, true,1 " would be coerced become to ["a", "B", true, 1]
# Default is:
# config.coerce_arrays = true
# config.coerce_array_separator = ','
config.coerce_arrays = true
config.coerce_array_separator = ','
end
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/prschmid/unified_settings.
License
This gem is available as open source under the terms of the MIT License.