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 CHANGED
File without changes
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
- adheres_to ElectronicDevice, Calculator
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
- A class must define all instance and class methods of all visibilities defined in each module passed to adheres_to or an error is raised. Not only must all methods be defined, but their method signitures must match those of the specified module(s). Failure to implement all methods will result in a NotImplementedError when the class is loaded. Failure to incorrectly declare a message signature will result in a Adherence::NotAdheredError when the class is loaded. Because the methods defined in the class must be inspected, adheres_to must be called after the methods are defined in the class.
116
+ ### Modules
78
117
 
79
- Similarly, a module may adhere to other modules:
118
+ A module may adhere to other modules:
80
119
 
81
120
  ```ruby
82
121
  module ScientificCalculator
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "adherence"
3
- spec.version = "0.1.0"
4
- spec.date = "2012-08-30"
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"
@@ -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
@@ -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 adheres_to_module(mod)
12
- raise ArgumentError, "Module expected" unless mod.class.to_s == "Module"
7
+ def adheres_to(*mods)
8
+ @adhered_modules = []
13
9
 
14
- (mod.instance_methods(false) + mod.private_instance_methods(false)).each do |method|
15
- if !(instance_methods + private_instance_methods).include?(method)
16
- raise NotImplementedError, "#{self.class} must define #{method}"
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
- if !equal? instance_method(method), mod.instance_method(method)
19
- raise NotAdheredError, "Signature does not match #{mod}##{method}"
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
- module_class_methods = mod.singleton_class.instance_methods(false) + mod.singleton_class.private_instance_methods(false)
24
- module_class_methods.each do |method|
25
- if !(singleton_class.instance_methods + singleton_class.private_instance_methods).include?(method)
26
- raise NotImplementedError, "#{self.class} must define self.#{method}"
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
- if !equal? singleton_class.instance_method(method), mod.singleton_class.instance_method(method)
29
- raise NotAdheredError, "Signature does not match #{mod}.#{method}"
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
@@ -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
- before do
20
- class Duck
21
- attr_reader :name
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
- def initialize(name)
24
- @name = name
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
- "quack"
58
+ "woof"
29
59
  end
30
60
 
31
61
  def move(feet)
32
- "waddle #{feet} feet"
62
+ "trots #{feet} feet"
33
63
  end
34
64
 
35
65
  private
36
66
 
37
67
  def life_expectancy
38
- 10
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
- Duck.new(name)
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 succeed if the object has defined all behavior from the module" do
48
- expect do
49
- class Duck
50
- adheres_to Animal
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
- it "should raise an error if argument is not a module" do
56
- expect do
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
- it "should raise an error if all methods are not implemented" do
64
- Duck.send(:remove_method, :speak)
109
+ private
65
110
 
66
- expect do
67
- class Duck
68
- adheres_to Animal
111
+ def self.build(name, &block)
112
+ new
69
113
  end
70
- end.to raise_error(NotImplementedError)
71
- end
114
+ end
72
115
 
73
- it "should raise an error if all private methods are not implemented" do
74
- Duck.send(:remove_method, :life_expectancy)
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 Duck
121
+ class Bear
78
122
  adheres_to Animal
79
- end
80
- end.to raise_error(NotImplementedError)
81
- end
82
123
 
83
- it "should raise an error if all class methods are not implemented" do
84
- Duck.singleton_class.send(:remove_method, :build)
124
+ def speak
125
+ "grrrr"
126
+ end
85
127
 
86
- expect { Duck.adheres_to(Animal) }.to raise_error(NotImplementedError)
87
- end
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
- it "should raise an error if methods are implemented with an invalid signature" do
90
- Duck.send(:remove_method, :move)
91
- Duck.class_eval do
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 Duck
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.1.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-08-30 00:00:00.000000000 Z
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.23
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