accessible 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +19 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +11 -0
- data/accessible.gemspec +28 -0
- data/config/empty.yml +0 -0
- data/config/more_configs/my_config.yaml +1 -0
- data/config/my_config.yml +1 -0
- data/config/sample.yml +3 -0
- data/lib/accessible/accessorizers.rb +41 -0
- data/lib/accessible/data_loader.rb +31 -0
- data/lib/accessible/hash_methods.rb +24 -0
- data/lib/accessible/version.rb +3 -0
- data/lib/accessible.rb +46 -0
- data/test/accessible_test.rb +188 -0
- data/test/accessorizers_test.rb +203 -0
- data/test/data_loader_test.rb +57 -0
- data/test/hash_methods_test.rb +61 -0
- data/test/test_helper.rb +12 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7ba9fa465e93322e6b9f7c2c37714e3c7cb96932
|
4
|
+
data.tar.gz: ad0299925c1b42408e90d184f1bbf49b531fd841
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f81549a605ef57707f46b34e1a4f1cd212dc6defef01c91773045e3975c4922959a73f8f97327c2fa235c011053ec21bd4f4850b7dbc1030f85e93627bec3503
|
7
|
+
data.tar.gz: 8fa230b3242fa3ceb3a220d0d6cfa14ccd04751a35ce6a4f3dc4ed54f592e28c9dd8f66a524d85d5874c2c612f1dabac041e2dfdcafe4508312fa3ce88d6cc94
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Accessible
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.0
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Scott Clark
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Accessible
|
2
|
+
[](https://travis-ci.org/saclark/accessible) [](https://coveralls.io/r/saclark/accessible)
|
3
|
+
|
4
|
+
A simple and flexible means of configuration for Ruby applications.
|
5
|
+
|
6
|
+
# Set up a configuration class
|
7
|
+
Create a class in your project, include the `Accessible` module, and `load` a data source. You may then also `merge` in any number of additional data sources.
|
8
|
+
```ruby
|
9
|
+
class MyConfig
|
10
|
+
include Accessible
|
11
|
+
|
12
|
+
load 'config/defaults.yml'
|
13
|
+
merge 'config/environments/dev.yml'
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
> Note: The `load` method will clear out any previously loaded data and replace it with the new data, whereas `merge` will merge in new data with previously loaded data (i.e. matching keys will be overridden and new keys will be added).
|
18
|
+
|
19
|
+
Both `load` and `merge` can take any of the following as a data source:
|
20
|
+
- A filepath to a yaml file (i.e. `'config/default.yml'`)
|
21
|
+
- A symbol representing the name of a yaml file found in `config/` (i.e. `:default`)
|
22
|
+
- A raw hash of data (i.e. `{ :base_url => 'http://www.example.com' }`)
|
23
|
+
|
24
|
+
Both methods also accept an optional second parameter: the name of a specific key within the data source, whose data is the only data that should be loaded from the source. This is provided in case you prefer to maintain a single configuration file with default and environment specific data separated out under different keys.
|
25
|
+
|
26
|
+
# Access your configuration data
|
27
|
+
There are two ways to access your configuration data.
|
28
|
+
|
29
|
+
Let's imagine the following data is loaded:
|
30
|
+
```ruby
|
31
|
+
{
|
32
|
+
:environments => {
|
33
|
+
:dev => {
|
34
|
+
:users => [
|
35
|
+
{ :name => 'user0' },
|
36
|
+
{ :name => 'user1' },
|
37
|
+
{ :name => 'user2' }
|
38
|
+
]
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
```
|
43
|
+
|
44
|
+
__Methods__
|
45
|
+
|
46
|
+
You can get and set data by calling methods on your data set that match the name of the keys. If you try to get or set a key does not exist, an error will be thrown.
|
47
|
+
```ruby
|
48
|
+
MyConfig.environments.dev.users[1].name # => 'user1'
|
49
|
+
|
50
|
+
MyConfig.environments.dev.users[1].name = 'new_user1'
|
51
|
+
MyConfig.environments.dev.users[1].name # => 'new_user1'
|
52
|
+
|
53
|
+
MyConfig.does_not_exist # => NoMethodError
|
54
|
+
MyConfig.does_not_exist = 'foo' # => NoMethodError
|
55
|
+
```
|
56
|
+
|
57
|
+
__Brackets__
|
58
|
+
|
59
|
+
You may also use the familiar `:[]` and `:[]=` methods, which behave the same as they do on any hash. `:[]` will return `nil` if the key does not exist, `:[]=` will create and set the key-value pair if the key does not exist. This makes `:[]` useful for providing default values.
|
60
|
+
```ruby
|
61
|
+
MyConfig[:environments][:dev][:users][1][:name] # => 'user1'
|
62
|
+
|
63
|
+
MyConfig[:environments][:dev][:users][1][:name] = 'new_user1'
|
64
|
+
MyConfig[:environments][:dev][:users][1][:name] # => 'new_user1'
|
65
|
+
|
66
|
+
MyConfig[:does_not_exist] || 'default' # => 'default'
|
67
|
+
MyConfig[:new_key] = 'foo' # => `:new_key => 'foo'` pair is created
|
68
|
+
```
|
69
|
+
|
70
|
+
---
|
71
|
+
|
72
|
+
Check out the unit tests for the nitty-gritty of how things should work.
|
data/Rakefile
ADDED
data/accessible.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'accessible/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'accessible'
|
8
|
+
spec.version = Accessible::VERSION
|
9
|
+
spec.authors = ['Scott Clark']
|
10
|
+
spec.email = ['sclarkdev@gmail.com']
|
11
|
+
spec.summary = 'Simple and flexible ruby app configuration.'
|
12
|
+
spec.description = 'A simple and flexible means of configuration for Ruby applications.'
|
13
|
+
spec.homepage = 'https://github.com/saclark/accessible'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.required_ruby_version = '>= 2.0.0'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.1'
|
25
|
+
spec.add_development_dependency 'simplecov'
|
26
|
+
spec.add_development_dependency 'coveralls', '~> 0.7'
|
27
|
+
spec.add_development_dependency 'minitest-reporters'
|
28
|
+
end
|
data/config/empty.yml
ADDED
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
data: 'data from config/more_configs/my_config.yaml'
|
@@ -0,0 +1 @@
|
|
1
|
+
data: 'data from config/my_config.yml'
|
data/config/sample.yml
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Accessible
|
2
|
+
module Accessorizers
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def accessorize_data(data)
|
6
|
+
HashMethods.each_hash(data) do |hash|
|
7
|
+
hash.each do |key, value|
|
8
|
+
define_accessors(hash, key)
|
9
|
+
accessorize_data(value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def accessorize_obj(obj)
|
15
|
+
if !obj.respond_to?(:to_h)
|
16
|
+
raise(NotImplementedError, "Expected `#{obj}` to respond to `:to_h`")
|
17
|
+
end
|
18
|
+
|
19
|
+
obj.to_h.keys.each do |key|
|
20
|
+
define_accessors(obj, key)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def define_accessors(obj, key)
|
25
|
+
define_getter(obj, key)
|
26
|
+
define_setter(obj, key)
|
27
|
+
end
|
28
|
+
|
29
|
+
def define_getter(obj, key)
|
30
|
+
obj.define_singleton_method(key) do
|
31
|
+
obj.to_h.fetch(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_setter(obj, key)
|
36
|
+
obj.define_singleton_method("#{key}=") do |new_value|
|
37
|
+
obj.to_h[key] = Accessible::Accessorizers.accessorize_data(new_value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
module Accessible
|
5
|
+
module DataLoader
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def evaluate_erb(text)
|
9
|
+
ERB.new(text).result
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_yaml_erb(yaml_file)
|
13
|
+
contents = File.read(yaml_file)
|
14
|
+
evaluated_contents = evaluate_erb(contents)
|
15
|
+
YAML.load(evaluated_contents) || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_source(source)
|
19
|
+
case source
|
20
|
+
when Hash
|
21
|
+
source
|
22
|
+
when Symbol
|
23
|
+
load_yaml_erb("config/#{source}.yml")
|
24
|
+
when String
|
25
|
+
load_yaml_erb(source)
|
26
|
+
else
|
27
|
+
raise("Invalid data source: #{source}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Accessible
|
2
|
+
module HashMethods
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def each_hash(data, &block)
|
6
|
+
case data
|
7
|
+
when Hash
|
8
|
+
block.call(data)
|
9
|
+
when Array
|
10
|
+
data.each { |elem| each_hash(elem, &block) }
|
11
|
+
end
|
12
|
+
|
13
|
+
data
|
14
|
+
end
|
15
|
+
|
16
|
+
def deep_merge(orig_data, new_data)
|
17
|
+
merger = proc do |key, v1, v2|
|
18
|
+
Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
|
19
|
+
end
|
20
|
+
|
21
|
+
orig_data.merge(new_data, &merger)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/accessible.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'accessible/accessorizers'
|
2
|
+
require 'accessible/data_loader'
|
3
|
+
require 'accessible/hash_methods'
|
4
|
+
require 'accessible/version'
|
5
|
+
|
6
|
+
module Accessible
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def to_h
|
13
|
+
@data ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def load(source, namespace = nil)
|
17
|
+
to_h.clear
|
18
|
+
merge(source, namespace)
|
19
|
+
end
|
20
|
+
|
21
|
+
def merge(source, namespace = nil)
|
22
|
+
new_data = DataLoader.load_source(source)
|
23
|
+
|
24
|
+
if namespace
|
25
|
+
@data = HashMethods.deep_merge(to_h, new_data.fetch(namespace))
|
26
|
+
else
|
27
|
+
@data = HashMethods.deep_merge(to_h, new_data)
|
28
|
+
end
|
29
|
+
|
30
|
+
Accessorizers.accessorize_obj(self)
|
31
|
+
Accessorizers.accessorize_data(to_h)
|
32
|
+
|
33
|
+
to_h
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](key)
|
37
|
+
to_h[key]
|
38
|
+
end
|
39
|
+
|
40
|
+
def []=(key, new_value)
|
41
|
+
Accessorizers.define_accessors(to_h, key)
|
42
|
+
Accessorizers.define_accessors(self, key)
|
43
|
+
to_h[key] = Accessible::Accessorizers.accessorize_data(new_value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class AccessibleTest < Minitest::Spec
|
4
|
+
describe Accessible do
|
5
|
+
let(:config) { Class.new { include Accessible } }
|
6
|
+
|
7
|
+
describe '#to_h' do
|
8
|
+
it 'should create `@data` if it does not exist' do
|
9
|
+
config.instance_variable_defined?(:@data).must_equal(false)
|
10
|
+
config.to_h
|
11
|
+
config.instance_variable_defined?(:@data).must_equal(true)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should assign an empty hash to `@data` if it does not exist' do
|
15
|
+
config.instance_variable_defined?(:@data).must_equal(false)
|
16
|
+
config.to_h
|
17
|
+
config.instance_variable_get(:@data).must_equal({})
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should return a Hash instance' do
|
21
|
+
config.to_h.must_be_instance_of(Hash)
|
22
|
+
config.load({ :adding => ['data'] })
|
23
|
+
config.to_h.must_be_instance_of(Hash)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should return `@data`' do
|
27
|
+
config.to_h.must_equal(config.instance_variable_get(:@data))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#load' do
|
32
|
+
it 'should reassign `@data` to the given data' do
|
33
|
+
config.load({ :a => 'a' })
|
34
|
+
config.to_h.must_equal({ :a => 'a' })
|
35
|
+
config.load({ :b => 'b' })
|
36
|
+
config.to_h.must_equal({ :b => 'b' })
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should assign all data if no namespace given' do
|
40
|
+
config.load({ :a => 'a', :b => 'b' })
|
41
|
+
config.to_h.must_equal({ :a => 'a', :b => 'b' })
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should assign namespaced data if namespace given' do
|
45
|
+
config.load({ :a => 'a', :foo => { :b => 'b'} }, :foo)
|
46
|
+
config.to_h.must_equal({ :b => 'b' })
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should raise a KeyError if the given namespace is not found' do
|
50
|
+
proc do
|
51
|
+
config.load({ :a => 'a', :foo => { :b => 'b'} }, :bar)
|
52
|
+
end.must_raise(KeyError)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#merge' do
|
57
|
+
it 'should accept a hash and load it' do
|
58
|
+
config.merge({ :a => 'a' })
|
59
|
+
config.to_h.must_equal({ :a => 'a' })
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should accept a symbol and load the corresponding config file' do
|
63
|
+
config.merge(:my_config)
|
64
|
+
config.to_h.must_equal({ 'data' => 'data from config/my_config.yml' })
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should accept a file path and load the file' do
|
68
|
+
config.merge('config/more_configs/my_config.yaml')
|
69
|
+
config.to_h.must_equal({ 'data' => 'data from config/more_configs/my_config.yaml' })
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should raise an error when not given a hash, symbol, or string' do
|
73
|
+
begin
|
74
|
+
config.merge(100)
|
75
|
+
rescue RuntimeError => e
|
76
|
+
e.message.must_equal("Invalid data source: 100")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should not require an initial source to have been `#load`ed' do
|
81
|
+
config.merge({ :a => 'a' })
|
82
|
+
config.to_h.must_equal({ :a => 'a' })
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should mutate `@data`' do
|
86
|
+
config.load({ :foo => 'original' })
|
87
|
+
config.merge({ :foo => 'new' })
|
88
|
+
config.to_h.must_equal({ :foo => 'new' })
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should merge all data if no namespace given' do
|
92
|
+
config.load({ :a => 'a'})
|
93
|
+
config.merge({ :a => 'a', :b => 'b' })
|
94
|
+
config.to_h.must_equal({ :a => 'a', :b => 'b' })
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should merge namespaced data if namespace given' do
|
98
|
+
config.load({ :a => 'a' })
|
99
|
+
config.merge({ :foo => { :a => 'new a' }, :b => 'b' }, :foo)
|
100
|
+
config.to_h.must_equal({ :a => 'new a' })
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should raise a KeyError if the given namespace is not found' do
|
104
|
+
proc do
|
105
|
+
config.merge({ :a => 'a', :foo => { :b => 'b'} }, :bar)
|
106
|
+
end.must_raise(KeyError)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should perform a deep merge' do
|
110
|
+
h1 = { :a => true, :b => { :c => [1, 2, 3] } }
|
111
|
+
h2 = { :a => false, :b => { :x => [3, 4, 5] } }
|
112
|
+
|
113
|
+
config.load(h1)
|
114
|
+
config.merge(h2)
|
115
|
+
config.to_h.must_equal({
|
116
|
+
:a => false,
|
117
|
+
:b => {
|
118
|
+
:c => [1, 2, 3],
|
119
|
+
:x => [3, 4, 5]
|
120
|
+
}
|
121
|
+
})
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should define acessors on `@data`' do
|
125
|
+
config.merge({ :a => 'a' })
|
126
|
+
config.to_h.singleton_methods.must_include(:a)
|
127
|
+
config.to_h.singleton_methods.must_include(:a=)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should define accessors on the class' do
|
131
|
+
config.merge({ :a => 'a' })
|
132
|
+
config.singleton_methods.must_include(:a)
|
133
|
+
config.singleton_methods.must_include(:a=)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should return a Hash instance' do
|
137
|
+
config.merge({}).must_be_instance_of(Hash)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should return the result of calling #to_h after the merge' do
|
141
|
+
config.load({ :a => 'a' })
|
142
|
+
merge_result = config.merge({ :b => 'b' })
|
143
|
+
merge_result.must_equal(config.to_h)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe '#[]' do
|
148
|
+
it 'should delegate to `@data`' do
|
149
|
+
config.load({ :a => 'a' })
|
150
|
+
config[:a].must_equal(config.instance_variable_get(:@data)[:a])
|
151
|
+
config[:b].must_equal(config.instance_variable_get(:@data)[:b])
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe '#[]=' do
|
156
|
+
it 'should add the key value pair to `@data`' do
|
157
|
+
config[:a] = 'a'
|
158
|
+
config.instance_variable_get(:@data)[:a].must_equal('a')
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should define acessors on `@data` for the given key' do
|
162
|
+
config[:a] = 'a'
|
163
|
+
config.instance_variable_get(:@data).singleton_methods.must_include(:a)
|
164
|
+
config.instance_variable_get(:@data).singleton_methods.must_include(:a=)
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should define accessors on the class for the given key' do
|
168
|
+
config[:a] = 'a'
|
169
|
+
config.singleton_methods.must_include(:a)
|
170
|
+
config.singleton_methods.must_include(:a=)
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should define accessors on set values' do
|
174
|
+
config[:a] = { :foo => 'foo value' }
|
175
|
+
config.a.singleton_methods.must_include(:foo)
|
176
|
+
config.a.foo.must_equal('foo value')
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should delegate to `@data`' do
|
180
|
+
(config[:a] = 'a').must_equal(config.instance_variable_get(:@data)[:a] = 'a')
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should return the set value' do
|
184
|
+
config.send(:[]=, :a, 'a').must_equal('a')
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class AccessorizersTest < Minitest::Spec
|
4
|
+
describe Accessible::Accessorizers do
|
5
|
+
let(:obj) do
|
6
|
+
Class.new do
|
7
|
+
def self.to_h
|
8
|
+
@data
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.to_h=(data)
|
12
|
+
@data = data
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#accessorize_data' do
|
18
|
+
before do
|
19
|
+
@hash = {
|
20
|
+
:complex => {
|
21
|
+
:nested => {
|
22
|
+
:array => [
|
23
|
+
{ :hash_0 => 'hash_0 value' },
|
24
|
+
{ :hash_1 => [ { :inner => 'inner value' } ] }
|
25
|
+
]
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should recursively define getters on the hash for every key' do
|
32
|
+
Accessible::Accessorizers.accessorize_data(@hash)
|
33
|
+
@hash.complex.must_equal(@hash[:complex])
|
34
|
+
@hash.complex.nested.array.must_equal(@hash[:complex][:nested][:array])
|
35
|
+
@hash.complex.nested.array[0].hash_0.must_equal('hash_0 value')
|
36
|
+
@hash.complex.nested.array[1].hash_1[0].inner.must_equal('inner value')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should recursively define setters on the hash for every key' do
|
40
|
+
Accessible::Accessorizers.accessorize_data(@hash)
|
41
|
+
@hash.complex.nested.array[1].hash_1[0].inner = 'new inner value'
|
42
|
+
@hash.complex.nested.array[1].hash_1[0].inner.must_equal('new inner value')
|
43
|
+
@hash.complex = 'new value'
|
44
|
+
@hash.complex.must_equal('new value')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should distinguish nested keys' do
|
48
|
+
data = {
|
49
|
+
:languages => {
|
50
|
+
:ruby => {
|
51
|
+
:typed => 'dynamic'
|
52
|
+
},
|
53
|
+
:haskell => {
|
54
|
+
:typed => 'static'
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
Accessible::Accessorizers.accessorize_data(data)
|
59
|
+
data.languages.ruby.typed.must_equal('dynamic')
|
60
|
+
data.languages.haskell.typed.must_equal('static')
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should allow non-standard or complex method names' do
|
64
|
+
data = { 'key with spaces' => 'key with spaces value' }
|
65
|
+
Accessible::Accessorizers.accessorize_data(data)
|
66
|
+
data.must_respond_to(:"key with spaces")
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should override conflicting methods' do
|
70
|
+
hash = { :class => 'overwritten' }
|
71
|
+
Accessible::Accessorizers.accessorize_data(hash)
|
72
|
+
hash.class.must_equal('overwritten')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#accessorize_obj' do
|
77
|
+
it 'should define getters on the class for all first tier keys' do
|
78
|
+
obj.to_h = { :a => { :b => 'b' }, :c => 'c' }
|
79
|
+
Accessible::Accessorizers.accessorize_obj(obj)
|
80
|
+
obj.a.must_equal({ :b => 'b' })
|
81
|
+
obj.c.must_equal('c')
|
82
|
+
obj.wont_respond_to(:b)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should define getters equivalent to calling `:fetch` on `@data` with the same key' do
|
86
|
+
obj.to_h = { :foo => 'foo value'}
|
87
|
+
Accessible::Accessorizers.accessorize_obj(obj)
|
88
|
+
(obj.to_h.fetch(:foo)).must_equal(obj.foo)
|
89
|
+
|
90
|
+
obj.to_h.delete(:foo)
|
91
|
+
proc { obj.foo }.must_raise(KeyError)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should define setters on the class for all first tier keys' do
|
95
|
+
obj.to_h = { :a => { :b => 'b' }, :c => 'c' }
|
96
|
+
Accessible::Accessorizers.accessorize_obj(obj)
|
97
|
+
|
98
|
+
obj.a = 'new a value'
|
99
|
+
obj.a.must_equal('new a value')
|
100
|
+
|
101
|
+
obj.c = 'new c value'
|
102
|
+
obj.c.must_equal('new c value')
|
103
|
+
|
104
|
+
obj.wont_respond_to(:b=)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should define accessors on values set after initial load' do
|
108
|
+
obj.to_h = { :foo => 'foo value' }
|
109
|
+
Accessible::Accessorizers.accessorize_obj(obj)
|
110
|
+
obj.foo = { :new_value => 'new value' }
|
111
|
+
|
112
|
+
obj.to_h[:foo].must_respond_to(:new_value)
|
113
|
+
obj.to_h[:foo].must_respond_to(:new_value=)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should define setters equivalent using `:[]=`' do
|
117
|
+
obj.to_h = { :foo => 'foo value' }
|
118
|
+
Accessible::Accessorizers.accessorize_obj(obj)
|
119
|
+
|
120
|
+
(obj.foo = 'new foo value').must_equal(obj.to_h[:foo] = 'new foo value')
|
121
|
+
|
122
|
+
obj.to_h.delete(:foo)
|
123
|
+
obj.foo = 'restored foo'
|
124
|
+
obj.foo.must_equal('restored foo')
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should raise an error if the object does not respond to `:to_h`' do
|
128
|
+
proc { Accessible::Accessorizers.accessorize_obj(:foo) }.must_raise(NotImplementedError)
|
129
|
+
|
130
|
+
begin
|
131
|
+
Accessible::Accessorizers.accessorize_obj(:foo)
|
132
|
+
rescue NotImplementedError => e
|
133
|
+
e.message.must_equal("Expected `foo` to respond to `:to_h`")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#define_accessors' do
|
139
|
+
it 'should' do
|
140
|
+
skip('pending')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#define_getter' do
|
145
|
+
it 'should define a getter for the given key' do
|
146
|
+
hash = { :foo => 'foo value' }
|
147
|
+
Accessible::Accessorizers.define_getter(hash, :foo)
|
148
|
+
hash.foo.must_equal('foo value')
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should define a getter only on the instance' do
|
152
|
+
hash = { :foo => 'foo value' }
|
153
|
+
Accessible::Accessorizers.define_getter(hash, :foo)
|
154
|
+
{}.wont_respond_to(:foo)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should be the same as calling :fetch on the hash with the same key' do
|
158
|
+
hash = { :foo => 'foo value'}
|
159
|
+
|
160
|
+
Accessible::Accessorizers.define_getter(hash, :foo)
|
161
|
+
(hash.fetch(:foo)).must_equal(hash.foo)
|
162
|
+
|
163
|
+
hash.delete(:foo)
|
164
|
+
proc { hash.foo }.must_raise(KeyError)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#define_setter' do
|
169
|
+
it 'should define a setter for the given key' do
|
170
|
+
hash = { :foo => 'foo value' }
|
171
|
+
Accessible::Accessorizers.define_setter(hash, :foo)
|
172
|
+
hash.foo = 'new foo value'
|
173
|
+
hash[:foo].must_equal('new foo value')
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should define a setter only on the instance' do
|
177
|
+
hash = { :foo => 'foo value' }
|
178
|
+
Accessible::Accessorizers.define_setter(hash, :foo)
|
179
|
+
{}.wont_respond_to(:foo=)
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should define accessors on set values' do
|
183
|
+
hash = { :foo => 'foo value' }
|
184
|
+
Accessible::Accessorizers.define_setter(hash, :foo)
|
185
|
+
hash.foo = { :new_value => 'new value' }
|
186
|
+
|
187
|
+
hash[:foo].must_respond_to(:new_value)
|
188
|
+
hash[:foo].must_respond_to(:new_value=)
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should be the same as calling :[]= with the same key and value' do
|
192
|
+
hash = { :foo => 'foo value'}
|
193
|
+
|
194
|
+
Accessible::Accessorizers.define_setter(hash, :foo)
|
195
|
+
(hash.foo = 'new foo value').must_equal(hash[:foo] = 'new foo value')
|
196
|
+
|
197
|
+
Accessible::Accessorizers.define_setter(hash, :bar)
|
198
|
+
hash.bar = 'bar'
|
199
|
+
hash[:bar].must_equal('bar')
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class DataLoaderTest < Minitest::Spec
|
4
|
+
describe Accessible::DataLoader do
|
5
|
+
before do
|
6
|
+
@file_path = 'config/sample.yml'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#evaluate_erb' do
|
10
|
+
it 'should return the erb evaluated string' do
|
11
|
+
contents = File.read(@file_path)
|
12
|
+
Accessible::DataLoader.evaluate_erb(contents).must_equal("numbers:\n integers:\n :one: 1\n")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#load_yaml_erb' do
|
17
|
+
it 'should return the file contents as a hash' do
|
18
|
+
Accessible::DataLoader.load_yaml_erb(@file_path).must_be_instance_of(Hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return file data as a hash with keys unaltered' do
|
22
|
+
Accessible::DataLoader.load_yaml_erb(@file_path)['numbers']['integers'][:one].wont_be_nil
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should return an empty hash if the file is empty' do
|
26
|
+
Accessible::DataLoader.load_yaml_erb('config/empty.yml').must_equal({})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#load_source' do
|
31
|
+
it 'should accept a hash and return it' do
|
32
|
+
data = Accessible::DataLoader.load_source({ :a => 'a' })
|
33
|
+
data.must_equal({ :a => 'a' })
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should accept a symbol and return the corresponding file data' do
|
37
|
+
data = Accessible::DataLoader.load_source(:my_config)
|
38
|
+
data.must_equal({ 'data' => 'data from config/my_config.yml' })
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should accept a file path and return the file data' do
|
42
|
+
data = Accessible::DataLoader.load_source('config/more_configs/my_config.yaml')
|
43
|
+
data.must_equal({ 'data' => 'data from config/more_configs/my_config.yaml' })
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should raise an error when not given a hash, symbol, or string' do
|
47
|
+
proc { Accessible::DataLoader.load_source(100) }.must_raise(RuntimeError)
|
48
|
+
|
49
|
+
begin
|
50
|
+
Accessible::DataLoader.load_source(100)
|
51
|
+
rescue RuntimeError => e
|
52
|
+
e.message.must_equal("Invalid data source: 100")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class HashMethodsTest < Minitest::Spec
|
4
|
+
describe Accessible::HashMethods do
|
5
|
+
describe '#each_hash' do
|
6
|
+
it 'should execute the block on the given value if it is a hash' do
|
7
|
+
begin
|
8
|
+
Accessible::HashMethods.each_hash({}) do |hash|
|
9
|
+
raise(hash.to_s)
|
10
|
+
end
|
11
|
+
rescue RuntimeError => e
|
12
|
+
e.message.must_equal('{}')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should recursively execute the block on hashes nested in arrays' do
|
17
|
+
begin
|
18
|
+
Accessible::HashMethods.each_hash([{}]) do |hash|
|
19
|
+
raise(hash.to_s)
|
20
|
+
end
|
21
|
+
rescue RuntimeError => e
|
22
|
+
e.message.must_equal('{}')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should not pass non-hash values to the block' do
|
27
|
+
raise_if_executed = proc { raise('block was excuted') }
|
28
|
+
class SubclassedHash < Hash; end
|
29
|
+
Accessible::HashMethods.each_hash('foo', &raise_if_executed)
|
30
|
+
Accessible::HashMethods.each_hash(:foo, &raise_if_executed)
|
31
|
+
Accessible::HashMethods.each_hash(100, &raise_if_executed)
|
32
|
+
Accessible::HashMethods.each_hash(nil, &raise_if_executed)
|
33
|
+
Accessible::HashMethods.each_hash(true, &raise_if_executed)
|
34
|
+
Accessible::HashMethods.each_hash([], &raise_if_executed)
|
35
|
+
Accessible::HashMethods.each_hash(proc{}, &raise_if_executed)
|
36
|
+
Accessible::HashMethods.each_hash(Accessible, &raise_if_executed)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should return the value it was given' do
|
40
|
+
result = Accessible::HashMethods.each_hash({ :a => 'a' }) { :foo }
|
41
|
+
result.must_equal({ :a => 'a' })
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#deep_merge' do
|
46
|
+
it 'should merge neseted hashes' do
|
47
|
+
hash_1 = { :a => false, :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }
|
48
|
+
hash_2 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }
|
49
|
+
expected = { :a => 1, :b => "b", :c => { :c1 => 2, :c2 => "c2", :c3 => { :d1 => "d1", :d2 => "d2" } } }
|
50
|
+
|
51
|
+
Accessible::HashMethods.deep_merge(hash_1, hash_2).must_equal(expected)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#symbolize_keys' do
|
56
|
+
it 'should' do
|
57
|
+
skip('pending')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'coveralls'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/reporters'
|
5
|
+
require 'minitest/spec'
|
6
|
+
|
7
|
+
Minitest::Reporters.use!(Minitest::Reporters::SpecReporter.new)
|
8
|
+
SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
|
9
|
+
SimpleCov.start { add_filter '/test/' }
|
10
|
+
Coveralls.wear!
|
11
|
+
|
12
|
+
require 'accessible'
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: accessible
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Scott Clark
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: coveralls
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-reporters
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: A simple and flexible means of configuration for Ruby applications.
|
84
|
+
email:
|
85
|
+
- sclarkdev@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".coveralls.yml"
|
91
|
+
- ".gitignore"
|
92
|
+
- ".ruby-gemset"
|
93
|
+
- ".ruby-version"
|
94
|
+
- ".travis.yml"
|
95
|
+
- Gemfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- accessible.gemspec
|
100
|
+
- config/empty.yml
|
101
|
+
- config/more_configs/my_config.yaml
|
102
|
+
- config/my_config.yml
|
103
|
+
- config/sample.yml
|
104
|
+
- lib/accessible.rb
|
105
|
+
- lib/accessible/accessorizers.rb
|
106
|
+
- lib/accessible/data_loader.rb
|
107
|
+
- lib/accessible/hash_methods.rb
|
108
|
+
- lib/accessible/version.rb
|
109
|
+
- test/accessible_test.rb
|
110
|
+
- test/accessorizers_test.rb
|
111
|
+
- test/data_loader_test.rb
|
112
|
+
- test/hash_methods_test.rb
|
113
|
+
- test/test_helper.rb
|
114
|
+
homepage: https://github.com/saclark/accessible
|
115
|
+
licenses:
|
116
|
+
- MIT
|
117
|
+
metadata: {}
|
118
|
+
post_install_message:
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 2.0.0
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 2.2.2
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: Simple and flexible ruby app configuration.
|
138
|
+
test_files:
|
139
|
+
- test/accessible_test.rb
|
140
|
+
- test/accessorizers_test.rb
|
141
|
+
- test/data_loader_test.rb
|
142
|
+
- test/hash_methods_test.rb
|
143
|
+
- test/test_helper.rb
|