Module: Configurability
- Extended by:
- Configurability, Loggability
- Included in:
- Configurability
- Defined in:
- lib/configurability.rb
Overview
A unified, unintrusive, assume-nothing configuration system for Ruby
Defined Under Namespace
Modules: DeferredConfig Classes: Config, SettingInstaller
Constant Summary collapse
- VERSION =
Library version constant
'4.0.0'- REVISION =
Version-control revision constant
%q$Revision$
Instance Attribute Summary collapse
-
#after_configure_hooks ⇒ Object
readonly
Returns the value of attribute after_configure_hooks.
-
#configurable_objects ⇒ Object
The objects that have had Configurability added to them.
-
#loaded_config ⇒ Object
the loaded configuration (after ::configure_objects has been called at least once).
Class Method Summary collapse
-
.after_configure(&block) ⇒ Object
Register a callback to be run after the config is loaded.
-
.after_configure_hooks_run=(new_value) ⇒ Object
Set the flag that indicates that the after-configure hooks have run at least once.
-
.after_configure_hooks_run? ⇒ Boolean
Returns
trueif the after-configuration hooks have run at least once. -
.call_after_configure_hooks ⇒ Object
Call the post-configuration callbacks.
-
.configure_objects(config) ⇒ Object
Configure objects that have had Configurability added to them with the sections of the specified
configthat correspond to theirconfig_key. -
.default_config ⇒ Object
Gather the default configuration in a Configurability::Config object and return it.
-
.expand_config_hash(key, hash) ⇒ Object
Nest the specified
hashinside subhashes for each subsection of the givenkeyand return the result. -
.extend_object(object) ⇒ Object
Add configurability to the given
object. -
.find_config_section(config, key) ⇒ Object
Find the section of the specified
configobject that corresponds to the givenkey. -
.gather_defaults(collection = {}) ⇒ Object
Gather defaults from objects with Configurability in the given
collectionobject. -
.get_config_subsection(config, key) ⇒ Object
Return the subsection of the specified
configthat corresponds tokey, trying both struct-like and hash-like interfaces. -
.included(mod) ⇒ Object
Mixin hook: extend including classes instead.
-
.install_config(config, object) ⇒ Object
Install the appropriate section of the
configinto the givenobject. -
.make_key_from_object(object) ⇒ Object
Try to generate a config key from the given object.
-
.normalize_config_key(key) ⇒ Object
Return the specified
keynormalized into a valid Symbol config key. -
.reset ⇒ Object
If a configuration has been loaded (via #configure_objects), clear it.
-
.version_string(include_buildnum = false) ⇒ Object
Get the library version.
Instance Method Summary collapse
-
#config_key(sym = nil) ⇒ Object
Get (and optionally set) the
config_key(a Symbol). -
#config_key=(sym) ⇒ Object
Set the config key of the object.
-
#configurability(config_key = nil, &block) ⇒ Object
Declare configuration settings and defaults.
-
#configure(config = nil) ⇒ Object
Default configuration method.
-
#default_config ⇒ Object
Return a Configurability::Config object that contains the configuration defaults for the receiver.
-
#defaults(fallback = nil) ⇒ Object
The default implementation of the method called by ::gather_defaults when gathering configuration defaults.
Instance Attribute Details
#after_configure_hooks ⇒ Object (readonly)
Returns the value of attribute after_configure_hooks.
42 43 44 |
# File 'lib/configurability.rb', line 42 def after_configure_hooks @after_configure_hooks end |
#configurable_objects ⇒ Object
The objects that have had Configurability added to them
the Array of objects that have had Configurability added to them
31 32 33 |
# File 'lib/configurability.rb', line 31 def configurable_objects @configurable_objects end |
#loaded_config ⇒ Object
the loaded configuration (after ::configure_objects has been called at least once)
36 37 38 |
# File 'lib/configurability.rb', line 36 def loaded_config @loaded_config end |
Class Method Details
.after_configure(&block) ⇒ Object
Register a callback to be run after the config is loaded.
61 62 63 64 65 66 67 68 |
# File 'lib/configurability.rb', line 61 def self::after_configure( &block ) raise LocalJumpError, "no block given" unless block self.after_configure_hooks << block # Call the block immediately if the hooks have already been called or are in # the process of being called. block.call if self.after_configure_hooks_run? end |
.after_configure_hooks_run=(new_value) ⇒ Object
Set the flag that indicates that the after-configure hooks have run at least once.
55 56 57 |
# File 'lib/configurability.rb', line 55 def self::after_configure_hooks_run=( new_value ) @after_configure_hooks_run = new_value ? true : false end |
.after_configure_hooks_run? ⇒ Boolean
Returns true if the after-configuration hooks have run at least once.
48 49 50 |
# File 'lib/configurability.rb', line 48 def self::after_configure_hooks_run? return @after_configure_hooks_run ? true : false end |
.call_after_configure_hooks ⇒ Object
Call the post-configuration callbacks.
73 74 75 76 77 78 79 80 81 |
# File 'lib/configurability.rb', line 73 def self::call_after_configure_hooks self.log.debug " calling %d post-config hooks" % [ self.after_configure_hooks.length ] @after_configure_hooks_run = true self.after_configure_hooks.to_a.each do |hook| # self.log.debug " %s line %s..." % hook.source_location hook.call end end |
.configure_objects(config) ⇒ Object
Configure objects that have had Configurability added to them with the sections of the specified config that correspond to their config_key. If the config doesn’t #respond_to the object’s config_key, the object’s #configure method is called with nil instead.
135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/configurability.rb', line 135 def self::configure_objects( config ) self.log.debug "Splitting up config %p between %d objects with configurability." % [ config, self.configurable_objects.length ] self.reset self.loaded_config = config self.configurable_objects.each do |obj| self.install_config( config, obj ) end self.call_after_configure_hooks end |
.default_config ⇒ Object
Gather the default configuration in a Configurability::Config object and return it.
244 245 246 |
# File 'lib/configurability.rb', line 244 def self::default_config return self.gather_defaults( Configurability::Config.new ) end |
.expand_config_hash(key, hash) ⇒ Object
Nest the specified hash inside subhashes for each subsection of the given key and return the result.
204 205 206 207 208 |
# File 'lib/configurability.rb', line 204 def self::( key, hash ) return key.to_s.split( '__' ).reverse.inject( hash ) do |inner_hash, subkey| { subkey.to_sym => inner_hash } end end |
.extend_object(object) ⇒ Object
Add configurability to the given object.
94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/configurability.rb', line 94 def self::extend_object( object ) self.log.debug "Adding Configurability to %p" % [ object ] super self.configurable_objects << object # If the config has already been propagated, add deferred configuration to the extending # object in case it overrides #configure later. if (( config = self.loaded_config )) self.install_config( config, object ) object.extend( Configurability::DeferredConfig ) end end |
.find_config_section(config, key) ⇒ Object
Find the section of the specified config object that corresponds to the given key.
172 173 174 175 176 177 |
# File 'lib/configurability.rb', line 172 def self::find_config_section( config, key ) return key.to_s.split( '__' ).inject( config ) do |section, subkey| next nil if section.nil? self.get_config_subsection( section, subkey.to_sym ) end end |
.gather_defaults(collection = {}) ⇒ Object
Gather defaults from objects with Configurability in the given collection object. Objects that wish to add a section to the defaults should implement a #defaults method in the same scope as #configure that returns the Hash of default, or set one of the constants in the default implementation of #defaults. The hash for each object will be merged into the collection via #merge!.
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/configurability.rb', line 217 def self::gather_defaults( collection={} ) mergefunc = Configurability::Config.method( :merge_complex_hashes ) self.configurable_objects.each do |obj| next unless obj.respond_to?( :defaults ) if defaults_hash = obj.defaults nested_hash = self.( obj.config_key, defaults_hash ) Configurability.log.debug "Defaults for %p (%p): %p" % [ obj, obj.config_key, nested_hash ] collection.merge!( nested_hash, &mergefunc ) else Configurability.log.warn "No defaults for %p; skipping" % [ obj ] end end return collection end |
.get_config_subsection(config, key) ⇒ Object
Return the subsection of the specified config that corresponds to key, trying both struct-like and hash-like interfaces.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/configurability.rb', line 182 def self::get_config_subsection( config, key ) if config.respond_to?( key ) self.log.debug " config has a #%s method; using that" % [ key ] return config.send( key ) elsif config.respond_to?( :[] ) && config.respond_to?( :key? ) self.log.debug " config has a hash-ish interface..." if config.key?( key.to_sym ) || config.key?( key.to_s ) self.log.debug " and has a %s member; using that" % [ key ] return config[ key.to_sym ] || config[ key.to_s ] else self.log.debug " but no `%s` member." % [ key ] return nil end else self.log.debug " no %p section in %p; configuring with nil" % [ key, config ] return nil end end |
.included(mod) ⇒ Object
Mixin hook: extend including classes instead
109 110 111 |
# File 'lib/configurability.rb', line 109 def self::included( mod ) mod.extend( self ) end |
.install_config(config, object) ⇒ Object
Install the appropriate section of the config into the given object.
158 159 160 161 162 163 164 165 166 167 |
# File 'lib/configurability.rb', line 158 def self::install_config( config, object ) self.log.debug "Configuring %p with the %s section of the config." % [ object, object.config_key ] section = self.find_config_section( config, object.config_key ) configure_method = object.method( :configure ) self.log.debug " calling %p" % [ configure_method ] configure_method.call( section ) end |
.make_key_from_object(object) ⇒ Object
Try to generate a config key from the given object. If it responds_to #name, the result will be stringified and stripped of non-word characters. If the object itself doesn’t have a name, the name of its class will be used instead.
117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/configurability.rb', line 117 def self::make_key_from_object( object ) if object.respond_to?( :name ) name = object.name name = 'anonymous' if name.nil? || name.empty? return name.sub( /.*::/, '' ).gsub( /\W+/, '_' ).downcase.to_sym elsif object.class.name && !object.class.name.empty? return object.class.name.sub( /.*::/, '' ).gsub( /\W+/, '_' ).downcase.to_sym else return :anonymous end end |
.normalize_config_key(key) ⇒ Object
Return the specified key normalized into a valid Symbol config key.
238 239 240 |
# File 'lib/configurability.rb', line 238 def self::normalize_config_key( key ) return key.to_s.gsub( /\./, '__' ).to_sym end |
.reset ⇒ Object
If a configuration has been loaded (via #configure_objects), clear it.
151 152 153 154 |
# File 'lib/configurability.rb', line 151 def self::reset self.loaded_config = nil self.after_configure_hooks_run = false end |
.version_string(include_buildnum = false) ⇒ Object
Get the library version. If include_buildnum is true, the version string will include the VCS rev ID.
86 87 88 89 90 |
# File 'lib/configurability.rb', line 86 def self::version_string( include_buildnum=false ) vstring = "%s %s" % [ self.name, VERSION ] vstring << " (build %s)" % [ REVISION[/: ([[:xdigit:]]+)/, 1] || '0' ] if include_buildnum return vstring end |
Instance Method Details
#config_key(sym = nil) ⇒ Object
Get (and optionally set) the config_key (a Symbol).
258 259 260 261 262 |
# File 'lib/configurability.rb', line 258 def config_key( sym=nil ) self.config_key = sym unless sym.nil? @config_key ||= Configurability.make_key_from_object( self ) @config_key end |
#config_key=(sym) ⇒ Object
Set the config key of the object.
266 267 268 269 |
# File 'lib/configurability.rb', line 266 def config_key=( sym ) Configurability.configurable_objects |= [ self ] @config_key = Configurability.normalize_config_key( sym ) end |
#configurability(config_key = nil, &block) ⇒ Object
Declare configuration settings and defaults. In the provided block, you can create a configuration setting using the following syntax:
configurability( :my_config_key ) do
# Declare a setting with a `nil` default
setting :a_config_key
# Declare one with a default value
setting :another_config_key, default: 18
end
330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/configurability.rb', line 330 def configurability( config_key=nil, &block ) self.config_key = config_key if config_key if block Configurability.log.debug "Applying config declaration block using a SettingInstaller" installer = Configurability::SettingInstaller.new( self ) installer.instance_eval( &block ) end if (( config = Configurability.loaded_config )) Configurability.install_config( config, self ) end end |
#configure(config = nil) ⇒ Object
Default configuration method. This will merge the provided config with the defaults if there are any and the config responds to #to_h. If the config responds to #each_pair, any writable attributes of the calling object with the same name as a key of the config will be called with the corresponding value. E.g.,
class MyClass
extend Configurability
CONFIG_DEFAULTS = { environment: 'develop', apikey: 'testing-key' }
config_key :my_class
class << self
attr_accessor :environment, :apikey
end
end
config = { my_class: {apikey: 'demo-key'} }
Configurability.configure_objects( config )
MyClass.apikey
# => 'demo-key'
MyClass.environment
# => 'develop'
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/configurability.rb', line 294 def configure( config=nil ) config = self.defaults( {} ).merge( config.to_h || {} ) if config.nil? || config.respond_to?( :to_h ) @config = config if @config.respond_to?( :each_pair ) @config.each_pair do |key, value| Configurability.log.debug "Looking for %p config attribute" % [ key ] next unless self.respond_to?( "#{key}=" ) Configurability.log.debug " setting %p to %p" % [ key, value ] self.public_send( "#{key}=", value ) end else Configurability.log. debug "config object (%p) isn't iterable; skipping config attributes" % [ @config ] end return @config end |
#default_config ⇒ Object
Return a Configurability::Config object that contains the configuration defaults for the receiver.
374 375 376 377 |
# File 'lib/configurability.rb', line 374 def default_config default_values = self.defaults or return Configurability::Config::Struct.new( {} ) return Configurability::Config::Struct.new( default_values ) end |
#defaults(fallback = nil) ⇒ Object
The default implementation of the method called by ::gather_defaults when gathering configuration defaults. This method expects either a DEFAULT_CONFIG or a CONFIG_DEFAULTS constant to contain the configuration defaults, and will just return the fallback value if neither exists.
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/configurability.rb', line 354 def defaults( fallback=nil ) return fallback unless respond_to?( :const_defined? ) Configurability.log.debug "Looking for defaults in %p's constants." % [ self ] if self.const_defined?( :DEFAULT_CONFIG, false ) Configurability.log.debug " found DEFAULT_CONFIG" return self.const_get( :DEFAULT_CONFIG, false ).dup elsif self.const_defined?( :CONFIG_DEFAULTS, false ) Configurability.log.debug " found CONFIG_DEFAULTS" return self.const_get( :CONFIG_DEFAULTS, false ).dup else Configurability.log.debug " no default constants." return fallback end end |