ainterface 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1a4ab42daca0488b11de9d97ac864b3f23dbec33
4
+ data.tar.gz: d2beab95da462a4c55b0ded37bb77d0c843557ab
5
+ SHA512:
6
+ metadata.gz: c7051c8b8b7aac4f6842cf3ae58bcb94f1bcaefea487356375141ab362218040e03707241f6ad1601012cca584c7cb053b4c4d9f16f24d5f8b6264c341b424ea
7
+ data.tar.gz: 7067e03c873901d428715c792c45dad5434f7710c63bac3de2c1102d25742fed3ec841f290133c5762688b105f2aa164f9a7282d157392ed92935c98a4a7cf7c
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Fred Appelman
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,146 @@
1
+
2
+ <style> body {counter-reset: chapter; } H2:before {content: counter(chapter) ". "; counter-increment: chapter; } H2 {counter-reset: SectionH2; } H3:before {content: counter(chapter) "." counter(SectionH2) " "; counter-increment: SectionH2; } H3 {counter-reset: SectionH3; } H4:before {content: counter(chapter) "." counter(SectionH2) "." counter(SectionH3) " "; counter-increment: SectionH3; } H4 {counter-reset: SectionH4; } H5:before {content: counter(chapter) "." counter(SectionH2) "." counter(SectionH3) "." counter(SectionH4) " "; counter-increment: SectionH4; } H5 {counter-reset: SectionH5; } H6:before {content: counter(chapter) "." counter(SectionH2) "." counter(SectionH3) "." counter(SectionH4) "." counter(SectionH5) " "; counter-increment: SectionH5; } H6 {counter-reset: SectionH6; } </style>
3
+ # Interface
4
+
5
+ This Gem implements the concept of an abstract base class to Ruby.
6
+
7
+ ## References
8
+ This code has been heavily influenced by the code from Mark Bates (<http://metabates.com/2011/02/07/building-interfaces-and-abstract-classes-in-ruby/>) and James Lopez (<https://github.com/bluegod/rint>).
9
+
10
+ The classes provided by Mark Beates are not really "Gem ready". The `AbstractInterface` module referenced `Bicycle`. The work from James Lopez was much closer to what I wanted. The main problem I saw with his approach is that the `must_implement` were have to be provided in the `initialize` method which feels wrong and error prone. IMHO the statements should be on the module level. Hence this Gem to address this and work on the class and module level only.
11
+
12
+ ## Under the hood
13
+ This Gem will rewrite the new method of the class that implements an Interface and that new method will just do the normal init followed by a check if all required methods are implemented.
14
+
15
+ The new method now looks like this:
16
+
17
+ ```ruby
18
+ def self.new(*args, &block)
19
+ obj = self.allocate
20
+ obj.send :initialize, *args, &block
21
+ obj.send :__check_interface_methods
22
+ obj
23
+ end
24
+ ```
25
+
26
+ where `__check_interface_methods()` does the actuall checking to see if the Abstract Interface has been fully implemented.
27
+
28
+ ## Installation
29
+
30
+ Add this line to your application's Gemfile:
31
+
32
+ ```ruby
33
+ gem 'ainterface'
34
+ ```
35
+
36
+ And then execute:
37
+
38
+ $ bundle
39
+
40
+ Or install it yourself as:
41
+
42
+ $ gem install ainterface
43
+
44
+ ## Usage
45
+
46
+ This Gem implements the concept of an abstract interface in `Ruby`.
47
+
48
+ ```ruby
49
+ #!/usr/bin/env ruby
50
+ require 'ainterface'
51
+
52
+ # Define the abtract interface named Wheels
53
+ # Any class that implements Wheels must implement
54
+ # the methods 'number_of_wheels' and 'diameter'
55
+ module Wheels
56
+ must_implement :number_of_wheels
57
+ must_implement :diameter
58
+ end
59
+
60
+ # This class implments wheels.
61
+ class Car
62
+ implements Wheels
63
+ def number_of_wheels
64
+ 4
65
+ end
66
+ def diameter
67
+ 13
68
+ end
69
+ end
70
+
71
+ car = Car.new
72
+ ```
73
+
74
+ In the above example Car fullfills the Wheels contract and will not raise any error.
75
+
76
+ if for example the following code would have been written:
77
+
78
+ ```ruby
79
+ class Bicycle
80
+ implements Wheels
81
+ def number_of_wheels
82
+ 2
83
+ end
84
+ end
85
+
86
+ bicycle = Bicycle.new
87
+ ```
88
+
89
+ The following error message would have been thrown:
90
+
91
+ (eval):4:in `block in __check_interface_methods': Expected Bicycle to implement diameter for interface Wheels (AInterface::Error::NotImplementedError)
92
+ from (eval):2:in `each'
93
+ from (eval):2:in `__check_interface_methods'
94
+ from (eval):4:in `new'
95
+
96
+ In the above example `Wheels` was a module. If desired an Interface can also be implemented as a class.
97
+
98
+ ### Similar to include
99
+ Any method that was defined by the Interface module is also added
100
+ to the class that implements the interface
101
+
102
+ For example:
103
+
104
+ ```ruby
105
+ module Geometry
106
+ must_implement :width
107
+ must_implement :height
108
+
109
+ def outline
110
+ 2 * width + 2 * height
111
+ end
112
+ end
113
+
114
+ class Rectangle
115
+ implements Geometry
116
+ def width
117
+ 4
118
+ end
119
+ def height
120
+ 3
121
+ end
122
+ end
123
+
124
+ rect = Rectangle.new
125
+ p (rect.methods - Object.methods).sort
126
+ puts "Outline = #{rect.outline}"
127
+ ```
128
+
129
+ will product the following output:
130
+
131
+ ```ruby
132
+ [:height, :outline, :width]
133
+ Outline = 14
134
+ ```
135
+
136
+ As such `implements` acts as an `include` statement.
137
+
138
+ ## Options
139
+ The environment variable
140
+
141
+ ```shell
142
+ DISABLE_RUBY_INTERFACE=1
143
+ ```
144
+
145
+ can be set in order to globally disable the abstract interfaces - no Error will get thrown. This might be particularly useful in production for performance reasons if we are confident enough through tests that the interfaces are all implemented.
146
+
@@ -0,0 +1,137 @@
1
+ #
2
+ # Monkey patch the standard Module class
3
+ class Module
4
+ # Holds all methods that are in an interface
5
+ # for a given interface
6
+ # Use a long name to prevent accidental name conflicts
7
+ @@__interface_methods_per_interface = {}
8
+
9
+ # Called by a class to indicate it implements the specified
10
+ # interface(s)
11
+ #
12
+ # @param [AInterface] args One or more interface names that
13
+ # are being implemented
14
+ #
15
+ # @return [void]
16
+ #
17
+ def implements(*args)
18
+ raise AInterface::Error::MissingArgument if args == []
19
+ # Gather the methods from all specified interfaces
20
+ m = []
21
+ args.each do |module_name|
22
+ raise AInterface::Error::NosuchInterface, module_name.to_s unless
23
+ @@__interface_methods_per_interface[module_name.to_s]
24
+ @@__interface_methods_per_interface[module_name.to_s].each do |method_name|
25
+ m << [module_name.name, method_name]
26
+ end
27
+ end
28
+
29
+ # Create a method that checks for the existence
30
+ # of all methods
31
+ t = <<-EOF
32
+ def __check_interface_methods
33
+ #{m}.each do |module_name, method_name|
34
+ next if respond_to? method_name
35
+ raise AInterface::Error::NotImplementedError.new(
36
+ self.class.name,
37
+ method_name,
38
+ module_name
39
+ )
40
+ end
41
+ end
42
+ private :__check_interface_methods
43
+ EOF
44
+ class_eval t unless disabled_interface?
45
+
46
+ # Redefine the new method
47
+ t = <<-EOF
48
+ def self.new(*args, &block)
49
+ obj = self.allocate
50
+ obj.send :initialize, *args, &block
51
+ obj.send :__check_interface_methods
52
+ obj
53
+ end
54
+ EOF
55
+ class_eval t unless disabled_interface?
56
+
57
+ args.each do |module_name|
58
+ prepend module_name
59
+ end
60
+ end
61
+
62
+ # Errors raised here identify the class_name that is not implementing
63
+ # the method required by the Interface.
64
+ #
65
+ #
66
+ # Specifies that an method for an interface must be
67
+ # implemented
68
+ #
69
+ # @param [Symbol] args One ore more methods that need implementation
70
+ #
71
+ # @return [void]
72
+ #
73
+ def must_implement(*args)
74
+ raise AInterface::Error::MissingArgument if args == []
75
+ @@__interface_methods_per_interface[self.name] ||= []
76
+ args.each do |method_name|
77
+ @@__interface_methods_per_interface[self.name] << method_name
78
+ end
79
+ end
80
+
81
+ #
82
+ # Checks if the Interface error message must be surpressed
83
+ #
84
+ #
85
+ # @return [Boolean] Return true if error messages should be
86
+ # surpressed.
87
+ #
88
+ def disabled_interface?
89
+ ENV['DISABLE_RUBY_INTERFACE'] == '1'
90
+ end
91
+ private :disabled_interface?
92
+ end
93
+
94
+ #
95
+ # Module AInterface provides some helper classes for the
96
+ # abstract interface implementation.
97
+ #
98
+ # @author Fred Appelman <fred@appelman.net>
99
+ #
100
+ module AInterface
101
+ #
102
+ # Module Error provides error message handlers for the
103
+ # abstract interface implementation.
104
+ #
105
+ # @author Fred Appelman <fred@appelman.net>
106
+ #
107
+ module Error
108
+ #
109
+ # Class MissingArgument is raised when must_implement or
110
+ # implements is issued without an argument.
111
+ #
112
+ # @author Fred Appelman <fred@appelman.net>
113
+ #
114
+ class MissingArgument < StandardError; end
115
+
116
+ #
117
+ # Class NosuchInterface is raised when implements
118
+ # references a Interface that wasn't defined.
119
+ #
120
+ # @author Fred Appelman <fred@appelman.net>
121
+ #
122
+ class NosuchInterface < StandardError; end
123
+
124
+ # Raised when a method has not been implemented by a class that
125
+ # has used the implements <InterfaceName> method.
126
+ class NotImplementedError < NoMethodError
127
+ def initialize(class_name, method_name, interface_name)
128
+ super(error_message(class_name, method_name, interface_name), method_name)
129
+ end
130
+
131
+ def error_message(class_name, method_name, interface_name)
132
+ "Expected #{class_name} to implement #{method_name} for interface #{interface_name}"
133
+ end
134
+ private :error_message
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,9 @@
1
+ #
2
+ # Module AInterfaceVersion provides a version number
3
+ # for the gem AInterface
4
+ #
5
+ module AInterfaceVersion
6
+ #
7
+ # @return [String] The application version number
8
+ NUMBER = '1.0'
9
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ainterface
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Fred Appelman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Provides an abstract interface to Ruby
14
+ email: rubygems@appelman.net
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE.txt
20
+ - README.md
21
+ - lib/ainterface.rb
22
+ - lib/ainterface/version.rb
23
+ homepage: https://bitbucket.org/fappelman/ainterface
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ post_install_message:
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubyforge_project:
43
+ rubygems_version: 2.4.6
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: Provides the concept of an Abstract Interface for the Ruby language.
47
+ test_files: []
48
+ has_rdoc: false