inherited-hash 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +19 -0
- data/README.mdown +108 -0
- data/Rakefile +7 -0
- data/inherited-hash.gemspec +23 -0
- data/lib/inherited-hash.rb +61 -0
- data/lib/inherited-hash/global.rb +5 -0
- data/lib/inherited-hash/version.rb +3 -0
- data/spec/inherited-hash_spec.rb +106 -0
- data/spec/spec_helper.rb +4 -0
- metadata +104 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2011 by Ryan Biesemeyer (@yaauie)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.mdown
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
InheritedHash
|
2
|
+
=============
|
3
|
+
|
4
|
+
This module lets you specify a hash that is inherited by subclasses and
|
5
|
+
instances of any class that it extends.
|
6
|
+
|
7
|
+
It's best explained by example:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class Robot
|
11
|
+
extend InheritedHash
|
12
|
+
inherited_hash_accessor :sensor_settings
|
13
|
+
|
14
|
+
sensor_settings= {
|
15
|
+
:temperature => :kelvin,
|
16
|
+
:distance => :metric,
|
17
|
+
:pressure => :atmosphere
|
18
|
+
}
|
19
|
+
# ...
|
20
|
+
end
|
21
|
+
|
22
|
+
class EuropeanRobot < Robot
|
23
|
+
sensor_settings[:temperature] = :centigrade
|
24
|
+
# ...
|
25
|
+
end
|
26
|
+
|
27
|
+
class AmericanRobot < Robot
|
28
|
+
sensor_settings = {
|
29
|
+
:tempertaure => :fahrenheight,
|
30
|
+
:distance => :imperial
|
31
|
+
}
|
32
|
+
# ...
|
33
|
+
end
|
34
|
+
|
35
|
+
confused_robot = AmericanRobot.new
|
36
|
+
confused_robot.sensor_settings[:distance] = :wednesday
|
37
|
+
|
38
|
+
# get the hash for this instance.
|
39
|
+
puts confused_robot.sensor_settings
|
40
|
+
# => {:distance => :wednesday}
|
41
|
+
|
42
|
+
# get the hash built using inheritance.
|
43
|
+
# note how the result in our example contains
|
44
|
+
# elements from each layer of inheritance
|
45
|
+
puts confused_robot.sensor_settings!
|
46
|
+
# => {:distance => :wednesday, :temperature => :fahrenheight, :pressure => :atmosphere}
|
47
|
+
|
48
|
+
```
|
49
|
+
|
50
|
+
Inheritance
|
51
|
+
-----------
|
52
|
+
|
53
|
+
Each element in the inheritance chain stores its own `ConnectedHash`,
|
54
|
+
accessible directly by calling the name you gave it.
|
55
|
+
|
56
|
+
It can also build a composite `Hash` accounting for inheritance with the bang-
|
57
|
+
variant (`foo!` for a hash named `foo`) method, which is built by merging its
|
58
|
+
own hash with the hash it inherits, following the inheritance chain.
|
59
|
+
|
60
|
+
The composite `Hash` is generated every time you request it, so destructive
|
61
|
+
methods like `delete` may not work as intended; they will be destructive to
|
62
|
+
your current generated object, but will not affect the source or composite
|
63
|
+
hashes generated subsequently.
|
64
|
+
|
65
|
+
Finding a Definition
|
66
|
+
--------------------
|
67
|
+
|
68
|
+
Inspection is simple: Call `find_definition_of(key)` on the `ConnectedHash` to
|
69
|
+
find the object (instance, class, or module) whose `ConnectedHash` of the same
|
70
|
+
name defines the value that is inherited in the composite.
|
71
|
+
|
72
|
+
From our example above:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
puts confused_robot.sensor_settings.find_definition_of(:distance)
|
76
|
+
#=> #<AmericanRobot:0x10e6fdb78 @sensor_settings={:distance=>:wednesday}>
|
77
|
+
|
78
|
+
puts confused_robot.sensor_settings.find_definition_of(:tempertaure)
|
79
|
+
#=> AmericanRobot
|
80
|
+
|
81
|
+
puts confused_robot.sensor_settings.find_definition_of(:humidity).inspect
|
82
|
+
#=> nil
|
83
|
+
```
|
84
|
+
|
85
|
+
Global Availability
|
86
|
+
-------------------
|
87
|
+
|
88
|
+
To include this module into *all* Modules and Classes, simply:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
require 'inherited-hash/global'
|
92
|
+
```
|
93
|
+
|
94
|
+
Contributing
|
95
|
+
============
|
96
|
+
|
97
|
+
* Check out the latest code to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
98
|
+
* Check out the [issue tracker](https://github.com/yaauie/inherited-hash/issues) and [pull requests](https://github.com/yaauie/inherited-hash/pulls) to make sure someone already hasn't requested it and/or contributed it
|
99
|
+
* Fork the project
|
100
|
+
* Start a feature/bugfix branch
|
101
|
+
* Commit and push until you are happy with your contribution
|
102
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
103
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
104
|
+
|
105
|
+
License
|
106
|
+
=======
|
107
|
+
|
108
|
+
This project is Copyright (c) 2011 by Ryan Biesemeyer and released under an [MIT-style license](https://github.com/yaauie/inherited-hash/blob/master/LICENSE.txt).
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "inherited-hash/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "inherited-hash"
|
7
|
+
s.version = InheritedHash::VERSION
|
8
|
+
s.authors = ["Ryan Biesemeyer"]
|
9
|
+
s.email = ["ryan@yaauie.com"]
|
10
|
+
s.homepage = "https://github.com/yaauie/inherited-hash"
|
11
|
+
s.summary = %q{class inheritance with hashes}
|
12
|
+
s.description = %q{a module that allows you to specify hashes that are merged along the inheritance chain}
|
13
|
+
|
14
|
+
s.rubyforge_project = "inherited-hash"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency 'rspec'
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "inherited-hash/version"
|
2
|
+
|
3
|
+
module InheritedHash
|
4
|
+
def inherited_hash_accessor *names
|
5
|
+
names.each do |name|
|
6
|
+
[self,class<<self;self;end].each do |context|
|
7
|
+
context.send(%Q{instance_eval}.to_s) do
|
8
|
+
define_method(name) do
|
9
|
+
storage = %Q{@#{name}}.to_sym
|
10
|
+
unless instance_variable_defined?( storage )
|
11
|
+
instance_variable_set(storage, InheritedHash::ConnectedHash.new.connect(self,name))
|
12
|
+
end
|
13
|
+
instance_variable_get( storage)
|
14
|
+
end
|
15
|
+
define_method(%Q{#{name}!}.to_sym) do
|
16
|
+
send(name).to_hash!
|
17
|
+
end
|
18
|
+
define_method(%Q{#{name}=}.to_sym) do |hsh|
|
19
|
+
raise ArgumentError, 'Only hashes are allowed' unless hsh.is_a? Hash
|
20
|
+
send(name).replace(hsh)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ConnectedHash < Hash
|
28
|
+
def connect(anchor,name)
|
29
|
+
@anchor, @name = anchor, name
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def verify!
|
34
|
+
raise Exception, "#{self.inspect} must be connected!" unless @anchor and @name
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash!
|
38
|
+
verify!
|
39
|
+
return @anchor.class.send(%Q{#{@name}!}.to_sym).merge(self.to_hash) unless @anchor.is_a? Module
|
40
|
+
hash = Hash.new
|
41
|
+
@anchor.ancestors.reverse.each do |ancestor|
|
42
|
+
hash.merge!(ancestor.send(@name).to_hash) if ancestor.respond_to?(@name)
|
43
|
+
end
|
44
|
+
hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_hash
|
48
|
+
Hash.new(&default_proc).replace(super)
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_definition_of(key)
|
52
|
+
verify!
|
53
|
+
return @anchor if has_key? key
|
54
|
+
return @anchor.class.send(@name).find_definition_of(key) unless @anchor.is_a? Module
|
55
|
+
@anchor.ancestors.reverse.index do |ancestor|
|
56
|
+
next false unless ancestor.respond_to?(@name)
|
57
|
+
return ancestor if ancestor.send(@name).has_key?(key)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe 'InheritedHash' do
|
4
|
+
describe '#inherited_hash_accessor' do
|
5
|
+
subject do
|
6
|
+
Class.new do
|
7
|
+
extend InheritedHash
|
8
|
+
inherited_hash_accessor :foo
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should create accessor methods in instances' do
|
13
|
+
subject.new.should respond_to :foo
|
14
|
+
subject.new.should respond_to :foo!
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should create accessor methods in the class' do
|
18
|
+
subject.should respond_to :foo
|
19
|
+
subject.should respond_to :foo!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
context 'an inherited stack' do
|
23
|
+
before :each do
|
24
|
+
@s = Hash.new
|
25
|
+
@s[:root] = Class.new do
|
26
|
+
extend InheritedHash
|
27
|
+
inherited_hash_accessor :foo,:bar
|
28
|
+
|
29
|
+
self.foo={
|
30
|
+
:not_overridden => "don't override me",
|
31
|
+
:existing_key => 'unchanged'
|
32
|
+
}
|
33
|
+
end
|
34
|
+
@s[:leaf1] = Class.new(@s[:root]) do
|
35
|
+
foo[:new_key] = 'brand new'
|
36
|
+
foo[:existing_key] = 'changed'
|
37
|
+
end
|
38
|
+
@s[:leaf2] = Class.new(@s[:root]) do
|
39
|
+
end
|
40
|
+
@s[:leaf2_shoot1] = Class.new(@s[:leaf2]) do
|
41
|
+
foo[:existing_key] = 'changed!'
|
42
|
+
end
|
43
|
+
|
44
|
+
@s
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'in root class' do
|
48
|
+
subject {@s[:root]}
|
49
|
+
|
50
|
+
it 'should be directly accessible' do
|
51
|
+
subject.foo[:existing_key].should == 'unchanged'
|
52
|
+
subject.foo[:not_overridden].should == "don't override me"
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should not be overridden' do
|
56
|
+
subject.foo[:new_key].should be_nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'in extended class' do
|
61
|
+
subject {@s[:leaf1]}
|
62
|
+
|
63
|
+
it 'should be directly accessible' do
|
64
|
+
subject.foo[:new_key].should == 'brand new'
|
65
|
+
subject.foo[:existing_key].should == 'changed'
|
66
|
+
subject.foo[:not_overridden].should be_nil
|
67
|
+
end
|
68
|
+
it 'should be able to access inheritance' do
|
69
|
+
subject.foo![:new_key].should == 'brand new'
|
70
|
+
subject.foo![:existing_key].should == 'changed'
|
71
|
+
subject.foo![:not_overridden].should == "don't override me"
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'instance' do
|
75
|
+
subject do
|
76
|
+
s = @s[:leaf1].new
|
77
|
+
s.foo = {:another_new_key => 'ooh, shiny'}
|
78
|
+
s
|
79
|
+
end
|
80
|
+
it 'should have access to instance-set key' do
|
81
|
+
subject.foo![:another_new_key].should == 'ooh, shiny'
|
82
|
+
end
|
83
|
+
it 'should have access to keys inherited from class' do
|
84
|
+
subject.foo![:existing_key].should == 'changed'
|
85
|
+
end
|
86
|
+
it 'should have access to keys inherited from root' do
|
87
|
+
subject.foo![:not_overridden].should == "don't override me"
|
88
|
+
end
|
89
|
+
it 'should be able to find the definition in the object' do
|
90
|
+
subject.foo.find_definition_of(:another_new_key).should be subject
|
91
|
+
end
|
92
|
+
it 'should be able to find the definition in the class' do
|
93
|
+
subject.foo.find_definition_of(:new_key).should be subject.class
|
94
|
+
subject.foo.find_definition_of(:existing_key).should be subject.class
|
95
|
+
end
|
96
|
+
it 'should be able to find the definition in the root' do
|
97
|
+
subject.foo.find_definition_of(:not_overridden).should be @s[:root]
|
98
|
+
end
|
99
|
+
it 'should not be able to find a definition that does not exist' do
|
100
|
+
subject.foo.find_definition_of(:not_exist).should be_nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: inherited-hash
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ryan Biesemeyer
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-11-02 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rake
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
description: a module that allows you to specify hashes that are merged along the inheritance chain
|
49
|
+
email:
|
50
|
+
- ryan@yaauie.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- .gitignore
|
59
|
+
- Gemfile
|
60
|
+
- LICENSE.txt
|
61
|
+
- README.mdown
|
62
|
+
- Rakefile
|
63
|
+
- inherited-hash.gemspec
|
64
|
+
- lib/inherited-hash.rb
|
65
|
+
- lib/inherited-hash/global.rb
|
66
|
+
- lib/inherited-hash/version.rb
|
67
|
+
- spec/inherited-hash_spec.rb
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
homepage: https://github.com/yaauie/inherited-hash
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
hash: 3
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project: inherited-hash
|
98
|
+
rubygems_version: 1.8.10
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: class inheritance with hashes
|
102
|
+
test_files:
|
103
|
+
- spec/inherited-hash_spec.rb
|
104
|
+
- spec/spec_helper.rb
|