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 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
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_STORE
19
+ .idea/
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
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ cache: bundler
3
+
4
+ rvm:
5
+ - 2.0.0
6
+ - 2.1.0
7
+ - 2.2.1
8
+
9
+ script: 'bundle exec rake test'
10
+
11
+ notifications:
12
+ email:
13
+ recipients:
14
+ - sclarkdev@gmail.com
15
+ on_failure: change
16
+ on_success: never
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in accessible.gemspec
4
+ gemspec
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
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'coveralls/rake/task'
3
+ require 'rake/testtask'
4
+
5
+ Coveralls::RakeTask.new
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.pattern = "test/*_test.rb"
9
+ end
10
+
11
+ task :default => [:test]
@@ -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,3 @@
1
+ numbers:
2
+ integers:
3
+ :one: <%= 1 / 1 %>
@@ -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
@@ -0,0 +1,3 @@
1
+ module Accessible
2
+ VERSION = "0.1.0"
3
+ 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
@@ -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