inheritance_hash 0.0.1

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: 7e4e36238a2b52dcf48c009726068a39130cdc44
4
+ data.tar.gz: 96a5381e76b43802c28f0ba9a5be99c6988aa3fb
5
+ SHA512:
6
+ metadata.gz: 50112cd991b77a460358d0cec40b70b0307fa8ad0bfb035c1a78d7422a90185cf7d741eab2d562758a28b6feb9185b46a866416aa91aa05f42ccf0bce64d1ceb
7
+ data.tar.gz: 883855b64888a9e1ab2b5950b7d01627f102ed2c4f1cde19ee7c6c8f07007bf6a8bb8683f6edb7d1cab42833a7bb2ef5aac11abf140a198980126d3fd530bc0d
data/.gitignore ADDED
@@ -0,0 +1,17 @@
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
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in InheritanceHash.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Frank Hall
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,35 @@
1
+ # InheritanceHash
2
+
3
+ A hash that can inherit entries from other normal hashes or other inheritance hashes.
4
+ Originally created for class level attributes, InheritanceHash is designed for maintaining hashes in an inheritable fashion such that changes in the parent can be reflected in the children but not vice versa.
5
+
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'inheritance_hash'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install inheritance_hash
20
+
21
+ ## Usage
22
+
23
+ Use it just like a normal hash only call InheritanceHash.new
24
+
25
+ To inherit values from another hash use: ihash.inherit_from(other_hash)
26
+
27
+ To prevent inheriting a specific value use: ihash.dont_inherit(key)
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rdoc/task'
4
+
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => :spec
8
+ task :test => :spec
9
+
10
+ RDoc::Task.new do |rdoc|
11
+ rdoc.rdoc_dir = 'doc'
12
+ rdoc.main = 'README.md'
13
+ rdoc.rdoc_files.include 'README.md', "lib/**/*\.rb"
14
+
15
+ rdoc.options << '--line-numbers'
16
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'inheritance_hash/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'inheritance_hash'
8
+ spec.version = InheritanceHash::VERSION
9
+ spec.authors = ['Frank Hall']
10
+ spec.email = ['ChapterHouse.Dune@gmail.com']
11
+ spec.description = %q{A hash that can inherit entries from other normal hashes or other inheritance hashes.}
12
+ spec.summary = %q{Originally created for class level attributes, InheritanceHash is designed for maintaining hashes in an inheritable fashion such that changes in the parent can be reflected in the children but not vice versa.}
13
+ spec.homepage = 'http://chapterhouse.github.io/inheritance_hash'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
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.add_development_dependency 'bundler', '~> 1.3'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_development_dependency 'rdoc'
25
+
26
+ end
@@ -0,0 +1,4 @@
1
+ class InheritanceHash < Hash
2
+ # 0.0.1
3
+ VERSION = "0.0.1"
4
+ end
@@ -0,0 +1,240 @@
1
+ require 'inheritance_hash/version'
2
+
3
+ class InheritanceHash < Hash
4
+
5
+ #:nodoc:
6
+ alias :__has_key? :has_key?
7
+ private(:__has_key?)
8
+
9
+ instance_methods.each do |m|
10
+ undef_method(m) unless m =~ /(^__|^nil\?|^send$|^object_id$|^tap$|^class$)/
11
+ end
12
+
13
+ #:doc:
14
+ def self.[](*args) #:nodoc:
15
+ ihash = InheritanceHash.new
16
+ if args.size == 1
17
+ if args.first.is_a?(Array)
18
+ args.first.each { |item| ihash[item.first] = item.last }
19
+ else
20
+ if args.first.respond_to?(:to_h)
21
+ args.first.to_h.each { |key, value| ihash[key] = value }
22
+ else
23
+ raise ArgumentError.new('odd number of arguments for Hash')
24
+ end
25
+ end
26
+ elsif args.size.even?
27
+ args.each_slice(2) { |key, value| ihash[key] = value }
28
+ else
29
+ raise ArgumentError.new('odd number of arguments for Hash')
30
+ end
31
+ ihash
32
+ end
33
+
34
+ def initialize(*args) #:nodoc:
35
+ super
36
+ end
37
+
38
+ def [](key) #:nodoc:
39
+ if __has_key?(key)
40
+ super
41
+ elsif inheritable?(key) && !deleted?(key) && up_chain.has_key?(key)
42
+ up_chain[key]
43
+ else
44
+ default(key)
45
+ end
46
+ end
47
+
48
+ def []=(key, value) #:nodoc:
49
+ key_set(key)
50
+ super
51
+ end
52
+
53
+ def assoc(obj) #:nodoc:
54
+ if __has_key?(object)
55
+ super
56
+ else
57
+ up_chain.assoc(obj)
58
+ end
59
+ end
60
+
61
+ def clear #:nodoc:
62
+ deleted_keys += (up_chain.keys - noninheritable)
63
+ deleted_keys.uniq!
64
+ super
65
+ end
66
+
67
+ # This is not yet implemented as it will be neccessary to prevent children from causing
68
+ # side effects in other children by affecting the parent's comparison method.
69
+ def compare_by_identity
70
+ raise NotImplementedError
71
+ end
72
+
73
+ def delete(key) #:nodoc:
74
+ if noninheritable?(key)
75
+ super
76
+ else
77
+ if deleted?(key)
78
+ block_given? ? yield(key) : default
79
+ else
80
+ if __has_key?(key)
81
+ super
82
+ elsif up_chain.has_key?(key)
83
+ delete_key(key)
84
+ up_chain[key]
85
+ else
86
+ block_given? ? yield(key) : default
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+
93
+ def delete_if #:nodoc:
94
+ if block_given?
95
+ each { |key, value| delete(key) if yield(key, value) }
96
+ self
97
+ else
98
+ raise NotImplementedError, 'External iterator not yet supported'
99
+ end
100
+ end
101
+
102
+ # Define a key as uninheritable.
103
+ # If the key is not defined in this hash it will not check the parent.
104
+ # The key does not need to currently exist in the hash.
105
+ def dont_inherit(key)
106
+ noninheritable << key
107
+ noninheritable.uniq!
108
+ end
109
+
110
+ def fetch(*args) #:nodoc:
111
+ if args.length < 1 || args.length > 2
112
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 1..2)"
113
+ elsif args.length == 2 && block_given?
114
+ warn('warning: block supersedes default value argument')
115
+ end
116
+
117
+ key = args.first
118
+
119
+ if __has_key?(key)
120
+ super
121
+ elsif inheritable?(key) && !deleted?(key) && up_chain.has_key?(key)
122
+ up_chain.fetch(key)
123
+ else
124
+ if block_given?
125
+ yield(key)
126
+ elsif args.length == 2
127
+ args[1]
128
+ else
129
+ raise KeyError, "key not found #{args.first.inspect}"
130
+ end
131
+ end
132
+ end
133
+
134
+ def has_key?(key) #:nodoc:
135
+ super || up_chain.has_key?(key)
136
+ end
137
+
138
+
139
+ # Mark a key as inheritable (the default).
140
+ # If the key was previously marked as uninheritable, this will remove that setting.
141
+ # The key does not need to currently exist in the hash.
142
+ def inherit(key)
143
+ noninheritable.delete(key)
144
+ end
145
+
146
+ # Set the hash to inherit from. Usually this would be another InheritanceHash but can be a normal Hash.
147
+ # Note: Using a normal Hash will not automatically undelete keys in the child InheritanceHash if they are reset in the parent Hash.
148
+ def inherit_from(hash)
149
+ unless hash == self
150
+ up_chain.unchain_from(self) if up_chain.respond_to?(:unchain_from, true)
151
+ self.up_chain = hash
152
+ up_chain.down_chain_to(self) if up_chain.respond_to?(:down_chain_to, true)
153
+ end
154
+ end
155
+
156
+ def keep_if #:nodoc:
157
+ if block_given?
158
+ each { |key, value| delete(key) unless yield(key, value) }
159
+ self
160
+ else
161
+ raise NotImplementedError, 'External iterator not yet supported'
162
+ end
163
+ end
164
+
165
+ def respond_to?(*args) #:nodoc:
166
+ [:inherit, :dont_inherit, :inherit_from].include?(args.first) || args[1] && [:down_chain_to, :unchain_from].include?(args.first) || {}.respond_to?(*args)
167
+ end
168
+
169
+ def to_h #:nodoc:
170
+ up_chain.to_h.select { |key, _| inheritable?(key) }.merge(super).select { |key, _| !deleted?(key) }
171
+ end
172
+
173
+
174
+ protected
175
+
176
+ def down_chain_to(hash) #:nodoc:
177
+ unless hash == self
178
+ down_chains << hash
179
+ down_chains.uniq!
180
+ end
181
+ end
182
+
183
+ def key_set(key) #:nodoc:
184
+ if inheritable?(key)
185
+ deleted_keys.delete(key)
186
+ #deleted(key).delete
187
+ down_chains.each { |down_chain| down_chain.key_set(key) }
188
+ end
189
+ end
190
+
191
+ def unchain_from(hash) #:nodoc:
192
+ down_chains.delete(hash)
193
+ end
194
+
195
+ private
196
+
197
+
198
+ attr_writer :up_chain, :down_chains
199
+
200
+ def deleted?(key)
201
+ deleted_keys.include?(key)
202
+ #(deleted.keys + deleted_keys).include?(key)
203
+ end
204
+
205
+ def delete_key(key)
206
+ deleted_keys << key
207
+ deleted_keys.uniq!
208
+ end
209
+
210
+ # Inherited values we are pretending to have 'deleted' but may be restored if the up_chain sets another value.
211
+ def deleted_keys
212
+ @deleted_keys ||= []
213
+ end
214
+
215
+ def up_chain
216
+ @up_chain ||= {}
217
+ end
218
+
219
+ def inheritable?(key)
220
+ !noninheritable?(key)
221
+ end
222
+
223
+ def noninheritable
224
+ @noninheritable ||= []
225
+ end
226
+
227
+ def noninheritable?(key)
228
+ noninheritable.include?(key)
229
+ end
230
+
231
+ def method_missing(method_name, *args, &block)
232
+ to_h.send(*(args.unshift(method_name)), &block)
233
+ end
234
+
235
+ def down_chains
236
+ @down_chains ||= []
237
+ end
238
+
239
+ end
240
+
@@ -0,0 +1,163 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe InheritanceHash do
5
+
6
+ let(:parent) { InheritanceHash[:a, 1, :b, 2, :c, 3, :z, 10] }
7
+ let(:child) { InheritanceHash[:d, 4, :e, 5, :f, 6, :z, 11].tap { |x| x.inherit_from(parent)} }
8
+ let(:child2) { InheritanceHash[:g, 7, :h, 8, :i, 9, :z, 12].tap { |x| x.inherit_from(parent)} }
9
+
10
+ context '.[]' do
11
+
12
+ it 'accepts a list of key value pairs' do
13
+ expect(InheritanceHash[:a, 1, :b, 2]).to eql(Hash[:a, 1, :b, 2])
14
+ end
15
+
16
+ it 'accepts an object convertible to a hash' do
17
+ hash = {:a => 1, :b => 2}
18
+ expect(InheritanceHash[hash]).to eql(Hash[hash])
19
+ end
20
+
21
+ it 'accepts an array of key value pairs' do
22
+ array = [[:a, 1], [:b, 2]]
23
+ expect(InheritanceHash[array]).to eql(Hash[array])
24
+ end
25
+
26
+ it 'throws an error if given a list with an odd number of arguments' do
27
+ expect { InheritanceHash[1, 2, 3] }.to raise_error(ArgumentError)
28
+ end
29
+
30
+ end
31
+
32
+ context '[]' do
33
+
34
+ it 'acts like a Hash but returns values from within itself or the parent' do
35
+ expect(parent[:a]).to equal(1)
36
+ expect(child[:a]).to equal(1)
37
+ expect(child2[:a]).to equal(1)
38
+ end
39
+
40
+ it 'returns its own values over that of the parent\'s' do
41
+ expect(child[:z]).to equal(11)
42
+ expect(child2[:z]).to equal(12)
43
+ end
44
+
45
+ it 'does not return values from siblings' do
46
+ expect(child[:d]).to_not be_nil
47
+ expect(child2[:d]).to be_nil
48
+ end
49
+
50
+
51
+ end
52
+
53
+ context '[]=' do
54
+
55
+ it 'propagates the values to children' do
56
+ parent[:Q] = 101
57
+ expect(child[:Q]).to equal(101)
58
+ end
59
+
60
+ it 'does not propagate the values to parents or siblings' do
61
+ child[:Q] = 101
62
+ expect(parent[:Q]).to be_nil
63
+ expect(child2[:Q]).to be_nil
64
+ end
65
+
66
+ it 'does not change existing child values' do
67
+ parent[:d] = 101
68
+ expect(child[:d]).to equal(4)
69
+ end
70
+
71
+ it 'propagates the value to the children if the child previously deleted the key' do
72
+ child.delete(:a)
73
+ expect(parent[:a]).to_not be_nil
74
+ expect(child[:a]).to be_nil
75
+ parent[:a] = 101
76
+ expect(child[:a]).to equal(101)
77
+ end
78
+
79
+ end
80
+
81
+ context 'delete' do
82
+
83
+ it 'deletes values from children if they are being inherited' do
84
+ parent.delete(:a)
85
+ expect(parent[:a]).to be_nil
86
+ expect(child[:a]).to be_nil
87
+ end
88
+
89
+ it 'does not delete values from parents' do
90
+ child.delete(:a)
91
+ expect(child[:a]).to be_nil
92
+ expect(parent[:a]).to equal(1)
93
+ end
94
+
95
+ it 'does not delete values from children if they have a local value of the same key' do
96
+ child[:a] = 101
97
+ parent.delete(:a)
98
+ expect(parent[:a]).to be_nil
99
+ expect(child[:a]).to equal(101)
100
+ end
101
+
102
+ it 'falls back to the inherited value if local value is deleted' do
103
+ child[:a] = 101
104
+ child.delete(:a)
105
+ expect(child[:a]).to equal(parent[:a])
106
+ end
107
+
108
+ end
109
+
110
+ context 'dont_inherit' do
111
+
112
+ it 'prevents a value from being inherited from a parent' do
113
+ expect(child[:a]).to_not be_nil
114
+ child.dont_inherit(:a)
115
+ expect(child[:a]).to be_nil
116
+ expect(parent[:a]).to_not be_nil
117
+ parent[:a] = 101
118
+ expect(child[:a]).to be_nil
119
+ end
120
+
121
+ end
122
+
123
+
124
+ context 'fetch' do
125
+
126
+ it 'acts like a Hash but returns values from within itself or the parent' do
127
+ expect(parent.fetch(:a)).to equal(1)
128
+ expect(child.fetch(:a)).to equal(1)
129
+ expect(child2.fetch(:a)).to equal(1)
130
+ end
131
+
132
+ it 'returns its own values over that of the parent\'s' do
133
+ expect(child.fetch(:z)).to equal(11)
134
+ expect(child2.fetch(:z)).to equal(12)
135
+ end
136
+
137
+ it 'does not return values from siblings' do
138
+ expect(child.fetch(:d)).to_not be_nil
139
+ expect { child2.fetch(:d) }.to raise_error(KeyError)
140
+ end
141
+
142
+
143
+ end
144
+
145
+ context 'inherit' do
146
+
147
+ it 'reinherits a value that previously was disinherited' do
148
+ child.dont_inherit(:a)
149
+ expect(child[:a]).to be_nil
150
+ child.inherit(:a)
151
+ expect(child[:a]).to equal(parent[:a])
152
+ end
153
+
154
+ end
155
+
156
+ end
157
+
158
+
159
+
160
+
161
+
162
+
163
+
@@ -0,0 +1,20 @@
1
+ require 'bundler/setup'
2
+ require 'rspec/autorun'
3
+ require 'inheritance_hash'
4
+
5
+ # Support the errors_on check without rails loaded
6
+ #module ::ActiveModel::Validations
7
+ # def errors_on(attribute)
8
+ # self.valid?
9
+ # [self.errors[attribute]].flatten.compact
10
+ # end
11
+ # alias :error_on :errors_on
12
+ #end
13
+
14
+ RSpec.configure do |config|
15
+ config.order = "random"
16
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
17
+ end
18
+
19
+
20
+
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inheritance_hash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Frank Hall
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-18 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.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
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: rdoc
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A hash that can inherit entries from other normal hashes or other inheritance
70
+ hashes.
71
+ email:
72
+ - ChapterHouse.Dune@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - inheritance_hash.gemspec
83
+ - lib/inheritance_hash.rb
84
+ - lib/inheritance_hash/version.rb
85
+ - spec/lib/inheritance_hash_spec.rb
86
+ - spec/spec_helper.rb
87
+ homepage: http://chapterhouse.github.io/inheritance_hash
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.0.3
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Originally created for class level attributes, InheritanceHash is designed
111
+ for maintaining hashes in an inheritable fashion such that changes in the parent
112
+ can be reflected in the children but not vice versa.
113
+ test_files:
114
+ - spec/lib/inheritance_hash_spec.rb
115
+ - spec/spec_helper.rb