interface 1.0.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/{CHANGES → CHANGES.md} +45 -30
- data/Gemfile +2 -0
- data/LICENSE +72 -0
- data/{MANIFEST → MANIFEST.md} +13 -12
- data/README.md +91 -0
- data/Rakefile +11 -8
- data/examples/enhanced_features_demo.rb +110 -0
- data/examples/example_interface.rb +2 -2
- data/interface.gemspec +35 -34
- data/lib/interface.rb +268 -40
- data/spec/interface_enhanced_spec.rb +175 -0
- data/spec/interface_spec.rb +56 -0
- data.tar.gz.sig +0 -0
- metadata +28 -28
- metadata.gz.sig +0 -0
- data/README +0 -74
- data/test/test_interface.rb +0 -64
data/lib/interface.rb
CHANGED
@@ -1,99 +1,312 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# A module for implementing Java style interfaces in Ruby. For more information
|
2
4
|
# about Java interfaces, please see:
|
3
5
|
#
|
4
6
|
# http://java.sun.com/docs/books/tutorial/java/concepts/interface.html
|
5
7
|
#
|
8
|
+
# @author Daniel J. Berger
|
9
|
+
# @since 1.0.0
|
6
10
|
module Interface
|
7
11
|
# The version of the interface library.
|
8
|
-
|
12
|
+
VERSION = '1.2.0'.freeze
|
9
13
|
|
10
14
|
# Raised if a class or instance does not meet the interface requirements.
|
11
|
-
|
15
|
+
# Provides detailed information about which methods are missing and from which target.
|
16
|
+
class MethodMissing < RuntimeError
|
17
|
+
# @return [Array<Symbol>] the missing method names
|
18
|
+
attr_reader :missing_methods
|
19
|
+
|
20
|
+
# @return [String] the name of the target class/module
|
21
|
+
attr_reader :target_name
|
22
|
+
|
23
|
+
# @return [String] the name of the interface
|
24
|
+
attr_reader :interface_name
|
12
25
|
|
13
|
-
|
26
|
+
# Creates a new MethodMissing error with detailed information
|
27
|
+
#
|
28
|
+
# @param missing_methods [Array<Symbol>, Symbol] the missing method name(s)
|
29
|
+
# @param target [Module, Class] the target class or module
|
30
|
+
# @param interface_mod [Module] the interface module
|
31
|
+
def initialize(missing_methods, target = nil, interface_mod = nil)
|
32
|
+
@missing_methods = Array(missing_methods)
|
33
|
+
@target_name = target&.name || target&.class&.name || 'Unknown'
|
34
|
+
@interface_name = interface_mod&.name || 'Unknown Interface'
|
35
|
+
|
36
|
+
methods_list = @missing_methods.map { |m| "`#{m}`" }.join(', ')
|
37
|
+
super("#{@target_name} must implement #{methods_list} to satisfy #{@interface_name}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
alias extends extend
|
14
42
|
|
15
43
|
private
|
16
44
|
|
45
|
+
# Handles extending an object with the interface
|
46
|
+
#
|
47
|
+
# @param obj [Object] the object to extend
|
48
|
+
# @return [Object] the extended object
|
17
49
|
def extend_object(obj)
|
18
|
-
return append_features(obj) if Interface
|
19
|
-
append_features(
|
50
|
+
return append_features(obj) if obj.is_a?(Interface)
|
51
|
+
append_features(obj.singleton_class)
|
20
52
|
included(obj)
|
21
53
|
end
|
22
54
|
|
55
|
+
# Validates interface requirements when included/extended
|
56
|
+
#
|
57
|
+
# @param mod [Module] the module being extended
|
58
|
+
# @return [Module] the module
|
59
|
+
# @raise [Interface::MethodMissing] if required methods are not implemented
|
23
60
|
def append_features(mod)
|
24
|
-
return super if Interface
|
61
|
+
return super if mod.is_a?(Interface)
|
25
62
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
63
|
+
# For extend on instances or immediate validation
|
64
|
+
if should_validate_immediately?(mod)
|
65
|
+
validate_interface_requirements(mod)
|
66
|
+
end
|
67
|
+
super
|
68
|
+
end
|
29
69
|
|
30
|
-
|
31
|
-
|
32
|
-
|
70
|
+
# Called when this interface is included in a class or module
|
71
|
+
#
|
72
|
+
# @param base [Class, Module] the class or module that included this interface
|
73
|
+
def included(base)
|
74
|
+
super
|
75
|
+
return if base.is_a?(Interface)
|
76
|
+
|
77
|
+
interface_module = self
|
78
|
+
|
79
|
+
# For classes, set up method tracking to validate after all methods are defined
|
80
|
+
if base.is_a?(Class)
|
81
|
+
# Store reference to interface for later validation
|
82
|
+
base.instance_variable_set(:@pending_interface_validations,
|
83
|
+
(base.instance_variable_get(:@pending_interface_validations) || []) + [interface_module])
|
84
|
+
|
85
|
+
# Set up method_added callback if not already done
|
86
|
+
unless base.respond_to?(:interface_method_added_original)
|
87
|
+
base.singleton_class.alias_method(:interface_method_added_original, :method_added) if base.respond_to?(:method_added)
|
88
|
+
|
89
|
+
base.define_singleton_method(:method_added) do |method_name|
|
90
|
+
# Call original method_added if it existed
|
91
|
+
interface_method_added_original(method_name) if respond_to?(:interface_method_added_original)
|
33
92
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
93
|
+
# Check if all pending interfaces are now satisfied
|
94
|
+
pending = instance_variable_get(:@pending_interface_validations) || []
|
95
|
+
pending.each do |interface_mod|
|
96
|
+
if interface_mod.satisfied_by?(self)
|
97
|
+
# Interface is satisfied, remove from pending
|
98
|
+
pending.delete(interface_mod)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
instance_variable_set(:@pending_interface_validations, pending)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Set up validation at class end using TracePoint
|
105
|
+
setup_deferred_validation(base)
|
39
106
|
end
|
107
|
+
else
|
108
|
+
# For modules and instances, validate immediately
|
109
|
+
validate_interface_requirements(base)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Determines if we should validate immediately or defer validation
|
114
|
+
#
|
115
|
+
# @param mod [Module] the module to check
|
116
|
+
# @return [Boolean] true if validation should happen immediately
|
117
|
+
def should_validate_immediately?(mod)
|
118
|
+
required_method_ids = compute_required_methods
|
119
|
+
return true if required_method_ids.empty?
|
120
|
+
|
121
|
+
# Always validate immediately for instances (singleton classes)
|
122
|
+
return true if mod.singleton_class?
|
123
|
+
|
124
|
+
# Check if any required methods are already defined
|
125
|
+
implemented_methods = get_implemented_methods(mod)
|
126
|
+
(required_method_ids & implemented_methods).any?
|
127
|
+
rescue NoMethodError
|
128
|
+
# If instance_methods fails, this is likely an instance, validate immediately
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
# Sets up deferred validation using TracePoint to detect when class definition ends
|
133
|
+
#
|
134
|
+
# @param base [Class, Module] the class or module to validate later
|
135
|
+
def setup_deferred_validation(base)
|
136
|
+
interface_module = self
|
137
|
+
|
138
|
+
# Use TracePoint to detect when we're done defining the class
|
139
|
+
trace = TracePoint.new(:end) do |tp|
|
140
|
+
# Check if we're ending the definition of our target class
|
141
|
+
if tp.self == base
|
142
|
+
trace.disable
|
143
|
+
|
144
|
+
# Validate any remaining pending interfaces
|
145
|
+
pending = base.instance_variable_get(:@pending_interface_validations) || []
|
146
|
+
pending.each do |interface_mod|
|
147
|
+
begin
|
148
|
+
interface_mod.send(:validate_interface_requirements, base)
|
149
|
+
rescue => e
|
150
|
+
raise e
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
trace.enable
|
157
|
+
end
|
158
|
+
|
159
|
+
# Validates that all required methods are implemented
|
160
|
+
#
|
161
|
+
# @param mod [Module] the module to validate
|
162
|
+
# @raise [Interface::MethodMissing] if required methods are missing
|
163
|
+
def validate_interface_requirements(mod)
|
164
|
+
required_method_ids = compute_required_methods
|
165
|
+
implemented_methods = get_implemented_methods(mod)
|
166
|
+
missing_methods = required_method_ids - implemented_methods
|
167
|
+
|
168
|
+
return if missing_methods.empty?
|
169
|
+
|
170
|
+
# For backward compatibility, raise with first missing method if only one
|
171
|
+
# Otherwise use the enhanced error with full details
|
172
|
+
if missing_methods.size == 1
|
173
|
+
raise Interface::MethodMissing.new(missing_methods.first, mod, self)
|
174
|
+
else
|
175
|
+
raise Interface::MethodMissing.new(missing_methods, mod, self)
|
40
176
|
end
|
177
|
+
end
|
41
178
|
|
42
|
-
|
179
|
+
# Computes the final list of required methods after inheritance and unrequired methods
|
180
|
+
#
|
181
|
+
# @return [Array<Symbol>] the required method symbols
|
182
|
+
def compute_required_methods
|
183
|
+
inherited_methods = compute_inherited_methods
|
184
|
+
all_required = ((@ids || []) + inherited_methods).uniq
|
185
|
+
all_required - (@unreq || [])
|
186
|
+
end
|
187
|
+
|
188
|
+
# Gets inherited method requirements from parent interfaces
|
189
|
+
#
|
190
|
+
# @return [Array<Symbol>] inherited required methods
|
191
|
+
def compute_inherited_methods
|
192
|
+
parent_interfaces = ancestors.drop(1).select { |ancestor| ancestor.is_a?(Interface) }
|
193
|
+
parent_interfaces.flat_map { |interface| interface.instance_variable_get(:@ids) || [] }
|
194
|
+
end
|
195
|
+
|
196
|
+
# Gets the list of implemented methods for a module
|
197
|
+
#
|
198
|
+
# @param mod [Module] the module to check
|
199
|
+
# @return [Array<Symbol>] implemented method names
|
200
|
+
def get_implemented_methods(mod)
|
201
|
+
if mod.respond_to?(:instance_methods)
|
202
|
+
mod.instance_methods(true)
|
203
|
+
else
|
204
|
+
# For instances, get methods from their singleton class
|
205
|
+
mod.methods.map(&:to_sym)
|
206
|
+
end
|
43
207
|
end
|
44
208
|
|
45
209
|
public
|
46
210
|
|
47
|
-
# Accepts an array of method names that define the interface.
|
211
|
+
# Accepts an array of method names that define the interface. When this
|
48
212
|
# module is included/implemented, those method names must have already been
|
49
213
|
# defined.
|
50
214
|
#
|
51
|
-
|
52
|
-
|
215
|
+
# @param method_names [Array<Symbol>] method names that must be implemented
|
216
|
+
# @return [Array<Symbol>] the updated list of required methods
|
217
|
+
# @raise [ArgumentError] if no method names are provided
|
218
|
+
# @example
|
219
|
+
# MyInterface = interface do
|
220
|
+
# required_methods :foo, :bar, :baz
|
221
|
+
# end
|
222
|
+
def required_methods(*method_names)
|
223
|
+
raise ArgumentError, 'At least one method name must be provided' if method_names.empty?
|
224
|
+
|
225
|
+
@ids = method_names.map(&:to_sym)
|
53
226
|
end
|
54
227
|
|
55
228
|
# Accepts an array of method names that are removed as a requirement for
|
56
229
|
# implementation. Presumably you would use this in a sub-interface where
|
57
230
|
# you only wanted a partial implementation of an existing interface.
|
58
231
|
#
|
59
|
-
|
232
|
+
# @param method_names [Array<Symbol>] method names to remove from requirements
|
233
|
+
# @return [Array<Symbol>] the updated list of unrequired methods
|
234
|
+
# @example
|
235
|
+
# SubInterface = interface do
|
236
|
+
# extends ParentInterface
|
237
|
+
# unrequired_methods :optional_method
|
238
|
+
# end
|
239
|
+
def unrequired_methods(*method_names)
|
60
240
|
@unreq ||= []
|
61
|
-
@unreq
|
241
|
+
return @unreq if method_names.empty?
|
242
|
+
|
243
|
+
@unreq += method_names.map(&:to_sym)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Returns the list of all required methods for this interface
|
247
|
+
#
|
248
|
+
# @return [Array<Symbol>] all required method names
|
249
|
+
def get_required_methods
|
250
|
+
compute_required_methods
|
251
|
+
end
|
252
|
+
|
253
|
+
# Returns the list of unrequired methods for this interface
|
254
|
+
#
|
255
|
+
# @return [Array<Symbol>] unrequired method names
|
256
|
+
def get_unrequired_methods
|
257
|
+
(@unreq || []).dup
|
258
|
+
end
|
259
|
+
|
260
|
+
# Checks if a class or module implements this interface
|
261
|
+
#
|
262
|
+
# @param target [Class, Module] the class or module to check
|
263
|
+
# @return [Boolean] true if the interface is satisfied
|
264
|
+
def satisfied_by?(target)
|
265
|
+
required_method_ids = compute_required_methods
|
266
|
+
implemented_methods = get_implemented_methods(target)
|
267
|
+
(required_method_ids - implemented_methods).empty?
|
62
268
|
end
|
63
269
|
end
|
64
270
|
|
271
|
+
# Extends Object to provide the interface method for creating interfaces
|
65
272
|
class Object
|
66
273
|
# The interface method creates an interface module which typically sets
|
67
274
|
# a list of methods that must be defined in the including class or module.
|
68
275
|
# If the methods are not defined, an Interface::MethodMissing error is raised.
|
69
276
|
#
|
70
|
-
#
|
71
|
-
# sub-interfaces, and they can
|
277
|
+
# An interface can extend an existing interface as well. These are called
|
278
|
+
# sub-interfaces, and they can include the rules for their parent interface
|
72
279
|
# by simply extending it.
|
73
280
|
#
|
74
|
-
#
|
281
|
+
# @yield [Module] the interface module for configuration
|
282
|
+
# @return [Module] a new interface module
|
283
|
+
# @raise [ArgumentError] if no block is provided
|
75
284
|
#
|
285
|
+
# @example Basic interface
|
76
286
|
# # Require 'alpha' and 'beta' methods
|
77
|
-
# AlphaInterface = interface
|
78
|
-
#
|
79
|
-
#
|
287
|
+
# AlphaInterface = interface do
|
288
|
+
# required_methods :alpha, :beta
|
289
|
+
# end
|
80
290
|
#
|
291
|
+
# @example Sub-interface
|
81
292
|
# # A sub-interface that requires 'beta' and 'gamma' only
|
82
|
-
# GammaInterface = interface
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
293
|
+
# GammaInterface = interface do
|
294
|
+
# extends AlphaInterface
|
295
|
+
# required_methods :gamma
|
296
|
+
# unrequired_methods :alpha
|
297
|
+
# end
|
87
298
|
#
|
299
|
+
# @example Usage with error
|
88
300
|
# # Raises an Interface::MethodMissing error because :beta is not defined.
|
89
301
|
# class MyClass
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
302
|
+
# implements AlphaInterface
|
303
|
+
# def alpha
|
304
|
+
# # ...
|
305
|
+
# end
|
94
306
|
# end
|
95
|
-
#
|
96
307
|
def interface(&block)
|
308
|
+
raise ArgumentError, 'Block required for interface definition' unless block_given?
|
309
|
+
|
97
310
|
Module.new do |mod|
|
98
311
|
mod.extend(Interface)
|
99
312
|
mod.instance_eval(&block)
|
@@ -101,6 +314,21 @@ class Object
|
|
101
314
|
end
|
102
315
|
end
|
103
316
|
|
317
|
+
# Extends Module to provide the implements method as an alias for include
|
104
318
|
class Module
|
105
|
-
|
319
|
+
# Implements an interface by including it. This is syntactic sugar
|
320
|
+
# that makes the intent clearer when working with interfaces.
|
321
|
+
#
|
322
|
+
# @param interface_modules [Array<Module>] one or more interface modules
|
323
|
+
# @return [self]
|
324
|
+
#
|
325
|
+
# @example
|
326
|
+
# class MyClass
|
327
|
+
# implements MyInterface
|
328
|
+
#
|
329
|
+
# def required_method
|
330
|
+
# # implementation
|
331
|
+
# end
|
332
|
+
# end
|
333
|
+
alias implements include
|
106
334
|
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'interface'
|
5
|
+
|
6
|
+
RSpec.describe 'Interface Enhanced Features' do
|
7
|
+
describe 'enhanced error messages' do
|
8
|
+
it 'provides detailed error information for single missing method' do
|
9
|
+
basic_interface = interface do
|
10
|
+
required_methods :method_a, :method_b
|
11
|
+
end
|
12
|
+
|
13
|
+
# Use a block to ensure the error happens synchronously
|
14
|
+
expect do
|
15
|
+
Class.new do
|
16
|
+
def method_a; end
|
17
|
+
# missing method_b
|
18
|
+
include basic_interface
|
19
|
+
end
|
20
|
+
end.to raise_error(Interface::MethodMissing) do |error|
|
21
|
+
expect(error.missing_methods).to include(:method_b)
|
22
|
+
expect(error.message).to include('must implement')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'supports include at top of class definition' do
|
27
|
+
basic_interface = interface do
|
28
|
+
required_methods :method_a, :method_b
|
29
|
+
end
|
30
|
+
|
31
|
+
# Test the main functionality - include at top with working methods
|
32
|
+
test_class = Class.new do
|
33
|
+
include basic_interface
|
34
|
+
def method_a; 'a'; end
|
35
|
+
def method_b; 'b'; end
|
36
|
+
end
|
37
|
+
|
38
|
+
expect(test_class.new.method_a).to eq('a')
|
39
|
+
expect(test_class.new.method_b).to eq('b')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'interface validation methods' do
|
44
|
+
it 'returns required methods list' do
|
45
|
+
basic_interface = interface do
|
46
|
+
required_methods :method_a, :method_b
|
47
|
+
end
|
48
|
+
|
49
|
+
expect(basic_interface.get_required_methods).to contain_exactly(:method_a, :method_b)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns unrequired methods list for sub-interfaces' do
|
53
|
+
basic_interface = interface do
|
54
|
+
required_methods :method_a, :method_b
|
55
|
+
end
|
56
|
+
|
57
|
+
extended_interface = Module.new do
|
58
|
+
extend Interface
|
59
|
+
extend basic_interface
|
60
|
+
required_methods :method_c
|
61
|
+
unrequired_methods :method_a
|
62
|
+
end
|
63
|
+
|
64
|
+
expect(extended_interface.get_unrequired_methods).to contain_exactly(:method_a)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'correctly computes final required methods for sub-interfaces' do
|
68
|
+
basic_interface = interface do
|
69
|
+
required_methods :method_a, :method_b
|
70
|
+
end
|
71
|
+
|
72
|
+
extended_interface = Module.new do
|
73
|
+
extend Interface
|
74
|
+
extend basic_interface
|
75
|
+
required_methods :method_c
|
76
|
+
unrequired_methods :method_a
|
77
|
+
end
|
78
|
+
|
79
|
+
final_required = extended_interface.get_required_methods
|
80
|
+
expect(final_required).to contain_exactly(:method_b, :method_c)
|
81
|
+
expect(final_required).not_to include(:method_a)
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'satisfied_by? method' do
|
85
|
+
let(:implementing_class) do
|
86
|
+
Class.new do
|
87
|
+
def method_a; 'a'; end
|
88
|
+
def method_b; 'b'; end
|
89
|
+
def method_c; 'c'; end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'returns true when interface is satisfied' do
|
94
|
+
basic_interface = interface do
|
95
|
+
required_methods :method_a, :method_b
|
96
|
+
end
|
97
|
+
|
98
|
+
expect(basic_interface.satisfied_by?(implementing_class)).to be true
|
99
|
+
|
100
|
+
extended_interface = Module.new do
|
101
|
+
extend Interface
|
102
|
+
extend basic_interface
|
103
|
+
required_methods :method_c
|
104
|
+
unrequired_methods :method_a
|
105
|
+
end
|
106
|
+
|
107
|
+
expect(extended_interface.satisfied_by?(implementing_class)).to be true
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'returns false when interface is not satisfied' do
|
111
|
+
basic_interface = interface do
|
112
|
+
required_methods :method_a, :method_b
|
113
|
+
end
|
114
|
+
|
115
|
+
incomplete_class = Class.new do
|
116
|
+
def method_a; 'a'; end
|
117
|
+
# missing method_b
|
118
|
+
end
|
119
|
+
|
120
|
+
expect(basic_interface.satisfied_by?(incomplete_class)).to be false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'parameter validation' do
|
126
|
+
it 'raises error when no methods provided to required_methods' do
|
127
|
+
expect do
|
128
|
+
interface do
|
129
|
+
required_methods
|
130
|
+
end
|
131
|
+
end.to raise_error(ArgumentError, 'At least one method name must be provided')
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'raises error when no block provided to interface' do
|
135
|
+
expect do
|
136
|
+
Object.new.interface
|
137
|
+
end.to raise_error(ArgumentError, 'Block required for interface definition')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe 'symbol conversion' do
|
142
|
+
it 'converts string method names to symbols' do
|
143
|
+
string_interface = interface do
|
144
|
+
required_methods 'string_method', 'another_method'
|
145
|
+
end
|
146
|
+
|
147
|
+
expect(string_interface.get_required_methods).to contain_exactly(:string_method, :another_method)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe 'complex inheritance scenarios' do
|
152
|
+
it 'handles multiple levels of inheritance correctly' do
|
153
|
+
base_interface = interface do
|
154
|
+
required_methods :base_method
|
155
|
+
end
|
156
|
+
|
157
|
+
middle_interface = Module.new do
|
158
|
+
extend Interface
|
159
|
+
extend base_interface
|
160
|
+
required_methods :middle_method
|
161
|
+
end
|
162
|
+
|
163
|
+
final_interface = Module.new do
|
164
|
+
extend Interface
|
165
|
+
extend middle_interface
|
166
|
+
required_methods :final_method
|
167
|
+
unrequired_methods :base_method
|
168
|
+
end
|
169
|
+
|
170
|
+
required = final_interface.get_required_methods
|
171
|
+
expect(required).to contain_exactly(:middle_method, :final_method)
|
172
|
+
expect(required).not_to include(:base_method)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#####################################################
|
2
|
+
# interface_spec.rb
|
3
|
+
#
|
4
|
+
# Test suite for the Interface module.
|
5
|
+
#####################################################
|
6
|
+
require 'rspec'
|
7
|
+
require 'interface'
|
8
|
+
|
9
|
+
RSpec.describe 'Interface' do
|
10
|
+
alpha_interface = interface{ required_methods :alpha, :beta }
|
11
|
+
|
12
|
+
gamma_interface = interface{
|
13
|
+
extends alpha_interface
|
14
|
+
required_methods :gamma
|
15
|
+
unrequired_methods :alpha
|
16
|
+
}
|
17
|
+
|
18
|
+
let(:class_a){ Class.new }
|
19
|
+
|
20
|
+
let(:class_b){
|
21
|
+
Class.new do
|
22
|
+
def alpha; end
|
23
|
+
def beta; end
|
24
|
+
end
|
25
|
+
}
|
26
|
+
|
27
|
+
let(:class_c){
|
28
|
+
Class.new do
|
29
|
+
def beta; end
|
30
|
+
def gamma; end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
example "version" do
|
35
|
+
expect(Interface::VERSION).to eq('1.2.0')
|
36
|
+
expect(Interface::VERSION).to be_frozen
|
37
|
+
end
|
38
|
+
|
39
|
+
example "interface_requirements_not_met" do
|
40
|
+
expect{ class_a.extend(alpha_interface) }.to raise_error(Interface::MethodMissing)
|
41
|
+
expect{ class_a.new.extend(alpha_interface) }.to raise_error(Interface::MethodMissing)
|
42
|
+
end
|
43
|
+
|
44
|
+
example "sub_interface_requirements_not_met" do
|
45
|
+
expect{ class_b.extend(gamma_interface) }.to raise_error(Interface::MethodMissing)
|
46
|
+
expect{ class_b.new.extend(gamma_interface) }.to raise_error(Interface::MethodMissing)
|
47
|
+
end
|
48
|
+
|
49
|
+
example "alpha_interface_requirements_met" do
|
50
|
+
expect{ class_b.new.extend(alpha_interface) }.not_to raise_error
|
51
|
+
end
|
52
|
+
|
53
|
+
example "gamma_interface_requirements_met" do
|
54
|
+
expect{ class_c.new.extend(gamma_interface) }.not_to raise_error
|
55
|
+
end
|
56
|
+
end
|
data.tar.gz.sig
CHANGED
Binary file
|