accessible 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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