inherited-hash 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in inherited-hash.gemspec
4
+ gemspec
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,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core'
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.pattern = FileList['spec/**/*_spec.rb']
7
+ end
@@ -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,5 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__),'..','inherited-hash.rb'))
2
+
3
+ class Module
4
+ include InheritedHash
5
+ end
@@ -0,0 +1,3 @@
1
+ module InheritedHash
2
+ VERSION = "1.0.0"
3
+ 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
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'inherited-hash/global'
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