README.md in config_mapper-1.4.1 vs README.md in config_mapper-1.5.0

- old
+ new

@@ -1,12 +1,28 @@ # ConfigMapper -[![Gem Version](https://badge.fury.io/rb/config_mapper.png)](http://badge.fury.io/rb/config_mapper) -[![Build Status](https://secure.travis-ci.org/mdub/config_mapper.png?branch=master)](http://travis-ci.org/mdub/config_mapper) +[![Gem Version](https://badge.fury.io/rb/config_mapper.svg)](https://badge.fury.io/rb/config_mapper) +[![Build Status](https://travis-ci.org/mdub/config_mapper.svg?branch=master)](https://travis-ci.org/mdub/config_mapper) ConfigMapper maps configuration data onto Ruby objects. +<!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 --> + +- [Usage](#usage) + - [Target object](#target-object) + - [Errors](#errors) +- [ConfigStruct](#configstruct) + - [Attributes](#attributes) + - [Type validation/coercion](#type-validationcoercion) + - [Defaults](#defaults) + - [Semantic errors](#semantic-errors) +- [License](#license) +- [Contributing](#contributing) +- [See also](#see-also) + +<!-- /TOC --> + ## Usage Imagine you have some Ruby objects: ```ruby @@ -114,64 +130,107 @@ ConfigMapper works pretty well with plain old Ruby objects, but we provide a base-class, `ConfigMapper::ConfigStruct`, with a DSL that makes it even easier to declare configuration data-structures. +### Attributes + +The `attribute` method is similar to `attr_accessor`, defining both reader and writer methods for the named attribute. + ```ruby require "config_mapper/config_struct" class State < ConfigMapper::ConfigStruct - component :position do - attribute :x - attribute :y + attribute :orientation + +end +``` + +### Type validation/coercion + +If you specify a block when declaring an attribute, it will be invoked as part of the attribute's writer-method, to validate values when they are set. It should expect a single argument, and raise `ArgumentError` to signal invalid input. As the return value will be used as the value of the attribute, it's also an opportunity coerce values into canonical form. + +```ruby +class Server < ConfigMapper::ConfigStruct + + attribute :host do |arg| + unless arg =~ /^\w+(\.\w+)+$/ + raise ArgumentError, "invalid hostname: #{arg}" + end + arg end - attribute :orientation + attribute :port do |arg| + Integer(arg) + end end ``` -`ConfigStruct#config_errors` returns errors for each unset mandatory attribute. +Alternatively, specify a "validator" as a second argument to `attribute`. It should be an object that responds to `#call`, with the same semantics described above. Good choices include `Proc` or `Method` objects, or type-objects from the [dry-types](http://dry-rb.org/gems/dry-types/) project. ```ruby -state = State.new -state.position.x = 3 -state.position.y = 4 -state.config_errors -#=> { ".orientation" => #<ConfigMapper::ConfigStruct::NoValueProvided: no value provided> } +class Server < ConfigMapper::ConfigStruct + + attribute :host, Types::Strict::String.constrained(format: /^\w+(\.\w+)+$/) + attribute :port, method(:Integer) + +end ``` -`#config_errors` can be overridden to provide custom semantic validation. +For convenience, primitive Ruby types such as `Integer` and `Float` can be used as shorthand for their namesake type-coercion methods on `Kernel`: -Attributes can be given default values. Specify a default value of `nil` to mark an attribute as optional, e.g. +```ruby +class Server < ConfigMapper::ConfigStruct + attribute :port, Integer + +end +``` + +### Defaults + +Attributes can be given default values, e.g. + ```ruby class Address < ConfigMapper::ConfigStruct attribute :host attribute :port, :default => 80 attribute :path, :default => nil end ``` -If a block is provided when an `attribute` is declared, it is used to validate values when they are set, and/or coerce them to a canonical type. The block should raise `ArgumentError` to indicate an invalid value. +Specify a default value of `nil` to mark an attribute as optional. Attributes without a default are treated as "required". +### Sub-components + +The `component` method defines a nested component object, itself a `ConfigStruct`. + ```ruby -class Server < ConfigMapper::ConfigStruct +class State < ConfigMapper::ConfigStruct - attribute :host do |arg| - unless arg =~ /^\w+(\.\w+)+$/ - raise ArgumentError, "invalid hostname: #{arg}" - end - arg + component :position do + attribute :x + attribute :y end - attribute :port do |arg| - Integer(arg) - end - end ``` + +### Semantic errors + +`ConfigStruct#config_errors` returns errors for each unset mandatory attribute. + +```ruby +state = State.new +state.position.x = 3 +state.position.y = 4 +state.config_errors +#=> { ".orientation" => #<ConfigMapper::ConfigStruct::NoValueProvided: no value provided> } +``` + +`#config_errors` can be overridden to provide custom semantic validation. `ConfigStruct#configure_with` maps data into the object, and combines mapping errors and semantic errors (returned by `#config_errors`) into a single Hash: ```ruby data = {