Adding release checksum.
Signing v4.2.0
Added tag v4.2.0 for changeset 240b42b99796
home : https://configur.ability.guide/
code : https://hg.sr.ht/~ged/Configurability
docs : http://deveiate.org/code/configurability
github : https://github.com/ged/configurability
Configurability is a unified, non-intrusive, assume-nothing configuration system for Ruby. It lets you keep the configuration for multiple objects in a single config file, load the file when it's convenient for you, and distribute the configuration when you're ready, sending it everywhere it needs to go with a single action.
gem install configurability
# user.rb
require 'configurability'
class User
extend Configurability
configurability( :users ) do
setting :min_password_length, default: 6
end
end
# config.yml
users:
min_password_length: 12
In pry:
[1] pry(main)> require 'user'
=> true
[2] pry(main)> User.min_password_length
=> 6
[3] pry(main)> config = Configurability::Config.load( "config.yml" )
=> #<Configurability::Config:0x7fd99a0f635816 loaded from config.yml ...>
[4] pry(main)> config.install
=> [Loggability, User]
[5] pry(main)> User.min_password_length
=> 12
To add Configurability to your module or class, just extend
it and declare
some settings:
require 'configurability'
class Database
extend Configurability
configurability( :db ) do
setting :url, default: 'sqlite:/'
setting :user
setting :password
setting :port do |value|
Integer( value ) if value
end
end
end
This sets up the class to use the db
config key, and adds attributes for the
three subkey settings under it (with getters and setters) to the class. It also
adds a configure
class method that will set whichever of the settings are
passed to it, defaulting the url
to the provided value if it's not given.
The setting
can include a block which is given the config value before it's
set; from this block you can do validity-checking, cast it to something other
than a String, etc. The new value will be set to the return value of the block.
Note that this will be called with the default for the setting when it's
declared, so the block should be able to handle the default (even if it's
nil
).
If your config file (e.g., config.yml
) looks like this:
--
db:
url: 'postgres:/acme'
user: tim
password: "pXVvVY,YjWNRRi[yPWx4"
You can configure the Database
class (and all other objects extended with
Configurability) with it like so:
require 'configurability/config'
config = Configurability::Config.load( 'config.yml' )
Configurability.configure_objects( config )
After this happens you can access the configuration values like this:
Database.url
# => "postgres:/acme"
Database.user
# => "tim"
Database.password
# => "pXVvVY,YjWNRRi[yPWx4"
You can add helper methods inside the configurability
block to eliminate
repeated code in your setting blocks:
configurability( :server ) do
def self::make_path( value )
return nil unless value
pn = Pathname( value )
raise "Can't read from %s!" % [ pn ] unless pn.readable?
return pn
end
setting :template_path do |value|
make_path( value )
end
setting :plugin_path do |*values|
make_path( value )
end
end
If a setting is a boolean, you can also have a predicate method created for it alongside its getter and setter:
class Mailer
extend Configurability
configurability( :db ) do
setting :use_whitelist, default: false, predicate: true
end
end
Mailer.use_whitelist?
# => false
Mailer.use_whitelist = true
Mailer.use_whitelist?
# => true
Configurability is implemented using Module#extend
, so there's very little
magic going on. Every object (including Class and Module objects) that is
extended with Configurability is registered in an Array of objects that will be
configured when the config is loaded.
You extend a Class, of course, as in the above example:
class MyClass
extend Configurabtility
end
But you can also add it to individual instances of objects to configure them separately:
user = User.new
user.extend( Configurability )
When you call:
Configurability.configure_objects( config )
the specified config
will be spliced up and sent to all the objects that have
been registered. Configurability
expects the configuration to be broken
up into a number of sections, each of which is accessible via either a method
with the section name or the index operator (#[]
) that takes the section
name as a Symbol
or a String
:
config.section_name
config[:section_name]
config['section_name']
The section name is based on an object's config key, which is the argument
you specify when declaring your settings. If you don't provide one, it defaults
to the name of the object that is being extended with all non-word characters
converted into underscores (_
). It will also have any leading Ruby-style
namespaces stripped, e.g.,
MyClass -> :myclass
Acme::User -> :user
"J. Random Hacker" -> :j_random_hacker
If the object responds to the #name
method, then the return value of that
method is used to derive the name. If it doesn't have a #name
method, the
name of its Class
will be used instead. If its class is anonymous, then
the object's config key will be :anonymous
.
When the configuration is loaded, any attribute writers that correspond with
keys of the config will be called with the configured values, and an instance
variable called @config
is set to the appropriate section of the config.
As you add more objects to your configuration, it may be useful to group related sections together. You can specify that your object's configuration is part of a group by specifying a config key with either a dot if it's a String or a double underscore if it's a Symbol. E.g.,
# Read from the `db` subsection of `myapp`
configurability( 'myapp.db' )
# Same thing, but with a symbol:
configurability( :myapp__db )
The default behavior above is just provided as a reasonable default; you may want to customize one or two things about how configuration is handled in your objects.
If you want to customize the config key without calling configurability
you
can do that by declaring it with the config_key
method:
class OutputFormatter
extend Configurability
config_key :format
end
or by overriding the #config_key
method youtself and returning the desired
value as a Symbol:
class User
extend Configurability
def self::config_key
return :employees
end
end
You can also change what happens when an object is configured by implementing
a #configure
method that takes the config section as an argument:
class WebServer
extend Configurability
config_key :webserver
def self::configure( config )
@default_bind_addr = config[:host]
@default_port = config[:port]
end
end
If you still want the @config
variable to be set, just super
from your
implementation; don't if you don't want it to be set.
Configurability also includes Configurability::Config
, a fairly simple
configuration object class that can be used to load a YAML configuration file,
and then present both a Hash-like and a Struct-like interface for reading
configuration sections and values; it's meant to be used in tandem with
Configurability, but it's also useful on its own.
Here's a quick example to demonstrate some of its features. Suppose you have a config file that looks like this:
---
database:
development:
adapter: sqlite3
database: db/dev.db
pool: 5
timeout: 5000
testing:
adapter: sqlite3
database: db/testing.db
pool: 2
timeout: 5000
production:
adapter: postgres
database: fixedassets
pool: 25
timeout: 50
ldap:
uri: ldap://ldap.acme.com/dc=acme,dc=com
bind_dn: cn=web,dc=acme,dc=com
bind_pass: Mut@ge.Mix@ge
branding:
header: "#333"
title: "#dedede"
anchor: "#9fc8d4"
You can load this config like so:
require 'configurability/config'
config = Configurability::Config.load( 'examples/config.yml' )
# => #<Configurability::Config:0x1018a7c7016 loaded from
examples/config.yml; 3 sections: database, ldap, branding>
And then access it using struct-like methods:
config.database
# => #<Configurability::Config::Struct:101806fb816
{:development=>{:adapter=>"sqlite3", :database=>"db/dev.db", :pool=>5,
:timeout=>5000}, :testing=>{:adapter=>"sqlite3",
:database=>"db/testing.db", :pool=>2, :timeout=>5000},
:production=>{:adapter=>"postgres", :database=>"fixedassets",
:pool=>25, :timeout=>50}}>
config.database.development.adapter
# => "sqlite3"
config.ldap.uri
# => "ldap://ldap.acme.com/dc=acme,dc=com"
config.branding.title
# => "#dedede"
or using a Hash-like interface using either Symbol
s, String
s, or a mix of
both:
config[:branding][:title]
# => "#dedede"
config['branding']['header']
# => "#333"
config['branding'][:anchor]
# => "#9fc8d4"
You can install it (i.e., configure your objects) via the Configurability interface:
config.install
If you change the values in the config object, they won't propagate
automatically; you'll need to call #install
on it again to send the changes to
the objects being configured.
You can check to see if the file the config was loaded from has changed since you loaded it:
config.changed?
# => false
# Simulate changing the file by manually changing its mtime
File.utime( Time.now, Time.now, config.path )
config.changed?
# => true
If it has changed (or even if it hasn't), you can reload it, which automatically re-installs it via the Configurability interface if it has:
config.reload
You can make modifications via the same Struct- or Hash-like interfaces and write the modified config back out to the same file:
config.database.testing.adapter = 'mysql'
config[:database]['testing'].database = 't_fixedassets'
then dump it to a YAML string:
config.dump
# => "--- \ndatabase: \n development: \n adapter: sqlite3\n
database: db/dev.db\n pool: 5\n timeout: 5000\n testing: \n
adapter: mysql\n database: t_fixedassets\n pool: 2\n timeout:
5000\n production: \n adapter: postgres\n database:
fixedassets\n pool: 25\n timeout: 50\nldap: \n uri:
ldap://ldap.acme.com/dc=acme,dc=com\n bind_dn:
cn=web,dc=acme,dc=com\n bind_pass: Mut@ge.Mix@ge\nbranding: \n
header: \"#333\"\n title: \"#dedede\"\n anchor: \"#9fc8d4\"\n"
or write it back to the file it was loaded from:
config.write
Note that this is just using YAML.dump
, so any comments, ordering, or other
nice formatting you have in your config file will be clobbered if you rewrite
it.
It's a good idea to provide a set of reasonable defaults for any configured
object. The defaults for all settings are added to extended Classes and Modules
as a constant named CONFIG_DEFAULTS
. You can, of course set this constant
yourself as well.
Configurability provides a defaults
method that will return the hash of
settings and their default values from the CONFIG_DEFAULTS
constant. You can
also override the defaults
method yourself (and super to the original) if you
wish to do something different.
There are also a couple of useful functions built on top of this method:
gather_defaults
: You can fetch a Hash of the default config values of all objects that have
been extended with Configurability by calling
Configurabilty.gather_defaults
. You can also pass an object that responds
to #merge!
to the method to merge the defaults into an existing
config.
default_config
: This will return a Configurability::Config object made from the results of
gather_defaults
. This makes it easy to write a config file that contains the default
configuration: Configurability.default_config.write( "defaults.yml" )
You can submit bug reports, suggestions, clone it with Mercurial, and read more about future plans at the project page. If you prefer Git, there is also a Github mirror.
After checking out the source, run:
$ rake newb
This task will install any missing dependencies, run the tests/specs, and generate the API documentation.
Copyright (c) 2010-2020 Michael Granger and Mahlon E. Smith All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the author/s, nor the names of the project's contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.