Settings in Rails.
It's heavily based on config gem.
I made my own because I don't like the idea of having a ghost class globally accessible that I can't modify (What if I want to add some convenient methods on Settings?).
Put this in your Gemfile :
git_source(:github){ |repo_name| "https://github.com/#{repo_name}.git" }
# The `environment` / `platform` / env-var / schema features documented below
# live on `master`; pin an explicit `ref:`/`tag:` once a release ships them.
gem 'active_settings', github: 'jbox-web/active_settings', branch: 'master'then run bundle install.
Instead of defining a Settings constant for you, that task is left to you. Simply create a class in your application
that looks like:
class Settings < ActiveSettings::Base
source Rails.root.join('config', 'settings.yml')
environment Rails.env
endName it Settings, name it Config, name it whatever you want. Add as many or as few as you like. A good place to put
this file in a Rails app is config/settings.rb
Notice above we specified an absolute path to our settings file called settings.yml. This is just a typical YAML
file, evaluated through ERB before being parsed:
# config/settings.yml
cool:
saweet: nested settings
neat_setting: 24
awesome_setting: <%= "Did you know 5 + 5 = #{5 + 5}?" %>Keys are both accessible with a string or a symbol.
Security: the file content is run through ERB, so it can execute arbitrary Ruby. Only point
sourceat files you control — never at user-supplied input. An empty file loads as an empty config; a file that is not a YAML mapping raisesActiveSettings::Error::InvalidSettingsFileError.
The optional environment corresponds to a separate file named settings.<environment>.yml, sitting next to
your source file. When it exists it is deep-merged on top of settings.yml (its values win); when it is absent it
is simply ignored.
# config/settings.yml
neat_setting: 24
# config/settings.development.yml (loaded when environment is "development")
neat_setting: 800With environment Rails.env, booting in development yields Settings.neat_setting == 800, everything else falling
back to settings.yml.
You can also declare a platform (e.g. compose, swarm, kube, docker, …) to layer platform-specific files
stored under a settings_files/<platform>/ directory next to your source file:
class Settings < ActiveSettings::Base
source Rails.root.join('config', 'settings.yml')
environment Rails.env
platform ENV.fetch('PLATFORM', nil)
endFiles are deep-merged in this order of precedence (later wins, missing files ignored):
settings.yml(thesource)settings.<environment>.ymlsettings_files/<platform>/default.ymlsettings_files/<platform>/<environment>.yml
Finally, if enabled, environment variables are merged on top (see below).
You can use different methods to access to values :
- by using method chains :
>> Rails.env
=> "development"
>> Settings.cool
=> "#<ActiveSettings::Config ... >"
>> Settings.cool.saweet
=> "nested settings"
>> Settings.neat_setting
=> 800
>> Settings.awesome_setting
=> "Did you know 5 + 5 = 10?"- by using
fetchmethod :
>> Settings.cool.fetch(:saweet)
=> "nested settings"
>> Settings.cool.fetch('saweet')
=> "nested settings"You can provide default value :
>> Settings.cool.fetch(:foo, 'bar')
=> "bar"
>> Settings.cool.fetch(:foo) { 'bar' }
=> "bar"- by using
[]accessor :
>> Settings[:cool][:saweet]
=> "nested settings"
>> Settings['cool']['saweet']
=> "nested settings"- by using
digmethod :
>> Settings.dig(:cool, :saweet)
=> "nested settings"
>> Settings.dig('cool', 'saweet')
=> "nested settings"- by using
key?method :
>> Settings.cool.key?(:saweet)
=> "true"
>> Settings.cool.key?('saweet')
=> "true"You can use these settings anywhere, for example in a model:
class Post < ActiveRecord::Base
self.per_page = Settings.pagination.posts_per_page
endNote that key? and fetch test key existence, not the truthiness of the value: a key whose value is false
or nil still exists, so Settings.fetch(:some_flag, true) returns the stored false (not the default).
When ActiveSettings.use_env is enabled, environment variables prefixed with ActiveSettings.env_prefix are merged
on top of the file-based configuration. Nested keys are expressed with ActiveSettings.env_separator:
ActiveSettings.use_env = true # disabled by defaultSETTINGS.NEAT_SETTING=42
SETTINGS.DEEP.NESTED.WARN_THRESHOLD=50Available options (module-level, shared by every subclass — set them once at boot):
| Option | Default | Description |
|---|---|---|
use_env |
false |
Enable merging of environment variables. |
env_prefix |
'SETTINGS' |
Prefix that marks a variable as a setting. |
env_separator |
'.' |
Separator between nested keys. |
env_converter |
:downcase |
Key case conversion (:downcase or nil). |
env_parse_values |
true |
Auto-cast true/false and base-10 numbers. |
fail_on_missing |
false |
Raise KeyError when reading an unknown key via method access. |
knockout_prefix |
nil |
deep_merge knockout prefix. |
merge_nil_values |
false |
deep_merge option. |
overwrite_arrays |
true |
Replace arrays on merge instead of concatenating them. |
keep_array_duplicates |
true |
deep_merge option. |
Value casting is strict base-10: "010" becomes 10 (not octal 8), while hex/underscored strings such as "0x1a"
are kept as strings. A variable used both as a scalar and as a nested prefix (SETTINGS.FOO and SETTINGS.FOO.BAR)
raises ActiveSettings::Error::EnvKeyConflictError.
You can attach a dry-schema schema to your class and call validate!
explicitly (it is a no-op when no schema is defined):
class Settings < ActiveSettings::Base
source Rails.root.join('config', 'settings.yml')
environment Rails.env
schema do
required(:neat_setting).filled(:integer)
required(:cool).schema do
required(:saweet).filled(:string)
end
end
end
Settings.instance.validate! # raises ActiveSettings::Validation::Error on failureA setting value may be a Proc; it is evaluated lazily each time the configuration is converted to a hash
(to_hash/to_json). Merging preserves stored Procs without evaluating them:
class Settings < ActiveSettings::Base
source Rails.root.join('config', 'settings.yml')
def after_initialize!
super
merge!(computed: -> { Time.now.to_i })
end
endSettings.instance.freeze deep-freezes the whole configuration, including nested ActiveSettings::Config
objects and nested arrays.
By default nested mappings are wrapped into ActiveSettings::Config objects (accessible by method). To store a
plain Hash instead (accessible only by []/dig), wrap it with a type: hash marker:
raw:
type: hash
contents:
any: { free: form, hash: here }