ZenConfig
ZenConfig is an attempt to rewrite Zend Framework's Zend_Config for Ruby.
It allows easy management of configuration objects and files.
Installation
Add this line to your application's Gemfile:
gem 'zen_config'
And then execute:
$ bundle
Or install it yourself as:
$ gem install zen_config
App integration
Create your config file config/config.yml :
defaults: &defaults
user:
name:
max_length: 64
post:
max_length: 140
development:
<<: defaults
production:
<<: defaults
Create a new initializer config/initializers/config.rb :
APP_CONFIG = ZenConfig.new(YAML::load(File.open("#{Rails.root}/config/config.yml"))[Rails.env])
Restrict config scope in your classes :
class User
@config = APP_CONFIG.user
validates :name, :length => { :maximum => @config.name.max_length }
[...]
end
Usage
###Instantiate ZenConfig with a configuration hash :
config_hash = { :foo => "foo value", :bar => { :baz => "baz value" } }
MyConfig = ZenConfig.new config_hash
MyConfig.foo
=> "foo value"
MyConfig.bar.baz
=> "baz value"
###By default, ZenConfig is read only :
MyConfig.foo = "bar value"
NoMethodError: undefined method `foo=' for #<ZenConfig:0x00000002ee52f8>
###But changes can be allowed on build time :
MyConfig = ZenConfig.new config_hash, true
MyConfig.foo = "new foo value"
=> "new foo value"
MyConfig.foo
=> "new foo value"
###Then the object can be locked to read only again :
MyConfig.read_only
MyConfig.read_only?
=> true
MyConfig.foo = "foo value"
NoMethodError: undefined method `foo=' for #<ZenConfig:0x00000002ee52f8>
And there's no way to unlock write.
This guarantees that ZenConfig data hasn't been altered since read-only lock has been set. You should not use unlocked ZenConfig in your application code, since you don't know when and where it has been modified. Dynamic persistent writes functions will come in future versions.
###Sub configurations can be nested (if ZenConfig is unlocked) :
MyConfig.new :bar
MyConfig.bar.baz = "baz value"
###Nested configurations are ZenConfigs :
MyBarConfig = MyConfig.bar
MyBarConfig.class
=> ZenConfig
MyBarConfig.baz
=> "baz value"
This allow accessing configuration on a specific context.
###Nested ZenConfigs can access their parent :
MyBarConfig.parent.foo
=> "bar value"
###Of course, root ZenConfig has no parent :
MyConfig.parent
=> nil
###ZenConfigs can be converted to hashs :
MyConfig.to_hash
=> {:foo=>"new foo value", :bar=>{:baz=>"baz value"}}
###ZenConfigs subkeys can be parsed :
MyConfig.each do |key, value|
puts key.to_s + ":" + value.class.to_s
end
foo:String
bar:ZenConfig
=> {:foo=>"foo value", :bar=>{:baz=>"baz value"}}
###Counted :
MyConfig.count
=> 2
MyConfig.bar.count
=> 1
###Checked :
MyConfig.exists? :bar
=> true
or
MyConfig.bar_exists?
=> true
###Deleted (if ZenConfig is unlocked) :
MyConfig.delete :bar
MyConfig.to_hash
=> {:foo=>"new foo value"}
or
MyConfig.delete_bar
MyConfig.to_hash
=> {:foo=>"new foo value"}
Important note on reserved words :
Some words are reserved by Ruby or ZenConfig (public methods or attributes).
The best practice is to avoid using one of these reserved words as keys in your config files, but you can call them by adding an underscore in front of the key.
Using these reserved words can result in strange behaviors :
MyConfig = ZenConfig.new({ :reject => { :default => 10 } })
[...]
MyConfig.to_hash
=> {:reject=>{:value=>"10"}}
Config is successfully loaded but :
MyConfig.reject.value
NoMethodError: undefined method `value' for #<Enumerator:0x000000026fd5b8>
Solution :
MyConfig._reject.value
=> 10
Reserved words could be reduced by making ZenConfig a BasicObject subclass, but it's not possible as ZenConfig uses Enumerable.
Anyone knowing how to get the best of both worlds is welcome !
Goals
- Provide hierarchical configuration objects => Done!
- Bring a read-only lock mode to guarantee config values haven't been modified => Done!
- Allow config file loading.
- Allow config file writing.
- Provide full unit tests.
Known bugs
- Merging doesn't always work