abstractable 1.0.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 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