accessible 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/saclark/accessible.svg)](https://travis-ci.org/saclark/accessible) [![Coverage Status](https://coveralls.io/repos/saclark/accessible/badge.svg)](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
|