ConfigMapper maps configuration data onto Ruby objects.
Imagine you have some Ruby objects:
class Position
attr_reader :x
attr_reader :y
def x=(arg); @x = Integer(arg); end
def y=(arg); @y = Integer(arg); end
end
class State
def initialize
@position = Position.new
end
attr_reader :position
attr_accessor :orientation
end
state = State.new
and wish to populate/modify it, based on plain data:
config_data = {
"orientation" => "North",
"position" => {
"x" => 2,
"y" => 4
}
}
ConfigMapper will help you out:
require 'config_mapper'
errors = ConfigMapper.configure_with(config_data, state)
state.orientation #=> "North"
state.position.x #=> 2
It can even populate Hashes of objects, e.g.
positions = Hash.new { |h,k| h[k] = Position.new }
config_data = {
"fred" => { "x" => 2, "y" => 4 },
"mary" => { "x" => 3, "y" => 5 }
}
ConfigMapper.configure_with(config_data, positions)
positions["fred"].x #=> 2
positions["mary"].y #=> 5
Given
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:
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.
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.
config_data = {
"position" => {
"bogus" => "flibble"
}
}
errors = ConfigMapper.configure_with(config_data, state)
errors #=> { ".position.bogus" => #<NoMethodError> }
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.
require "config_mapper/config_struct"
class State < ConfigMapper::ConfigStruct
component :position do
attribute(:x) { |arg| Integer(arg) }
attribute(:y) { |arg| Integer(arg) }
end
attribute :orientation
end
By default, declared attributes are assumed to be mandatory. The
ConfigStruct#config_errors
method returns errors for each unset mandatory
attribute.
state = State.new
state.position.x = 3
state.position.y = 4
state.config_errors
#=> { ".orientation" => "no value provided" }
#config_errors
can be overridden to provide custom semantic validation.
Attributes can be given default values. Provide an explicit nil
default to
mark an attribute as optional, e.g.
class Address < ConfigMapper::ConfigStruct
attribute :host
attribute :port, :default => 80
attribute :path, :default => nil
end
ConfigStruct#configure_with
maps data into the object, and combines mapping errors and
semantic errors (returned by #config_errors
) into a single Hash:
data = {
"position" => { "x" => 3, "y" => "fore" },
"bogus" => "foobar"
}
state.configure_with(data)
#=> {
#=> ".orientation" => "no value provided",
#=> ".position.y" => #<ArgumentError: invalid value for Integer(): "fore">,
#=> ".bogus" => #<NoMethodError: undefined method `bogus=' for #<State:0x007fc8e9b12a60>>
#=> }
The gem is available as open source under the terms of the MIT License.
It's on GitHub; you know the drill.
- ConfigHound is a great way to load raw config-data, before throwing it to ConfigMapper.