adherence 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +0 -0
- data/Gemfile.lock +0 -0
- data/README.md +42 -3
- data/adherence.gemspec +2 -2
- data/lib/adherence.rb +24 -0
- data/lib/adherence/class.rb +70 -17
- data/lib/adherence/module.rb +0 -19
- data/spec/adherence_class_spec.rb +113 -46
- data/spec/adherence_module_spec.rb +0 -0
- metadata +6 -6
data/Gemfile
CHANGED
File without changes
|
data/Gemfile.lock
CHANGED
File without changes
|
data/README.md
CHANGED
@@ -20,6 +20,8 @@ Adherence provides a simple means of declaring abstract behavior in Ruby modules
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
+
### Classes
|
24
|
+
|
23
25
|
A class may adhere to any number of modules by use of the adheres_to method.
|
24
26
|
|
25
27
|
```ruby
|
@@ -42,6 +44,8 @@ module Calculator
|
|
42
44
|
end
|
43
45
|
|
44
46
|
class TexasInstruments
|
47
|
+
adheres_to ElectronicDevice, Calculator
|
48
|
+
|
45
49
|
def on
|
46
50
|
@power = true
|
47
51
|
end
|
@@ -69,14 +73,49 @@ class TexasInstruments
|
|
69
73
|
def self.compatible?(device1, device2)
|
70
74
|
device1.respond_to?(:add) && device2.respond_to?(:add)
|
71
75
|
end
|
76
|
+
end
|
77
|
+
```
|
72
78
|
|
73
|
-
|
79
|
+
When adheres_to is called, the existing class and instance methods defined in the class are inspected. A check is made on any method whose name and visibility matches that of a method defined in one of the specified modules. If the signature of the method defined in the class does not match that of the method defined in the module, an Adherence::NotAdheredError will be raised. Similarly, any methods defined after adheres_to is called will be inspected when added to the class. Any method not defined in the class will raise a NotImplementedError when invoked:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
class HewlettPackard
|
83
|
+
adheres_to ElectronicDevice, Calculator
|
84
|
+
|
85
|
+
def on
|
86
|
+
@power = true
|
87
|
+
end
|
88
|
+
|
89
|
+
def off
|
90
|
+
@power = false
|
91
|
+
end
|
92
|
+
|
93
|
+
def add(x1, x2)
|
94
|
+
x1 + x2
|
95
|
+
end
|
96
|
+
|
97
|
+
def subtract(x1, x2)
|
98
|
+
x1 - x2
|
99
|
+
end
|
100
|
+
|
101
|
+
def multiply(x1, x2)
|
102
|
+
x1 * x2
|
103
|
+
end
|
74
104
|
end
|
105
|
+
|
106
|
+
> device1 = HewlettPackard.new
|
107
|
+
> device2 = HewlettPackard.new
|
108
|
+
|
109
|
+
> device1.divide(12, 4)
|
110
|
+
Adherence::NotImplementedError: "HewlettPackard must define divide"
|
111
|
+
|
112
|
+
> HewlettPackard.compatible?(device1, device2)
|
113
|
+
Adherence::NotImplementedError: "HewlettPackard must define compatible?"
|
75
114
|
```
|
76
115
|
|
77
|
-
|
116
|
+
### Modules
|
78
117
|
|
79
|
-
|
118
|
+
A module may adhere to other modules:
|
80
119
|
|
81
120
|
```ruby
|
82
121
|
module ScientificCalculator
|
data/adherence.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "adherence"
|
3
|
-
spec.version = "0.
|
4
|
-
spec.date = "2012-
|
3
|
+
spec.version = "0.2.0"
|
4
|
+
spec.date = "2012-09-10"
|
5
5
|
spec.summary = "Adds simple interface-like behavior to Ruby"
|
6
6
|
spec.authors = ["Mike Bradford"]
|
7
7
|
spec.email = "mbradford@47primes.com"
|
data/lib/adherence.rb
CHANGED
@@ -1,12 +1,36 @@
|
|
1
1
|
Dir.glob(File.dirname(__FILE__) + "/adherence/*.rb").each {|f| require f}
|
2
2
|
|
3
3
|
module Adherence
|
4
|
+
module HelperMethods
|
5
|
+
private
|
6
|
+
|
7
|
+
def add_method(method, singleton_method=false)
|
8
|
+
name = method.name
|
9
|
+
params = method.parameters.reduce([]) do |memo, word|
|
10
|
+
arity, param = word
|
11
|
+
case arity
|
12
|
+
when :req then memo << "#{param}"
|
13
|
+
when :opt then memo << "#{param}=nil"
|
14
|
+
when :block then memo << "&#{param}"
|
15
|
+
when :rest then memo << "*#{param}"
|
16
|
+
end
|
17
|
+
end.join(", ")
|
18
|
+
|
19
|
+
module_eval <<-DEF
|
20
|
+
def #{"self." if singleton_method}#{name}(#{params})
|
21
|
+
raise NotImplementedError, "This method must be defined in the calling class."
|
22
|
+
end
|
23
|
+
DEF
|
24
|
+
end
|
25
|
+
end
|
4
26
|
end
|
5
27
|
|
6
28
|
class Module
|
29
|
+
include Adherence::HelperMethods
|
7
30
|
include Adherence::Module
|
8
31
|
end
|
9
32
|
|
10
33
|
class Class
|
34
|
+
include Adherence::HelperMethods
|
11
35
|
include Adherence::Class
|
12
36
|
end
|
data/lib/adherence/class.rb
CHANGED
@@ -2,37 +2,90 @@ module Adherence
|
|
2
2
|
class NotAdheredError < NotImplementedError; end
|
3
3
|
module Class
|
4
4
|
|
5
|
-
def adheres_to(*args)
|
6
|
-
args.each {|mod| adheres_to_module mod}
|
7
|
-
end
|
8
|
-
|
9
5
|
private
|
10
6
|
|
11
|
-
def
|
12
|
-
|
7
|
+
def adheres_to(*mods)
|
8
|
+
@adhered_modules = []
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
mods.each {|mod| adheres_to_module mod}
|
11
|
+
|
12
|
+
class_eval do
|
13
|
+
def self.method_added(method)
|
14
|
+
@adhered_modules.each do |mod|
|
15
|
+
if mod.instance_methods(false).include?(method) && !equal?(instance_method(method), mod.instance_method(method))
|
16
|
+
raise NotAdheredError, "Signature does not match #{mod}##{method}"
|
17
|
+
end
|
18
|
+
end
|
17
19
|
end
|
18
|
-
|
19
|
-
|
20
|
+
|
21
|
+
def self.singleton_method_added(method)
|
22
|
+
@adhered_modules.each do |mod|
|
23
|
+
if mod.singleton_class.instance_methods(false).include?(method) && !equal?(singleton_class.instance_method(method), mod.singleton_class.instance_method(method))
|
24
|
+
raise NotAdheredError, "Signature does not match #{mod}.#{method}"
|
25
|
+
end
|
26
|
+
end
|
20
27
|
end
|
21
28
|
end
|
29
|
+
end
|
22
30
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
31
|
+
def adheres_to_module(mod)
|
32
|
+
raise ArgumentError, "Module expected" unless mod.class.to_s == "Module"
|
33
|
+
@adhered_modules << mod
|
34
|
+
|
35
|
+
# Add all instance methods from mod not already defined in class
|
36
|
+
|
37
|
+
public_instance_methods_to_add = mod.public_instance_methods(false).reject {|m| public_method_defined?(m)}
|
38
|
+
protected_instance_methods_to_add = mod.protected_instance_methods(false).reject {|m| protected_method_defined?(m)}
|
39
|
+
private_instance_methods_to_add = mod.private_instance_methods(false).reject {|m| private_method_defined?(m)}
|
40
|
+
|
41
|
+
(public_instance_methods_to_add + protected_instance_methods_to_add + private_instance_methods_to_add).each do |method|
|
42
|
+
add_method mod.instance_method(method)
|
43
|
+
end
|
44
|
+
public *public_instance_methods_to_add
|
45
|
+
protected *protected_instance_methods_to_add
|
46
|
+
private *private_instance_methods_to_add
|
47
|
+
|
48
|
+
# Add all class methods from mod not already defined in class
|
49
|
+
|
50
|
+
public_class_methods_to_add = mod.singleton_class.public_instance_methods(false).reject {|m| singleton_class.public_method_defined?(m)}
|
51
|
+
protected_class_methods_to_add = mod.singleton_class.protected_instance_methods(false).reject {|m| singleton_class.protected_method_defined?(m)}
|
52
|
+
private_class_methods_to_add = mod.singleton_class.private_instance_methods(false).reject {|m| singleton_class.private_method_defined?(m)}
|
53
|
+
|
54
|
+
(public_class_methods_to_add + protected_class_methods_to_add + private_class_methods_to_add).each do |method|
|
55
|
+
add_method mod.singleton_class.instance_method(method), true
|
56
|
+
end
|
57
|
+
singleton_class.send :public, *public_class_methods_to_add
|
58
|
+
singleton_class.send :protected, *protected_class_methods_to_add
|
59
|
+
singleton_class.send :private, *private_class_methods_to_add
|
60
|
+
|
61
|
+
# Ensure all instance methods have the same signature of those defined in mod
|
62
|
+
|
63
|
+
%w(public protected private).each do |visibility|
|
64
|
+
send("#{visibility}_instance_methods", false).each do |method|
|
65
|
+
if mod.send("#{visibility}_method_defined?", method) && !equal?(instance_method(method), mod.instance_method(method))
|
66
|
+
raise NotAdheredError, "Signature does not match #{mod}##{method}"
|
67
|
+
end
|
27
68
|
end
|
28
|
-
|
29
|
-
|
69
|
+
end
|
70
|
+
|
71
|
+
# Ensure all class methods have the same signature of those defined in mod
|
72
|
+
|
73
|
+
%w(public protected private).each do |visibility|
|
74
|
+
singleton_class.send("#{visibility}_instance_methods", false).each do |method|
|
75
|
+
if mod.singleton_class.send("#{visibility}_method_defined?", method) && !equal?(singleton_class.instance_method(method), mod.singleton_class.instance_method(method))
|
76
|
+
raise NotAdheredError, "Signature does not match #{mod}.#{method}"
|
77
|
+
end
|
30
78
|
end
|
31
79
|
end
|
32
80
|
end
|
33
81
|
|
82
|
+
class <<self
|
83
|
+
attr_reader :adhered_modules
|
84
|
+
end
|
85
|
+
|
34
86
|
def equal?(method1, method2)
|
35
87
|
method1.name == method2.name && method1.parameters == method2.parameters ? true : false
|
36
88
|
end
|
89
|
+
|
37
90
|
end
|
38
91
|
end
|
data/lib/adherence/module.rb
CHANGED
@@ -40,24 +40,5 @@ module Adherence
|
|
40
40
|
private_class_method *mod.singleton_class.private_instance_methods(false)
|
41
41
|
end
|
42
42
|
|
43
|
-
def add_method(method, singleton_method=false)
|
44
|
-
name = method.name
|
45
|
-
params = method.parameters.reduce([]) do |memo, word|
|
46
|
-
arity, param = word
|
47
|
-
case arity
|
48
|
-
when :req then memo << "#{param}"
|
49
|
-
when :opt then memo << "#{param}=nil"
|
50
|
-
when :block then memo << "&#{param}"
|
51
|
-
when :rest then memo << "*#{param}"
|
52
|
-
end
|
53
|
-
end.join(", ")
|
54
|
-
|
55
|
-
module_eval <<-DEF
|
56
|
-
def #{"self." if singleton_method}#{name}(#{params})
|
57
|
-
raise NotImplementedError, "This method must be defined in the calling class."
|
58
|
-
end
|
59
|
-
DEF
|
60
|
-
end
|
61
|
-
|
62
43
|
end
|
63
44
|
end
|
@@ -16,89 +16,156 @@ end
|
|
16
16
|
|
17
17
|
describe Object, "adheres_to" do
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
it "should succeed if the object has defined all behavior from the module" do
|
20
|
+
expect do
|
21
|
+
class Duck
|
22
|
+
adheres_to Animal
|
23
|
+
|
24
|
+
def speak
|
25
|
+
"quack"
|
26
|
+
end
|
22
27
|
|
23
|
-
|
24
|
-
|
28
|
+
def move(feet)
|
29
|
+
"waddle #{feet} feet"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def life_expectancy
|
35
|
+
10
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.build(name, &block)
|
39
|
+
new
|
40
|
+
end
|
25
41
|
end
|
42
|
+
end.to_not raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should raise an error if argument is not a module" do
|
46
|
+
expect do
|
47
|
+
class Otter
|
48
|
+
adheres_to Class.new
|
49
|
+
end
|
50
|
+
end.to raise_error(ArgumentError)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should raise an error if a non-implemented class method is called" do
|
54
|
+
class Dog
|
55
|
+
adheres_to Animal
|
26
56
|
|
27
57
|
def speak
|
28
|
-
"
|
58
|
+
"woof"
|
29
59
|
end
|
30
60
|
|
31
61
|
def move(feet)
|
32
|
-
"
|
62
|
+
"trots #{feet} feet"
|
33
63
|
end
|
34
64
|
|
35
65
|
private
|
36
66
|
|
37
67
|
def life_expectancy
|
38
|
-
|
68
|
+
13
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
expect { Dog.build("fido") }.to raise_error(NotImplementedError)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should raise an error if a non-implemented instance method is called" do
|
76
|
+
class Shark
|
77
|
+
adheres_to Animal
|
78
|
+
|
79
|
+
def move(feet)
|
80
|
+
"swim #{feet} feet"
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def life_expectancy
|
86
|
+
32
|
39
87
|
end
|
40
88
|
|
41
89
|
def self.build(name, &block)
|
42
|
-
|
90
|
+
new
|
43
91
|
end
|
44
92
|
end
|
93
|
+
|
94
|
+
expect { Shark.new.speak }.to raise_error(NotImplementedError)
|
45
95
|
end
|
46
96
|
|
47
|
-
it "should
|
48
|
-
|
49
|
-
|
50
|
-
|
97
|
+
it "should raise an error if a non-implemented private instance method is called" do
|
98
|
+
class Horse
|
99
|
+
adheres_to Animal
|
100
|
+
|
101
|
+
def speak
|
102
|
+
"neigh"
|
51
103
|
end
|
52
|
-
end.to_not raise_error
|
53
|
-
end
|
54
104
|
|
55
|
-
|
56
|
-
|
57
|
-
class Duck
|
58
|
-
adheres_to Class.new
|
105
|
+
def move(feet)
|
106
|
+
"gallop #{feet} feet"
|
59
107
|
end
|
60
|
-
end.to raise_error(ArgumentError)
|
61
|
-
end
|
62
108
|
|
63
|
-
|
64
|
-
Duck.send(:remove_method, :speak)
|
109
|
+
private
|
65
110
|
|
66
|
-
|
67
|
-
|
68
|
-
adheres_to Animal
|
111
|
+
def self.build(name, &block)
|
112
|
+
new
|
69
113
|
end
|
70
|
-
end
|
71
|
-
end
|
114
|
+
end
|
72
115
|
|
73
|
-
|
74
|
-
|
116
|
+
expect { Horse.new.send(:life_expectancy) }.to raise_error(NotImplementedError)
|
117
|
+
end
|
75
118
|
|
119
|
+
it "should raise an error if instance method is implemented with an invalid signature" do
|
76
120
|
expect do
|
77
|
-
class
|
121
|
+
class Bear
|
78
122
|
adheres_to Animal
|
79
|
-
end
|
80
|
-
end.to raise_error(NotImplementedError)
|
81
|
-
end
|
82
123
|
|
83
|
-
|
84
|
-
|
124
|
+
def speak
|
125
|
+
"grrrr"
|
126
|
+
end
|
85
127
|
|
86
|
-
|
87
|
-
|
128
|
+
def move(distance)
|
129
|
+
"run #{distance} feet"
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def life_expectancy
|
135
|
+
30
|
136
|
+
end
|
88
137
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
def move(distance)
|
93
|
-
"waddle #{distance} feet"
|
138
|
+
def self.build(name, &block)
|
139
|
+
new
|
140
|
+
end
|
94
141
|
end
|
95
|
-
end
|
142
|
+
end.to raise_error(Adherence::NotAdheredError, "Signature does not match Animal#move")
|
143
|
+
end
|
96
144
|
|
145
|
+
it "should raise an error if class method is implemented with an invalid signature", class_method: true do
|
97
146
|
expect do
|
98
|
-
class
|
147
|
+
class Person
|
99
148
|
adheres_to Animal
|
149
|
+
|
150
|
+
def speak
|
151
|
+
"hello"
|
152
|
+
end
|
153
|
+
|
154
|
+
def move(feet)
|
155
|
+
"walk #{distance} feet"
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def life_expectancy
|
161
|
+
80
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.build(name)
|
165
|
+
new
|
166
|
+
end
|
100
167
|
end
|
101
|
-
end.to raise_error(Adherence::NotAdheredError)
|
168
|
+
end.to raise_error(Adherence::NotAdheredError, "Signature does not match Animal.build")
|
102
169
|
end
|
103
170
|
|
104
171
|
end
|
File without changes
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adherence
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -40,10 +40,10 @@ files:
|
|
40
40
|
- ./lib/adherence/module.rb
|
41
41
|
- ./lib/adherence.rb
|
42
42
|
- ./README.md
|
43
|
-
- ./spec/adherence_module_spec.rb
|
44
43
|
- ./spec/adherence_class_spec.rb
|
45
|
-
- spec/adherence_module_spec.rb
|
44
|
+
- ./spec/adherence_module_spec.rb
|
46
45
|
- spec/adherence_class_spec.rb
|
46
|
+
- spec/adherence_module_spec.rb
|
47
47
|
homepage: http://github.com/47primes/adherence
|
48
48
|
licenses: []
|
49
49
|
post_install_message:
|
@@ -64,10 +64,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
64
|
version: '0'
|
65
65
|
requirements: []
|
66
66
|
rubyforge_project:
|
67
|
-
rubygems_version: 1.8.
|
67
|
+
rubygems_version: 1.8.24
|
68
68
|
signing_key:
|
69
69
|
specification_version: 3
|
70
70
|
summary: Adds simple interface-like behavior to Ruby
|
71
71
|
test_files:
|
72
|
-
- spec/adherence_module_spec.rb
|
73
72
|
- spec/adherence_class_spec.rb
|
73
|
+
- spec/adherence_module_spec.rb
|