abstractable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8bd935180782c9239522ca5b1620e0df57ed3711
4
+ data.tar.gz: 783fb41773e04a0dab097fe83b1f3917c46d2dba
5
+ SHA512:
6
+ metadata.gz: 4961394ef67b57d5844633e51b9c152dbe47cb6d1dc4ddad9f150ec853676be4a0f353b4b7e7ac6009119422f121c56ff3324861d262a4cd936bc8e318b30117
7
+ data.tar.gz: c71bf70ef9481d8e9786d3471c806c98c4ed856f21df818d89f088b2b4ab5d72ccd99cdd2f7174f78f48184b59f5f4291c51bc32d3ab3a400111dd8a49e45cb6
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ /.idea/
16
+ /Guardfile
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,41 @@
1
+ HashSyntax:
2
+ EnforcedStyle: ruby19
3
+ SupportedStyles:
4
+ - ruby19
5
+ - hash_rockets
6
+
7
+ Documentation:
8
+ Enabled: false
9
+
10
+ LineLength:
11
+ Enabled: false
12
+
13
+ ClassLength:
14
+ Enabled: false
15
+
16
+ RegexpLiteral:
17
+ MaxSlashes: 0
18
+
19
+ GuardClause:
20
+ MinBodyLength: 3
21
+
22
+ TrivialAccessors:
23
+ AllowPredicates: true
24
+
25
+ RedundantReturn:
26
+ AllowMultipleReturnValues: true
27
+
28
+ RedundantBegin:
29
+ Enabled: false
30
+
31
+ SpaceInsideHashLiteralBraces:
32
+ EnforcedStyle: no_space
33
+
34
+ Style/Lambda:
35
+ Enabled: false
36
+
37
+ Style/Proc:
38
+ Enabled: false
39
+
40
+ StringLiterals:
41
+ EnforcedStyle: double_quotes
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ bundler_args: --without development
2
+ language: ruby
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1
7
+ script: bundle exec rspec
8
+ branches:
9
+ only:
10
+ - master
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rake", ">= 0.9"
4
+ gem "rdoc", ">= 3.9"
5
+
6
+ group :development do
7
+ gem "guard-rubocop"
8
+ gem "terminal-notifier-guard"
9
+ gem "flay"
10
+ gem "flog"
11
+ end
12
+
13
+ group :test do
14
+ gem "coveralls", require: false
15
+ gem "rubocop", ">= 0.19"
16
+ gem "rspec", ">= 3"
17
+ gem "simplecov", ">= 0.9"
18
+ end
19
+
20
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 pujoheadsoft
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.
data/README.md ADDED
@@ -0,0 +1,235 @@
1
+ # Abstractable
2
+
3
+ [![Build Status](http://img.shields.io/travis/pujoheadsoft/abstractable.svg)][travis]
4
+ [![Coverage Status](http://img.shields.io/coveralls/pujoheadsoft/abstractable.svg)][coveralls]
5
+ [![Code Climate](http://img.shields.io/codeclimate/github/pujoheadsoft/abstractable.svg)][codeclimate]
6
+
7
+ [travis]: http://travis-ci.org/pujoheadsoft/abstractable
8
+ [coveralls]: https://coveralls.io/r/pujoheadsoft/abstractable
9
+ [codeclimate]: https://codeclimate.com/github/pujoheadsoft/abstractable
10
+
11
+ ## Overview
12
+ Abstractable is Library for define abstract method.
13
+ Can know unimplemented abstract methods by fail fast as possible.
14
+ This mechanism is very useful for prevent the implementation leakage.
15
+ ```ruby
16
+ require "abstractable"
17
+ class AbstractDriver
18
+ extend Abstractable
19
+ abstract :open, :close
20
+ end
21
+
22
+ class AbstractIODriver < AbstractDriver
23
+ abstract :read, :write
24
+ end
25
+
26
+ class NotImplIODriver < AbstractIODriver; end
27
+
28
+ NotImplIODriver.new
29
+ # => following abstract methods are not implemented. (NotImplementedError)
30
+ # [:open, :close] defined in AbstractDriver
31
+ # [:read, :write] defined in AbstractIODriver
32
+ ```
33
+ See sample above. error occurred at call *new* method.
34
+ Info of unimplemented abstract method have been included in the error message.
35
+
36
+ ## Installation
37
+ gem install abstractable
38
+
39
+ ## How to use
40
+ First, call *require "abstractable"*.
41
+ Then, *extend Abstractable*.
42
+ ```ruby
43
+ require "abstractable"
44
+ class AbstractClass
45
+ extend Abstractable
46
+ end
47
+ ```
48
+ **Note:** This document omit the *require "abstractable"* from now on.
49
+
50
+ ### Define abstract method
51
+
52
+ 1. **Specify *Symbol* (can specify multiple)**
53
+ ```ruby
54
+ class AbstractList
55
+ extend Abstractable
56
+
57
+ abstract :add, :insert, :empty?
58
+ end
59
+ ```
60
+ If want to express the arguments then can write be as follows.
61
+ ```ruby
62
+ class AbstractList
63
+ extend Abstractable
64
+
65
+ def add(value); end
66
+ def insert(index, value); end
67
+ def empty?; end
68
+
69
+ abstract :add, :insert, :empty?
70
+ end
71
+ ```
72
+ 2. **In block**
73
+ Defined methods in block is all treated as an abstract method.
74
+ ```ruby
75
+ class AbstractList
76
+ extend Abstractable
77
+
78
+ abstract do
79
+ def add(value); end
80
+ def insert(index, value); end
81
+ def empty?; end
82
+ end
83
+ end
84
+ ```
85
+ Can also be written as follows.
86
+ ```ruby
87
+ class AbstractList
88
+ extend Abstractable
89
+
90
+ abstract { def add(value); end }
91
+ abstract { def insert(index, value); end }
92
+ abstract { def empty?; end }
93
+ end
94
+ ```
95
+
96
+ ### Get defined abstract methods
97
+ *abstract_methods* returns an array containing the names of abstract methods in the receiver.
98
+ if set *true* to args include ancestors abstract methods (default *true*).
99
+ ```ruby
100
+ class AbstractDriver
101
+ extend Abstractable
102
+ abstract :open, :close
103
+ end
104
+
105
+ class AbstractIODriver < AbstractDriver
106
+ abstract :read, :write
107
+ end
108
+
109
+ AbstractIODriver.abstract_methods # => [:read, :write, :open, :close]
110
+ ```
111
+ If specify *false* for the argument, only abstract methods defined in the receiver is returned.
112
+ ```ruby
113
+ AbstractIODriver.abstract_methods(false) # => [:read, :write]
114
+ ```
115
+ ### Undefine of abstract method
116
+ Can undefine and if you call *Module#undef_method*.
117
+ However, the receiver must be a class that defines an abstract method.
118
+ (Can't be undefine of the abstract methods of the parent class from a subclass.)
119
+ ```ruby
120
+ class Parent
121
+ extend Abstractable
122
+ abstract :greet
123
+ end
124
+
125
+ class Child < Parent
126
+ undef_method :greet
127
+ end
128
+
129
+ begin
130
+ Child.new # => NotImplementedError
131
+ rescue NotImplementedError
132
+ end
133
+
134
+ class Parent
135
+ undef_method :greet
136
+ end
137
+
138
+ Child.new # => OK
139
+ ```
140
+ ### Validation of unimplemented abstract method do always called?
141
+ Once you have confirmed that the unimplemented methods do not exist, validation does not take place.
142
+ if defined abstract method later, then perform validation once more.
143
+ *required_validate?* returns true if required unimplemented abstract methods validation.
144
+ ```ruby
145
+ class AbstractParent
146
+ extend Abstractable
147
+ abstract :greet
148
+ end
149
+
150
+ class Child < AbstractParent
151
+ def greet; end
152
+ end
153
+
154
+ Child.required_validate? # => true
155
+ Child.new
156
+ Child.required_validate? # => false
157
+
158
+ # define abstract method
159
+ AbstractParent.abstract :execute
160
+
161
+ Child.required_validate? # => true
162
+ ```
163
+ #### If define environment variable *ABSTRACTABLE_IGNORE_VALIDATE*
164
+ This case is *required_validate?* returns true always.
165
+ *ABSTRACTABLE_IGNORE_VALIDATE* value allow any value.
166
+ ```
167
+ # Linux
168
+ export ABSTRACTABLE_IGNORE_VALIDATE=true
169
+ # windows
170
+ set ABSTRACTABLE_IGNORE_VALIDATE=true
171
+ ```
172
+ ### Abstract method of singleton class
173
+ Abstract method of singleton class can't validation at new.
174
+ Therefore throw error at abstract method call.
175
+ But can find unimplemented abstract methods.
176
+ ```ruby
177
+ class AbstractParent
178
+ class << self
179
+ extend Abstractable
180
+ abstract :greet
181
+ end
182
+ end
183
+
184
+ class Child < AbstractParent
185
+ end
186
+
187
+ Child.greet # => greet is abstract method defined in
188
+ # #<Class:AbstractParent>, and must implement.
189
+ # (NotImplementedError)
190
+ ```
191
+ ### Do explicitly validation
192
+ if call *validate_not_implemented_abstract_methods* then can do explicitly validation.
193
+ ```ruby
194
+ class AbstractParent
195
+ extend Abstractable
196
+ abstract :greet
197
+ end
198
+
199
+ class Child < AbstractParent; end
200
+
201
+ # explicitly validation
202
+ Child.validate_not_implemented_abstract_methods
203
+ # => following abstract methods are not implemented. (NotImplementedError)
204
+ # [:greet] defined in AbstractParent
205
+ ```
206
+ ### Find unimplemented abstract methods
207
+ *abstractable#find_not_implemented_info* returns a following format Hash
208
+ ```ruby
209
+ {abstract_class => array of unimplemented abstract methods, ...}
210
+ ```
211
+ ```ruby
212
+ class AbstractParent
213
+ extend Abstractable
214
+ abstract :greet
215
+ end
216
+
217
+ class Child < AbstractParent; end
218
+
219
+ Abstractable.find_not_implemented_info(Child) # => {AbstractParent=>[:greet]}
220
+ ```
221
+ If call *find_not_implemented_info_from_singleton*,
222
+ Then can find unimplemented abstract methods of singleton class.
223
+ ```ruby
224
+ class AbstractParent
225
+ class << self
226
+ extend Abstractable
227
+ abstract :greet
228
+ end
229
+ end
230
+
231
+ class Child < AbstractParent; end
232
+
233
+ Abstractable.find_not_implemented_info_from_singleton(Child)
234
+ # => {#<Class:AbstractParent>=>[:greet]}
235
+ ```
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task default: :spec
4
+
5
+ task quality: [:flog, :flay]
6
+
7
+ Dir.glob("tasks/*.rake").each { |each| import each }
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "abstractable/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "abstractable"
8
+ spec.version = Abstractable::VERSION
9
+ spec.authors = ["Kenji Suzuki"]
10
+ spec.email = ["pujoheadsoft@gmail.com"]
11
+ spec.summary = "Library for define abstract method."
12
+ spec.description = "Library for define abstract method. Can know unimplemented abstract methods by fail fast as possible. This mechanism is very useful for prevent the implementation leakage."
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec"
23
+ end
@@ -0,0 +1,49 @@
1
+ require "abstractable"
2
+ require "abstractable/pedigree_stream"
3
+
4
+ module Abstractable
5
+ # this class can find to information of unimplemented abstract method
6
+ class NotImplementedInfoFinder
7
+ # find(klass) -> hash
8
+ #
9
+ # Returns an hash as information of unimplemented abstract method.
10
+ # hash format is {class => array of unimplemented method in class, ... }
11
+ def find(klass)
12
+ find_from_pedigree_stream(PedigreeStream.new(klass))
13
+ end
14
+
15
+ # find_from_singleton(klass) -> hash
16
+ # singleton_class version find.
17
+ def find_from_singleton(klass)
18
+ find_from_pedigree_stream(SingletonPedigreeStream.new(klass))
19
+ end
20
+
21
+ private
22
+
23
+ def find_from_pedigree_stream(pedigree_stream)
24
+ pedigree_stream.each_with_descendants_and_object(create_deny_empty_array_hash) do |klass, descendants, hash|
25
+ hash[klass] = find_from_ancestor_and_descendants(klass, descendants) if need_find?(klass, descendants)
26
+ end
27
+ end
28
+
29
+ def create_deny_empty_array_hash
30
+ hash = {}
31
+ def hash.[]=(key, value)
32
+ super(key, value) unless value.empty?
33
+ end
34
+ hash
35
+ end
36
+
37
+ def need_find?(ancestor, descendants)
38
+ ancestor.is_a?(Abstractable) && 0 < descendants.size
39
+ end
40
+
41
+ def find_from_ancestor_and_descendants(ancestor, descendants)
42
+ ancestor.abstract_methods(false).reject { |method| descendants.any?(&individual_method_defined?(method)) }
43
+ end
44
+
45
+ def individual_method_defined?(method)
46
+ lambda { |klass| klass.instance_methods(false).include? method }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,63 @@
1
+ module Abstractable
2
+ # this class represent stream of pedigree to myself from an ancestor.
3
+ class PedigreeStream
4
+ include Enumerable
5
+
6
+ def initialize(klass)
7
+ fail ArgumentError, "wrong type argument #{klass} (should be Class) " unless klass.is_a? Class
8
+ self.pedigree_stream = pedigree_stream_of(klass)
9
+ end
10
+
11
+ # each for Enumerable.
12
+ def each
13
+ pedigree_stream.each { |klass| yield klass }
14
+ end
15
+
16
+ # each with descendants
17
+ #
18
+ # Example of use:
19
+ # each_with_descendants do |klass, descendants_of_klass|
20
+ # your code...
21
+ # end
22
+ def each_with_descendants
23
+ each do |klass|
24
+ yield(klass, descendants_of(klass))
25
+ end
26
+ end
27
+
28
+ # each_with_object with descendants.
29
+ #
30
+ # Example of use:
31
+ # each_with_descendants_and_object([]) do |klass, descendants_of_klass, array|
32
+ # your code...
33
+ # end
34
+ def each_with_descendants_and_object(object)
35
+ each_with_descendants do |klass, descendants_of_klass|
36
+ yield(klass, descendants_of_klass, object)
37
+ end
38
+ object
39
+ end
40
+
41
+ # descendants_of(klass) -> array
42
+ # Returns an array of descendants name of class.
43
+ def descendants_of(klass)
44
+ i = pedigree_stream.index(klass)
45
+ i ? pedigree_stream.drop(i + 1) : []
46
+ end
47
+
48
+ protected
49
+
50
+ attr_accessor :pedigree_stream
51
+
52
+ def pedigree_stream_of(klass)
53
+ klass.ancestors.reverse
54
+ end
55
+ end
56
+
57
+ # PedigreeStream of Singleton Class.
58
+ class SingletonPedigreeStream < PedigreeStream
59
+ def pedigree_stream_of(klass)
60
+ klass.ancestors.reverse.map(&:singleton_class)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module Abstractable
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,128 @@
1
+ require "abstractable/version"
2
+ require "abstractable/not_implemented_info_finder"
3
+
4
+ # abstract method support module.
5
+ module Abstractable
6
+ class WrongOperationError < StandardError; end
7
+
8
+ def included(base)
9
+ base.extend(Abstractable)
10
+ end
11
+
12
+ # new after unimplemented abstract methods validation.
13
+ def new(*args, &block)
14
+ validate_on_create(:new)
15
+ super(*args, &block)
16
+ end
17
+
18
+ # allocate after unimplemented abstract methods validation.
19
+ def allocate(*args, &block)
20
+ validate_on_create(:allocate)
21
+ super(*args, &block)
22
+ end
23
+
24
+ # Define abstract methods.
25
+ #
26
+ # Example:
27
+ # abstract :execute # one symbol
28
+ # abstract :method1, :method2 # multiple symbol
29
+ # # in block
30
+ # abstract do
31
+ # def method3; end
32
+ # def method4; end
33
+ # end
34
+ def abstract(*names, &block)
35
+ add_abstract_methods(*names.compact)
36
+ add_abstract_methods_by_block(&block) if block_given?
37
+ end
38
+
39
+ # abstract_methods(all=true) -> array
40
+ #
41
+ # Returns an array containing the names of abstract methods in the receiver.
42
+ # if set true to args include ancestors abstract methods.
43
+ # (default true)
44
+ #
45
+ def abstract_methods(all = true)
46
+ return individual_abstract_methods unless all
47
+ collect = lambda { |klass, array| array.push(*klass.abstract_methods(false)) if klass.is_a? Abstractable }
48
+ individual_abstract_methods + (ancestors - [self]).each_with_object([], &collect)
49
+ end
50
+
51
+ # Unimplemented abstract methods validation.
52
+ #
53
+ # if found unimplemented methods then throw NotImplementedError.
54
+ def validate_not_implemented_abstract_methods
55
+ not_impl_info = Abstractable.find_not_implemented_info(self)
56
+ fail NotImplementedError, build_error_message(not_impl_info) unless not_impl_info.empty?
57
+ @implemented_abstract_methods = abstract_methods
58
+ end
59
+
60
+ # required_validate? -> true or false
61
+ #
62
+ # Returns <code>true</code> if required unimplemented abstract methods validation.
63
+ #
64
+ # if validated or if defined environment variable <code>ABSTRACTABLE_IGNORE_VALIDATE</code>
65
+ # then always return true.
66
+ def required_validate?
67
+ !ENV["ABSTRACTABLE_IGNORE_VALIDATE"] && @implemented_abstract_methods != abstract_methods
68
+ end
69
+
70
+ # called when the method is undef.
71
+ def method_undefined(method)
72
+ individual_abstract_methods.delete(method)
73
+ end
74
+
75
+ # Shortcut to NotImplementedInfoFinder.new.find(klass)
76
+ def self.find_not_implemented_info(klass)
77
+ NotImplementedInfoFinder.new.find(klass)
78
+ end
79
+
80
+ # Shortcut to NotImplementedInfoFinder.new.find_from_singleton(klass)
81
+ def self.find_not_implemented_info_from_singleton(klass)
82
+ NotImplementedInfoFinder.new.find_from_singleton(klass)
83
+ end
84
+
85
+ private
86
+
87
+ def individual_abstract_methods
88
+ @individual_abstract_methods ||= []
89
+ end
90
+
91
+ def validate_on_create(create_method_name)
92
+ validate_not_implemented_abstract_methods if required_validate?
93
+ if is_a?(Abstractable) && 0 < abstract_methods(false).size
94
+ fail WrongOperationError, "#{self} has abstract methods. and therefore can't call #{create_method_name}."
95
+ end
96
+ end
97
+
98
+ def build_error_message(not_implemented_infos)
99
+ messages = ["following abstract methods are not implemented."]
100
+ formatter = Proc.new { |klass, methods| "#{methods} defined in #{klass}" }
101
+ messages.push(*not_implemented_infos.map(&formatter)).join("\n")
102
+ end
103
+
104
+ def define_abstract_skeleton(method)
105
+ this = self
106
+ class_eval do
107
+ define_method method do |*_|
108
+ fail NotImplementedError, "#{method} is abstract method defined in #{this}, and must implement."
109
+ end
110
+ end
111
+ end
112
+
113
+ def add_abstract_methods_by_block(&block)
114
+ old_instance_methods = instance_methods(false)
115
+ block.call
116
+ add_abstract_methods(*(instance_methods(false) - old_instance_methods))
117
+ end
118
+
119
+ def add_abstract_methods(*methods)
120
+ methods.each { |method| add_abstract_method(method) }
121
+ end
122
+
123
+ def add_abstract_method(method)
124
+ fail ArgumentError, "wrong type argument #{method} (should be Symbol) " unless method.is_a? Symbol
125
+ individual_abstract_methods << method
126
+ define_abstract_skeleton(method)
127
+ end
128
+ end
@@ -0,0 +1,310 @@
1
+ require "spec_helper"
2
+
3
+ describe Abstractable, "Simple" do
4
+
5
+ before(:context) do
6
+
7
+ class AbstractList
8
+ extend Abstractable
9
+ abstract :size, :empty?, :add
10
+ end
11
+
12
+ class NotImplList < AbstractList; end
13
+
14
+ class OneImplList < AbstractList
15
+ def size; end
16
+ end
17
+
18
+ class AllImplList < OneImplList
19
+ def empty?; end
20
+
21
+ def add; end
22
+ end
23
+
24
+ end
25
+
26
+ it "all implemented abstract methods" do
27
+ expect { AllImplList.new }.not_to raise_error
28
+ expect { AllImplList.allocate }.not_to raise_error
29
+ end
30
+
31
+ it "not implemented abstract methods" do
32
+ expect do
33
+ NotImplList.new
34
+ end.to raise_error(NotImplementedError, <<-EOF.gsub(/^\s+|\n$/, ""))
35
+ following abstract methods are not implemented.
36
+ [:size, :empty?, :add] defined in AbstractList
37
+ EOF
38
+ end
39
+
40
+ it "1 implemented abstract methods" do
41
+ expect do
42
+ OneImplList.new
43
+ end.to raise_error(NotImplementedError, <<-EOF.gsub(/^\s+|\n$/, ""))
44
+ following abstract methods are not implemented.
45
+ [:empty?, :add] defined in AbstractList
46
+ EOF
47
+ end
48
+
49
+ it "new abstract" do
50
+ error_message = "#{AbstractList} has abstract methods. and therefore can't call new."
51
+ expect do
52
+ AbstractList.new
53
+ end.to raise_error(Abstractable::WrongOperationError, error_message)
54
+ end
55
+
56
+ it "allocate abstract" do
57
+ error_message = "#{AbstractList} has abstract methods. and therefore can't call allocate."
58
+ expect do
59
+ AbstractList.allocate
60
+ end.to raise_error(Abstractable::WrongOperationError, error_message)
61
+ end
62
+
63
+ end
64
+
65
+ describe Abstractable, "Complex class hierarchy" do
66
+
67
+ before(:context) do
68
+
69
+ module AbstractAddressHolder
70
+ extend Abstractable
71
+ abstract :city
72
+ abstract :state, :zip
73
+ end
74
+
75
+ module AbstractNameHolder
76
+ extend Abstractable
77
+ abstract do
78
+ def last_name; end
79
+
80
+ def first_name; end
81
+ end
82
+ end
83
+
84
+ class OneImplAddressHolder
85
+ include AbstractAddressHolder
86
+ def state; end
87
+ end
88
+
89
+ class TwoImplAdressAndNameHolder < OneImplAddressHolder
90
+ include AbstractNameHolder
91
+ def first_name; end
92
+ end
93
+
94
+ class AllImplAdressAndNameHolder
95
+ include AbstractAddressHolder
96
+ include AbstractNameHolder
97
+
98
+ def last_name; end
99
+
100
+ def first_name; end
101
+
102
+ def city; end
103
+
104
+ def state; end
105
+
106
+ def zip; end
107
+ end
108
+ end
109
+
110
+ it "all implemented abstract methods" do
111
+ expect { AllImplAdressAndNameHolder.new }.not_to raise_error
112
+ end
113
+
114
+ it "1 implemented abstract methods" do
115
+ expect do
116
+ OneImplAddressHolder.new
117
+ end.to raise_error(NotImplementedError, <<-EOF.gsub(/^\s+|\n$/, ""))
118
+ following abstract methods are not implemented.
119
+ [:city, :zip] defined in AbstractAddressHolder
120
+ EOF
121
+ end
122
+
123
+ it "2 implemented abstract methods" do
124
+ expect do
125
+ TwoImplAdressAndNameHolder.new
126
+ end.to raise_error(NotImplementedError, <<-EOF.gsub(/^\s+|\n$/, ""))
127
+ following abstract methods are not implemented.
128
+ [:city, :zip] defined in AbstractAddressHolder
129
+ [:last_name] defined in AbstractNameHolder
130
+ EOF
131
+ end
132
+
133
+ end
134
+
135
+ describe Abstractable, "Independency" do
136
+
137
+ before(:context) do
138
+ class AbstractFormatter
139
+ extend Abstractable
140
+ abstract :format
141
+ end
142
+
143
+ class AFormatter < AbstractFormatter
144
+ def format; end
145
+ end
146
+
147
+ class BFormatter < AbstractFormatter
148
+ def format; end
149
+ end
150
+
151
+ end
152
+
153
+ it "AFormatter & BFormatter validation is independent" do
154
+ expect(AFormatter.required_validate?).to be_truthy
155
+ expect(BFormatter.required_validate?).to be_truthy
156
+
157
+ AFormatter.new
158
+ expect(AFormatter.required_validate?).to be_falsey
159
+ expect(BFormatter.required_validate?).to be_truthy
160
+
161
+ BFormatter.new
162
+ expect(AFormatter.required_validate?).to be_falsey
163
+ expect(BFormatter.required_validate?).to be_falsey
164
+ end
165
+
166
+ it "Add later abstract method" do
167
+ AbstractFormatter.abstract :close
168
+
169
+ expect { AFormatter.new }.to raise_error(NotImplementedError)
170
+ expect { BFormatter.new }.to raise_error(NotImplementedError)
171
+
172
+ class AFormatter
173
+ def close; end
174
+ end
175
+ expect { AFormatter.new }.not_to raise_error
176
+ expect { BFormatter.new }.to raise_error(NotImplementedError)
177
+
178
+ class BFormatter
179
+ def close; end
180
+ end
181
+ expect { AFormatter.new }.not_to raise_error
182
+ expect { BFormatter.new }.not_to raise_error
183
+ end
184
+
185
+ end
186
+
187
+ describe Abstractable, "delete abstract" do
188
+
189
+ it "delete by undef_method & undef_abstract" do
190
+ class AbstractWriter
191
+ extend Abstractable
192
+ abstract :write, :open, :close
193
+ end
194
+
195
+ class AbstractDocumentWriter < AbstractWriter
196
+ abstract :write_document
197
+ end
198
+
199
+ class AbstractXMLDocumentWriter < AbstractDocumentWriter
200
+ abstract :to_xml
201
+ end
202
+
203
+ class XMLDocumentWriter < AbstractXMLDocumentWriter; end
204
+
205
+ expect { XMLDocumentWriter.new }.to raise_error(NotImplementedError)
206
+
207
+ class AbstractXMLDocumentWriter
208
+ undef_method :to_xml
209
+ end
210
+
211
+ expect(XMLDocumentWriter.abstract_methods).to eq([:write_document, :write, :open, :close])
212
+ expect(AbstractXMLDocumentWriter.abstract_methods).to eq([:write_document, :write, :open, :close])
213
+ expect(AbstractWriter.abstract_methods).to eq([:write, :open, :close])
214
+
215
+ class AbstractDocumentWriter
216
+ undef_method :write_document, :open, :close
217
+ end
218
+
219
+ expect(XMLDocumentWriter.abstract_methods).to eq([:write, :open, :close])
220
+ expect(AbstractXMLDocumentWriter.abstract_methods).to eq([:write, :open, :close])
221
+ expect(AbstractWriter.abstract_methods).to eq([:write, :open, :close])
222
+
223
+ class AbstractWriter
224
+ undef_method :write, :open, :close
225
+ end
226
+
227
+ expect(XMLDocumentWriter.abstract_methods).to eq([])
228
+ expect(AbstractXMLDocumentWriter.abstract_methods).to eq([])
229
+ expect(AbstractWriter.abstract_methods).to eq([])
230
+ end
231
+
232
+ end
233
+
234
+ describe Abstractable, "Class method" do
235
+
236
+ before(:context) do
237
+ class AbstractApplication
238
+ class << self
239
+ extend Abstractable
240
+ abstract :name
241
+ end
242
+ end
243
+
244
+ class NotImplApplication < AbstractApplication; end
245
+
246
+ class Application < AbstractApplication
247
+ def self.name; end
248
+ end
249
+
250
+ end
251
+
252
+ it "not implemented class method" do
253
+ message = "name is abstract method defined in #{AbstractApplication.singleton_class}, and must implement."
254
+ expect do
255
+ NotImplApplication.name
256
+ end.to raise_error(NotImplementedError, message)
257
+ end
258
+
259
+ it "implemented class method" do
260
+ expect { Application.new }.not_to raise_error
261
+ end
262
+
263
+ it "find not implemented info from singleton class" do
264
+ h = {AbstractApplication.singleton_class => [:name]}
265
+ expect(Abstractable.find_not_implemented_info_from_singleton(NotImplApplication)).to eq(h)
266
+ expect(Abstractable.find_not_implemented_info_from_singleton(Application)).to eq({})
267
+ end
268
+
269
+ end
270
+
271
+ describe Abstractable, "Use environment variable: ABSTRACTABLE_IGNORE_VALIDATE" do
272
+
273
+ before(:context) do
274
+ class AbstractPerson
275
+ extend Abstractable
276
+ abstract :say
277
+ end
278
+
279
+ class NotImplPerson < AbstractPerson; end
280
+ end
281
+
282
+ it "if defined ABSTRACTABLE_IGNORE_VALIDATE then ignore validation." do
283
+ expect(ENV).to receive(:[]).with("ABSTRACTABLE_IGNORE_VALIDATE").and_return(true)
284
+ expect { NotImplPerson.new }.not_to raise_error
285
+ end
286
+
287
+ end
288
+
289
+ describe Abstractable::NotImplementedInfoFinder do
290
+
291
+ it "find" do
292
+ class AbstractCollection
293
+ extend Abstractable
294
+ abstract :add, :remove
295
+ end
296
+
297
+ class AbstractQueue < AbstractCollection
298
+ abstract :clear
299
+ end
300
+
301
+ class PriorityQueue < AbstractQueue; end
302
+
303
+ abst_info1 = {AbstractCollection => [:add, :remove]}
304
+ abst_info2 = {AbstractQueue => [:clear]}
305
+ expect(Abstractable::NotImplementedInfoFinder.new.find(PriorityQueue)).to eq(abst_info1.merge(abst_info2))
306
+ expect(Abstractable::NotImplementedInfoFinder.new.find(AbstractQueue)).to eq(abst_info1)
307
+ expect(Abstractable::NotImplementedInfoFinder.new.find(AbstractCollection)).to eq({})
308
+ end
309
+
310
+ end
@@ -0,0 +1,13 @@
1
+ require "simplecov"
2
+ require "coveralls"
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ Coveralls::SimpleCov::Formatter
7
+ ]
8
+ SimpleCov.start do
9
+ add_filter ".bundle/"
10
+ end
11
+
12
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
13
+ require "abstractable"
data/tasks/flay.rake ADDED
@@ -0,0 +1,11 @@
1
+ require "rake/tasklib"
2
+ require "flay"
3
+ require "flay_task"
4
+
5
+ FlayTask.new do |t|
6
+ t.dirs = FileList["lib/**/*.rb"].map do |each|
7
+ each[%r{[^/]+}]
8
+ end.uniq
9
+ t.threshold = 0
10
+ t.verbose = true
11
+ end
data/tasks/flog.rake ADDED
@@ -0,0 +1,18 @@
1
+ require "flog"
2
+
3
+ desc "Analyze for code complexity"
4
+ task :flog do
5
+ flog = Flog.new(continue: true)
6
+ flog.flog(*FileList["lib/**/*.rb"])
7
+ threshold = 28
8
+
9
+ bad_methods = flog.totals.select do |name, score|
10
+ !(/##{flog.no_method}$/ =~ name) && score > threshold
11
+ end
12
+ bad_methods.sort { |a, b| a[1] <=> b[1] }.reverse.each do |name, score|
13
+ printf "%8.1f: %s\n", score, name
14
+ end
15
+ unless bad_methods.empty?
16
+ $stderr.puts "#{bad_methods.size} methods have a complexity > #{threshold}"
17
+ end
18
+ end
data/tasks/rspec.rake ADDED
@@ -0,0 +1,3 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ RSpec::Core::RakeTask.new
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: abstractable
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Kenji Suzuki
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Library for define abstract method. Can know unimplemented abstract methods
42
+ by fail fast as possible. This mechanism is very useful for prevent the implementation
43
+ leakage.
44
+ email:
45
+ - pujoheadsoft@gmail.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".coveralls.yml"
51
+ - ".gitignore"
52
+ - ".rspec"
53
+ - ".rubocop.yml"
54
+ - ".travis.yml"
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - abstractable.gemspec
60
+ - lib/abstractable.rb
61
+ - lib/abstractable/not_implemented_info_finder.rb
62
+ - lib/abstractable/pedigree_stream.rb
63
+ - lib/abstractable/version.rb
64
+ - spec/abstractable_spec.rb
65
+ - spec/spec_helper.rb
66
+ - tasks/flay.rake
67
+ - tasks/flog.rake
68
+ - tasks/rspec.rake
69
+ homepage: ''
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.4.1
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Library for define abstract method.
93
+ test_files:
94
+ - spec/abstractable_spec.rb
95
+ - spec/spec_helper.rb