Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mdub/config_mapper
Maps config data onto plain old Ruby objects
https://github.com/mdub/config_mapper
Last synced: about 2 months ago
JSON representation
Maps config data onto plain old Ruby objects
- Host: GitHub
- URL: https://github.com/mdub/config_mapper
- Owner: mdub
- License: mit
- Created: 2015-08-03T11:40:34.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2023-02-28T07:55:29.000Z (almost 2 years ago)
- Last Synced: 2024-04-24T12:04:01.959Z (8 months ago)
- Language: Ruby
- Size: 134 KB
- Stars: 6
- Watchers: 3
- Forks: 6
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# ConfigMapper
[![Gem Version](https://badge.fury.io/rb/config_mapper.svg)](https://badge.fury.io/rb/config_mapper)
[![Build Status](https://github.com/mdub/config_mapper/actions/workflows/test.yaml/badge.svg?branch=master)](https://github.com/mdub/config_mapper/actions/workflows/test.yaml)ConfigMapper maps configuration data onto Ruby objects.
- [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)## Usage
Imagine you have some Ruby objects:
```ruby
class Positionattr_reader :x
attr_reader :ydef x=(arg); @x = Integer(arg); end
def y=(arg); @y = Integer(arg); endend
class State
def initialize
@position = Position.new
endattr_reader :position
attr_accessor :orientationend
state = State.new
```and wish to populate/modify it, based on plain data:
```ruby
config_data = {
"orientation" => "North",
"position" => {
"x" => 2,
"y" => 4
}
}
```ConfigMapper will help you out:
```ruby
require 'config_mapper'errors = ConfigMapper.configure_with(config_data, state)
state.orientation #=> "North"
state.position.x #=> 2
```### Target object
Given
```ruby
ConfigMapper.configure_with(config_data, target)
```the `target` object is expected provide accessor-methods corresponding
to the attributes that you want to make configurable. For example, with:```ruby
config_data = {
"orientation" => "North",
"position" => { "x" => 2, "y" => 4 }
}
```it should have a `orientiation=` method, and a `position` method that
returns a `Position` object, which should in turn have `x=` and `y=`
methods.ConfigMapper cannot and will not _create_ objects for you.
### Errors
`ConfigMapper.configure_with` returns a Hash of errors encountered while mapping data onto objects. The errors are Exceptions (typically ArgumentError or NoMethodError), keyed by the path to the offending data. e.g.
```ruby
config_data = {
"position" => {
"bogus" => "flibble"
}
}errors = ConfigMapper.configure_with(config_data, state)
errors #=> { ".position.bogus" => # }
```## ConfigStruct
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
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::ConfigStructattribute :host do |arg|
unless arg =~ /^\w+(\.\w+)+$/
raise ArgumentError, "invalid hostname: #{arg}"
end
arg
endattribute :port do |arg|
Integer(arg)
endend
```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
class Server < ConfigMapper::ConfigStructattribute :host, Types::Strict::String.constrained(format: /^\w+(\.\w+)+$/)
attribute :port, method(:Integer)end
```For convenience, primitive Ruby types such as `Integer` and `Float` can be used as shorthand for their namesake type-coercion methods on `Kernel`:
```ruby
class Server < ConfigMapper::ConfigStructattribute :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
```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 State < ConfigMapper::ConfigStructcomponent :position do
attribute :x
attribute :y
endend
````component_list` declares a nested list of configurable objects, indexed by position.
```ruby
class Polygon < ConfigMapper::ConfigStructcomponent_list :points do
attribute :x
attribute :y
endend
```
`component_dict` declares a dictionary (map) of configurable objects, indexed by an arbitrary key.
```ruby
class Cargo < ConfigMapper::ConfigStructcomponent_dict :packages do
attribute :contents
attribute :weight, Float
endend
```In both cases, new collection entries pop into existance the first time they are accessed.
### 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" => # }
````#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 = {
"position" => { "x" => 3, "y" => "fore" },
"bogus" => "foobar"
}
state.configure_with(data)
#=> {
#=> ".orientation" => "no value provided",
#=> ".position.y" => #,
#=> ".bogus" => #>
#=> }
````ConfigStruct.from_data` instantiates an object from data, raising an exception if errors are encountered:
```ruby
state = State.from_data(data)
```## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
## Contributing
It's on GitHub; you know the drill.
## See also
* [ConfigHound](https://github.com/mdub/config_hound) is a great way to
load raw config-data, before throwing it to ConfigMapper.