config_mapper 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/Guardfile +43 -0
- data/README.md +101 -10
- data/lib/config_mapper/config_struct.rb +189 -0
- data/lib/config_mapper/hash_target.rb +27 -0
- data/lib/config_mapper/object_target.rb +27 -0
- data/lib/config_mapper/target.rb +39 -0
- data/lib/config_mapper/version.rb +1 -1
- data/lib/config_mapper.rb +30 -14
- metadata +8 -6
- data/lib/config_mapper/attribute_sink.rb +0 -42
- data/lib/config_mapper/error_proxy.rb +0 -23
- data/lib/config_mapper/object_as_hash.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3eceb23aa75978730fec0b82c9a01185852d056
|
4
|
+
data.tar.gz: 155083970129df5b800a6085957a21c98e869f1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c959618d4f898cc3e49c3512042c043b5b5a4b26cb7a9287e6620832fc53be5de77950fc7e7d551695c203ef8a8235a361e20db5f5d557bad0834f2e63075c1
|
7
|
+
data.tar.gz: 128a784d48f8933c27c83d55e754889b43f9eb6e61451ca6a4e23162e88175a6b53ad8e1a8cb27f156bac796767e6715d08b07c82f632b50433825b172842837
|
data/Gemfile
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, :cmd => "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
|
43
|
+
end
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ end
|
|
20
20
|
class State
|
21
21
|
|
22
22
|
def initialize
|
23
|
-
|
23
|
+
@position = Position.new
|
24
24
|
end
|
25
25
|
|
26
26
|
attr_reader :position
|
@@ -37,8 +37,8 @@ and wish to populate/modify it, based on plain data:
|
|
37
37
|
config_data = {
|
38
38
|
"orientation" => "North",
|
39
39
|
"position" => {
|
40
|
-
|
41
|
-
|
40
|
+
"x" => 2,
|
41
|
+
"y" => 4
|
42
42
|
}
|
43
43
|
}
|
44
44
|
```
|
@@ -48,7 +48,7 @@ ConfigMapper will help you out:
|
|
48
48
|
```ruby
|
49
49
|
require 'config_mapper'
|
50
50
|
|
51
|
-
errors = ConfigMapper.
|
51
|
+
errors = ConfigMapper.configure_with(config_data, state)
|
52
52
|
state.orientation #=> "North"
|
53
53
|
state.position.x #=> 2
|
54
54
|
```
|
@@ -63,16 +63,38 @@ config_data = {
|
|
63
63
|
"mary" => { "x" => 3, "y" => 5 }
|
64
64
|
}
|
65
65
|
|
66
|
-
ConfigMapper.
|
66
|
+
ConfigMapper.configure_with(config_data, positions)
|
67
67
|
positions["fred"].x #=> 2
|
68
68
|
positions["mary"].y #=> 5
|
69
69
|
```
|
70
70
|
|
71
|
+
### Target object
|
72
|
+
|
73
|
+
Given
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
ConfigMapper.configure_with(config_data, target)
|
77
|
+
```
|
78
|
+
|
79
|
+
the `target` object is expected provide accessor-methods corresponding
|
80
|
+
to the attributes that you want to make configurable. For example, with:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
config_data = {
|
84
|
+
"orientation" => "North",
|
85
|
+
"position" => { "x" => 2, "y" => 4 }
|
86
|
+
}
|
87
|
+
```
|
88
|
+
|
89
|
+
it should have a `orientiation=` method, and a `position` method that
|
90
|
+
returns a `Position` object, which should in turn have `x=` and `y=`
|
91
|
+
methods.
|
92
|
+
|
93
|
+
ConfigMapper cannot and will not _create_ objects for you.
|
94
|
+
|
71
95
|
### Errors
|
72
96
|
|
73
|
-
`ConfigMapper.
|
74
|
-
onto objects. The errors are Exceptions (typically ArgumentError or NoMethodError),
|
75
|
-
keyed by a Array representing the path to the offending data. e.g.
|
97
|
+
`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.
|
76
98
|
|
77
99
|
```ruby
|
78
100
|
config_data = {
|
@@ -81,8 +103,72 @@ config_data = {
|
|
81
103
|
}
|
82
104
|
}
|
83
105
|
|
84
|
-
errors = ConfigMapper.
|
85
|
-
errors #=> {
|
106
|
+
errors = ConfigMapper.configure_with(config_data, state)
|
107
|
+
errors #=> { ".position.bogus" => #<NoMethodError> }
|
108
|
+
```
|
109
|
+
|
110
|
+
## ConfigStruct
|
111
|
+
|
112
|
+
ConfigMapper works pretty well with plain old Ruby objects, but we
|
113
|
+
provide a base-class, `ConfigMapper::ConfigStruct`, with a DSL that
|
114
|
+
makes it even easier to declare configuration data-structures.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
require "config_mapper/config_struct"
|
118
|
+
|
119
|
+
class State < ConfigMapper::ConfigStruct
|
120
|
+
|
121
|
+
component :position do
|
122
|
+
attribute(:x) { |arg| Integer(arg) }
|
123
|
+
attribute(:y) { |arg| Integer(arg) }
|
124
|
+
end
|
125
|
+
|
126
|
+
attribute :orientation
|
127
|
+
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
By default, declared attributes are assumed to be mandatory. The
|
132
|
+
`ConfigStruct#config_errors` method returns errors for each unset mandatory
|
133
|
+
attribute.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
state = State.new
|
137
|
+
state.position.x = 3
|
138
|
+
state.position.y = 4
|
139
|
+
state.config_errors
|
140
|
+
#=> { ".orientation" => "no value provided" }
|
141
|
+
```
|
142
|
+
|
143
|
+
`#config_errors` can be overridden to provide custom semantic validation.
|
144
|
+
|
145
|
+
Attributes can be given default values. Provide an explicit `nil` default to
|
146
|
+
mark an attribute as optional, e.g.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class Address < ConfigMapper::ConfigStruct
|
150
|
+
|
151
|
+
attribute :host
|
152
|
+
attribute :port, :default => 80
|
153
|
+
attribute :path, :default => nil
|
154
|
+
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
`ConfigStruct#configure_with` maps data into the object, and combines mapping errors and
|
159
|
+
semantic errors (returned by `#config_errors`) into a single Hash:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
data = {
|
163
|
+
"position" => { "x" => 3, "y" => "fore" },
|
164
|
+
"bogus" => "foobar"
|
165
|
+
}
|
166
|
+
state.configure_with(data)
|
167
|
+
#=> {
|
168
|
+
#=> ".orientation" => "no value provided",
|
169
|
+
#=> ".position.y" => #<ArgumentError: invalid value for Integer(): "fore">,
|
170
|
+
#=> ".bogus" => #<NoMethodError: undefined method `bogus=' for #<State:0x007fc8e9b12a60>>
|
171
|
+
#=> }
|
86
172
|
```
|
87
173
|
|
88
174
|
## License
|
@@ -92,3 +178,8 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
92
178
|
## Contributing
|
93
179
|
|
94
180
|
It's on GitHub; you know the drill.
|
181
|
+
|
182
|
+
## See also
|
183
|
+
|
184
|
+
* [ConfigHound](https://github.com/mdub/config_hound) is a great way to
|
185
|
+
load raw config-data, before throwing it to ConfigMapper.
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require "config_mapper"
|
2
|
+
require "forwardable"
|
3
|
+
|
4
|
+
module ConfigMapper
|
5
|
+
|
6
|
+
# A set of configurable attributes.
|
7
|
+
#
|
8
|
+
class ConfigStruct
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Defines reader and writer methods for the specified attribute.
|
13
|
+
#
|
14
|
+
# A `:default` value may be specified; otherwise, the attribute is
|
15
|
+
# considered mandatory.
|
16
|
+
#
|
17
|
+
# If a block is provided, it will invoked in the writer-method to
|
18
|
+
# validate the argument.
|
19
|
+
#
|
20
|
+
# @param name [Symbol] attribute name
|
21
|
+
# @options options [String] :default (nil) default value
|
22
|
+
# @yield type-coercion block
|
23
|
+
#
|
24
|
+
def attribute(name, options = {}, &coerce_block)
|
25
|
+
name = name.to_sym
|
26
|
+
if options.key?(:default)
|
27
|
+
default_value = options.fetch(:default).freeze
|
28
|
+
attribute_initializers[name] = proc { default_value }
|
29
|
+
else
|
30
|
+
required_attributes << name
|
31
|
+
end
|
32
|
+
attr_reader(name)
|
33
|
+
if coerce_block
|
34
|
+
define_method("#{name}=") do |arg|
|
35
|
+
instance_variable_set("@#{name}", coerce_block.call(arg))
|
36
|
+
end
|
37
|
+
else
|
38
|
+
attr_writer(name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Defines a sub-component.
|
43
|
+
#
|
44
|
+
# If a block is be provided, it will be `class_eval`ed to define the
|
45
|
+
# sub-components class.
|
46
|
+
#
|
47
|
+
# @param name [Symbol] component name
|
48
|
+
# @options options [String] :type (ConfigMapper::ConfigStruct)
|
49
|
+
# component base-class
|
50
|
+
#
|
51
|
+
def component(name, options = {}, &block)
|
52
|
+
name = name.to_sym
|
53
|
+
declared_components << name
|
54
|
+
type = options.fetch(:type, ConfigStruct)
|
55
|
+
type = Class.new(type, &block) if block
|
56
|
+
type = type.method(:new) if type.respond_to?(:new)
|
57
|
+
attribute_initializers[name] = type
|
58
|
+
attr_reader name
|
59
|
+
end
|
60
|
+
|
61
|
+
# Defines an associative array of sub-components.
|
62
|
+
#
|
63
|
+
# If a block is be provided, it will be `class_eval`ed to define the
|
64
|
+
# sub-components class.
|
65
|
+
#
|
66
|
+
# @param name [Symbol] dictionary attribute name
|
67
|
+
# @options options [Proc] :key_type
|
68
|
+
# function used to validate keys
|
69
|
+
# @options options [String] :type (ConfigMapper::ConfigStruct)
|
70
|
+
# base-class for sub-component values
|
71
|
+
#
|
72
|
+
def component_dict(name, options = {}, &block)
|
73
|
+
name = name.to_sym
|
74
|
+
declared_component_dicts << name
|
75
|
+
type = options.fetch(:type, ConfigStruct)
|
76
|
+
type = Class.new(type, &block) if block
|
77
|
+
type = type.method(:new) if type.respond_to?(:new)
|
78
|
+
key_type = options[:key_type]
|
79
|
+
key_type = key_type.method(:new) if key_type.respond_to?(:new)
|
80
|
+
attribute_initializers[name] = lambda do
|
81
|
+
ConfigDict.new(type, key_type)
|
82
|
+
end
|
83
|
+
attr_reader name
|
84
|
+
end
|
85
|
+
|
86
|
+
def required_attributes
|
87
|
+
@required_attributes ||= []
|
88
|
+
end
|
89
|
+
|
90
|
+
def attribute_initializers
|
91
|
+
@attribute_initializers ||= {}
|
92
|
+
end
|
93
|
+
|
94
|
+
def declared_components
|
95
|
+
@declared_components ||= []
|
96
|
+
end
|
97
|
+
|
98
|
+
def declared_component_dicts
|
99
|
+
@declared_component_dicts ||= []
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
def initialize
|
105
|
+
self.class.attribute_initializers.each do |name, initializer|
|
106
|
+
instance_variable_set("@#{name}", initializer.call)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def immediate_config_errors
|
111
|
+
missing_required_attribute_errors
|
112
|
+
end
|
113
|
+
|
114
|
+
def config_errors
|
115
|
+
immediate_config_errors.merge(component_config_errors)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Configure with data.
|
119
|
+
#
|
120
|
+
# @param attribute_values [Hash] attribute values
|
121
|
+
# @return [Hash] errors encountered, keyed by attribute path
|
122
|
+
#
|
123
|
+
def configure_with(attribute_values)
|
124
|
+
errors = ConfigMapper.configure_with(attribute_values, self)
|
125
|
+
config_errors.merge(errors)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def components
|
131
|
+
{}.tap do |result|
|
132
|
+
self.class.declared_components.each do |name|
|
133
|
+
result[".#{name}"] = instance_variable_get("@#{name}")
|
134
|
+
end
|
135
|
+
self.class.declared_component_dicts.each do |name|
|
136
|
+
instance_variable_get("@#{name}").each do |key, value|
|
137
|
+
result[".#{name}[#{key.inspect}]"] = value
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
NOT_SET = "no value provided".freeze
|
144
|
+
|
145
|
+
def missing_required_attribute_errors
|
146
|
+
{}.tap do |errors|
|
147
|
+
self.class.required_attributes.each do |name|
|
148
|
+
unless instance_variable_defined?("@#{name}")
|
149
|
+
errors[".#{name}"] = NOT_SET
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def component_config_errors
|
156
|
+
{}.tap do |errors|
|
157
|
+
components.each do |component_name, component_value|
|
158
|
+
next unless component_value.respond_to?(:config_errors)
|
159
|
+
component_value.config_errors.each do |key, value|
|
160
|
+
errors["#{component_name}#{key}"] = value
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
class ConfigDict
|
169
|
+
|
170
|
+
def initialize(entry_type, key_type = nil)
|
171
|
+
@entry_type = entry_type
|
172
|
+
@key_type = key_type
|
173
|
+
@entries = {}
|
174
|
+
end
|
175
|
+
|
176
|
+
def [](key)
|
177
|
+
key = @key_type.call(key) if @key_type
|
178
|
+
@entries[key] ||= @entry_type.call
|
179
|
+
end
|
180
|
+
|
181
|
+
extend Forwardable
|
182
|
+
|
183
|
+
def_delegators :@entries, :each, :empty?, :keys, :map, :size
|
184
|
+
|
185
|
+
include Enumerable
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "config_mapper/target"
|
2
|
+
|
3
|
+
module ConfigMapper
|
4
|
+
|
5
|
+
# Configuration proxy for a Hash.
|
6
|
+
#
|
7
|
+
class HashTarget < Target
|
8
|
+
|
9
|
+
def initialize(hash)
|
10
|
+
@hash = hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def path(key)
|
14
|
+
"[#{key.inspect}]"
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(key)
|
18
|
+
@hash[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(key, value)
|
22
|
+
@hash[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "config_mapper/target"
|
2
|
+
|
3
|
+
module ConfigMapper
|
4
|
+
|
5
|
+
# Configuration proxy for an Object.
|
6
|
+
#
|
7
|
+
class ObjectTarget < Target
|
8
|
+
|
9
|
+
def initialize(object)
|
10
|
+
@object = object
|
11
|
+
end
|
12
|
+
|
13
|
+
def path(key)
|
14
|
+
".#{key}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(key)
|
18
|
+
@object.public_send(key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(key, value)
|
22
|
+
@object.public_send("#{key}=", value)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ConfigMapper
|
2
|
+
|
3
|
+
# Something that accepts configuration.
|
4
|
+
#
|
5
|
+
class Target
|
6
|
+
|
7
|
+
# Map configuration data onto the target.
|
8
|
+
#
|
9
|
+
# @return [Hash] exceptions encountered
|
10
|
+
#
|
11
|
+
def with(data)
|
12
|
+
errors = {}
|
13
|
+
data.each do |key, value|
|
14
|
+
configure_attribute(key, value, errors)
|
15
|
+
end
|
16
|
+
errors
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Set a single attribute.
|
22
|
+
#
|
23
|
+
def configure_attribute(key, value, errors)
|
24
|
+
attribute_path = path(key)
|
25
|
+
if value.is_a?(Hash) && !get(key).nil?
|
26
|
+
nested_errors = ConfigMapper.configure_with(value, get(key))
|
27
|
+
nested_errors.each do |nested_path, error|
|
28
|
+
errors["#{attribute_path}#{nested_path}"] = error
|
29
|
+
end
|
30
|
+
else
|
31
|
+
set(key, value)
|
32
|
+
end
|
33
|
+
rescue NoMethodError, ArgumentError => e
|
34
|
+
errors[attribute_path] = e
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/config_mapper.rb
CHANGED
@@ -1,23 +1,39 @@
|
|
1
|
-
require "config_mapper/
|
1
|
+
require "config_mapper/hash_target"
|
2
|
+
require "config_mapper/object_target"
|
2
3
|
|
3
4
|
# Supports marshalling of plain-old data (e.g. loaded from
|
4
5
|
# YAML files) onto strongly-typed objects.
|
5
6
|
#
|
6
7
|
module ConfigMapper
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Set attributes of a target object based on configuration data.
|
12
|
+
#
|
13
|
+
# For simple, scalar values, set the attribute by calling the
|
14
|
+
# named writer-method on the target object.
|
15
|
+
#
|
16
|
+
# For Hash values, set attributes of the named sub-component.
|
17
|
+
#
|
18
|
+
# @param data configuration data
|
19
|
+
# @param [Object, Hash] target the object to configure
|
20
|
+
#
|
21
|
+
# @return [Hash] exceptions encountered
|
22
|
+
#
|
23
|
+
def configure_with(data, target)
|
24
|
+
target(target).with(data)
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :set, :configure_with
|
28
|
+
|
29
|
+
def target(target)
|
30
|
+
if target.is_a?(Hash)
|
31
|
+
HashTarget.new(target)
|
32
|
+
else
|
33
|
+
ObjectTarget.new(target)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
21
37
|
end
|
22
38
|
|
23
39
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: config_mapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -64,14 +64,16 @@ files:
|
|
64
64
|
- ".rubocop.yml"
|
65
65
|
- ".travis.yml"
|
66
66
|
- Gemfile
|
67
|
+
- Guardfile
|
67
68
|
- LICENSE.txt
|
68
69
|
- README.md
|
69
70
|
- Rakefile
|
70
71
|
- config_mapper.gemspec
|
71
72
|
- lib/config_mapper.rb
|
72
|
-
- lib/config_mapper/
|
73
|
-
- lib/config_mapper/
|
74
|
-
- lib/config_mapper/
|
73
|
+
- lib/config_mapper/config_struct.rb
|
74
|
+
- lib/config_mapper/hash_target.rb
|
75
|
+
- lib/config_mapper/object_target.rb
|
76
|
+
- lib/config_mapper/target.rb
|
75
77
|
- lib/config_mapper/version.rb
|
76
78
|
homepage: https://github.com/mdub/config_mapper
|
77
79
|
licenses:
|
@@ -93,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
95
|
version: '0'
|
94
96
|
requirements: []
|
95
97
|
rubyforge_project:
|
96
|
-
rubygems_version: 2.
|
98
|
+
rubygems_version: 2.5.0
|
97
99
|
signing_key:
|
98
100
|
specification_version: 4
|
99
101
|
summary: Maps config data onto plain old objects
|
@@ -1,42 +0,0 @@
|
|
1
|
-
require "config_mapper/error_proxy"
|
2
|
-
require "config_mapper/object_as_hash"
|
3
|
-
|
4
|
-
module ConfigMapper
|
5
|
-
|
6
|
-
# Sets attributes on an object, collecting errors
|
7
|
-
#
|
8
|
-
class AttributeSink
|
9
|
-
|
10
|
-
def initialize(target, errors = {})
|
11
|
-
@target = ObjectAsHash[target]
|
12
|
-
@errors = errors
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_reader :target
|
16
|
-
attr_reader :errors
|
17
|
-
|
18
|
-
# Set multiple attributes from a Hash.
|
19
|
-
#
|
20
|
-
def set_attributes(data)
|
21
|
-
data.each do |key, value|
|
22
|
-
set_attribute(key, value)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Set a single attribute.
|
27
|
-
#
|
28
|
-
def set_attribute(key, value)
|
29
|
-
if value.is_a?(Hash) && !target[key].nil?
|
30
|
-
nested_errors = ErrorProxy.new(errors, [key])
|
31
|
-
nested_mapper = self.class.new(target[key], nested_errors)
|
32
|
-
nested_mapper.set_attributes(value)
|
33
|
-
else
|
34
|
-
target[key] = value
|
35
|
-
end
|
36
|
-
rescue NoMethodError, ArgumentError => e
|
37
|
-
errors[[key]] = e
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module ConfigMapper
|
2
|
-
|
3
|
-
# Wraps a Hash of errors, injecting prefixes
|
4
|
-
#
|
5
|
-
class ErrorProxy
|
6
|
-
|
7
|
-
def initialize(errors, prefix)
|
8
|
-
@errors = errors
|
9
|
-
@prefix = prefix
|
10
|
-
end
|
11
|
-
|
12
|
-
def []=(key, value)
|
13
|
-
errors[prefix + key] = value
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
attr_reader :errors
|
19
|
-
attr_reader :prefix
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module ConfigMapper
|
2
|
-
|
3
|
-
# Wrap an object to make it look more like a Hash.
|
4
|
-
#
|
5
|
-
class ObjectAsHash
|
6
|
-
|
7
|
-
def self.[](target)
|
8
|
-
if target.is_a?(Hash)
|
9
|
-
target
|
10
|
-
else
|
11
|
-
ObjectAsHash.new(target)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize(target)
|
16
|
-
@target = target
|
17
|
-
end
|
18
|
-
|
19
|
-
def [](key)
|
20
|
-
@target.public_send(key)
|
21
|
-
end
|
22
|
-
|
23
|
-
def []=(key, value)
|
24
|
-
@target.public_send("#{key}=", value)
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|