duck_enforcer 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 +7 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +20 -0
- data/README.md +147 -0
- data/duck_enforcer.gemspec +40 -0
- data/lib/duck_enforcer.rb +45 -0
- data/lib/duck_enforcer/version.rb +14 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2ec0abe7bb33ad27ec9683838b48c84c617e0ab7
|
4
|
+
data.tar.gz: 0cfba434337fac107af0952219b7d0421766362a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cb42ea35c07adc6799a8f01eaa3f8735b8ca51d7e1fddc7323a81a9f02a742f16d27cdac71f3a6db238f3ae07f94d5ad177b6e55e1deb9d6744875cf5f66a37a
|
7
|
+
data.tar.gz: ce6cf25fba9675f17f9ac6ebca271c3e144453e5160485537fc3f2fd26866841057c34257404a693e09d2230fb6eca4505623c437b952077072d4f8ae271eb24
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.2.5)
|
5
|
+
rspec (3.5.0)
|
6
|
+
rspec-core (~> 3.5.0)
|
7
|
+
rspec-expectations (~> 3.5.0)
|
8
|
+
rspec-mocks (~> 3.5.0)
|
9
|
+
rspec-core (3.5.2)
|
10
|
+
rspec-support (~> 3.5.0)
|
11
|
+
rspec-expectations (3.5.0)
|
12
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
13
|
+
rspec-support (~> 3.5.0)
|
14
|
+
rspec-mocks (3.5.0)
|
15
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
16
|
+
rspec-support (~> 3.5.0)
|
17
|
+
rspec-support (3.5.0)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
rspec
|
24
|
+
|
25
|
+
BUNDLED WITH
|
26
|
+
1.12.5
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012-14 Bozhidar Batsov
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# DuckEnforcer
|
2
|
+
|
3
|
+
_Add a java smell to your ruby_
|
4
|
+
|
5
|
+
Working on a project with a lots of other developer ? No longer wanting to explain what a duck is meant to be able to do ? DuckEnforcer might be for you !
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class Quacker < DuckEnforcer
|
9
|
+
implement :quack
|
10
|
+
end
|
11
|
+
|
12
|
+
class GoodDuck
|
13
|
+
def quack() puts 'quack'; end
|
14
|
+
quacks_like_a! Quacker # OK, all good
|
15
|
+
end
|
16
|
+
|
17
|
+
class BadDuck
|
18
|
+
def waddle() puts 'waddle'; end
|
19
|
+
quacks_like_a! Quacker # Raise a NotImplementedError
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Easy as pie:
|
26
|
+
|
27
|
+
```
|
28
|
+
gem install duck_enforcer
|
29
|
+
```
|
30
|
+
|
31
|
+
Or in a Gemfile:
|
32
|
+
|
33
|
+
```
|
34
|
+
gem 'duck_enforcer'
|
35
|
+
```
|
36
|
+
|
37
|
+
and finally:
|
38
|
+
|
39
|
+
```
|
40
|
+
require 'duck_enforcer'
|
41
|
+
```
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
Using DuckEnforcer is a four steps process:
|
46
|
+
|
47
|
+
1. Define your interface
|
48
|
+
2. Write your implementations
|
49
|
+
3. ...
|
50
|
+
4. Profit.
|
51
|
+
|
52
|
+
### Defining an interface
|
53
|
+
|
54
|
+
Just define your interface as a class inheriting from DuckEnforcer.
|
55
|
+
|
56
|
+
Add an `implement` statement per needed method.
|
57
|
+
|
58
|
+
Exemple of an Observer interface:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
require 'duck_enforcer'
|
62
|
+
|
63
|
+
class MyObserver < DuckEnforcer
|
64
|
+
implement :update
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
### Writing implementations
|
69
|
+
|
70
|
+
To (kind of) 'implement' the interface, you just have to write a class defining an instance method per 'implement' statement, and (optionally) add the `quacks_like_a!` statement:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
class LoggerObserver
|
74
|
+
def update(value)
|
75
|
+
puts value
|
76
|
+
end
|
77
|
+
|
78
|
+
quacks_like_a! MyObserver
|
79
|
+
end
|
80
|
+
|
81
|
+
class RecorderObserver
|
82
|
+
attr_reader :values
|
83
|
+
|
84
|
+
def update(value)
|
85
|
+
(@values ||= []) << value
|
86
|
+
end
|
87
|
+
|
88
|
+
quacks_like_a! MyObserver
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
If you add another method to your interface, your script will fail while loading the classes that miss the new method, making it far more easy to update a duck typing based hierarchy.
|
93
|
+
|
94
|
+
### Scope restriction
|
95
|
+
|
96
|
+
In order to avoid leaking your abstrations, you can as well use the `as_a` helper, that encapsulate the callee in a DuckEnforcer wrapper, limiting access to interface methods only (plus Object instance methods).
|
97
|
+
|
98
|
+
Example:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
class ConcreteObserver
|
102
|
+
def update(value)
|
103
|
+
log(value)
|
104
|
+
end
|
105
|
+
|
106
|
+
def log(value)
|
107
|
+
puts value
|
108
|
+
end
|
109
|
+
|
110
|
+
quacks_like_a! MyObserver
|
111
|
+
end
|
112
|
+
|
113
|
+
class Foobar
|
114
|
+
def initialize(observer)
|
115
|
+
# here, we are encapsulating observer in a DuckEnforcer wrapper
|
116
|
+
@observer = observer.as_a MyObserver
|
117
|
+
end
|
118
|
+
|
119
|
+
def bar=(value)
|
120
|
+
@observer.log('Failure') # That call will raise a NoMethodError
|
121
|
+
@observer.update(value) # That call will succeed
|
122
|
+
@bar = value
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
In the previous example, to get rid of the NoMethodError, you have to either:
|
128
|
+
|
129
|
+
* Add an `implement :log` statement in MyObserver (worst solution so far, as you are delegating two responsibilities to implementing objects: logging and update processing)
|
130
|
+
* Remove the log call, and inject a logger object in your Foobar constructor (cleaner).
|
131
|
+
|
132
|
+
Last interesting remark: an object can quack like a tons of things...
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class Writable
|
136
|
+
implement :write
|
137
|
+
end
|
138
|
+
|
139
|
+
class Readable
|
140
|
+
implement :read
|
141
|
+
end
|
142
|
+
|
143
|
+
class File
|
144
|
+
# ...
|
145
|
+
quacks_like_a! Writable, Readable
|
146
|
+
end
|
147
|
+
```
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
4
|
+
require 'duck_enforcer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'duck_enforcer'
|
8
|
+
s.version = DuckEnforcer::Version::STRING
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.required_ruby_version = '>= 2.0.0'
|
11
|
+
s.authors = ['Alexandre Ignjatovic']
|
12
|
+
s.description = <<-EOF
|
13
|
+
A duck typing enforcer.
|
14
|
+
EOF
|
15
|
+
|
16
|
+
s.email = 'alexandre.ignjatovic@gmail.com'
|
17
|
+
s.files = `git ls-files`.split($RS).reject do |file|
|
18
|
+
file =~ %r{^(?:
|
19
|
+
spec/.*
|
20
|
+
|Gemfile
|
21
|
+
|Rakefile
|
22
|
+
|\.rspec
|
23
|
+
|\.gitignore
|
24
|
+
|\.rubocop.yml
|
25
|
+
|\.rubocop_todo.yml
|
26
|
+
|.*\.eps
|
27
|
+
)$}x
|
28
|
+
end
|
29
|
+
s.test_files = []
|
30
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
31
|
+
s.extra_rdoc_files = ['LICENSE.txt', 'README.md']
|
32
|
+
s.homepage = 'http://github.com/bankair/duck_enforcer'
|
33
|
+
s.licenses = ['MIT']
|
34
|
+
s.require_paths = ['lib']
|
35
|
+
s.rubygems_version = '1.8.23'
|
36
|
+
|
37
|
+
s.summary = 'A ruby microframework to enforce duck typing.'
|
38
|
+
|
39
|
+
s.add_development_dependency('rspec', '~> 3.4')
|
40
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'duck_enforcer/version'
|
3
|
+
|
4
|
+
# DuckEnforcer base class
|
5
|
+
class DuckEnforcer
|
6
|
+
require 'forwardable'
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def self.implement(*args)
|
10
|
+
def_delegators :@obj, *args
|
11
|
+
Array(args).each { |method| (@methods ||= []) << method }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.check_conformity!(klass)
|
15
|
+
@methods.each do |method|
|
16
|
+
unless klass.instance_methods.include? method
|
17
|
+
raise(NotImplementedError, "Missing method #{method} in class #{klass}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(obj)
|
23
|
+
self.class.check_conformity! obj.class
|
24
|
+
@obj = obj
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add the Class.quacks_like_a! helper method
|
29
|
+
class Class
|
30
|
+
def quacks_like_a!(*args)
|
31
|
+
Array(args).each do |klass|
|
32
|
+
unless klass.ancestors.include? DuckEnforcer
|
33
|
+
raise(ArgumentError, "#{klass.inspect} is not a DuckEnforcer")
|
34
|
+
end
|
35
|
+
klass.check_conformity!(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add the Object#as_a helper method
|
41
|
+
class Object
|
42
|
+
def as_a(klass)
|
43
|
+
klass.new(self)
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: duck_enforcer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexandre Ignjatovic
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.4'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.4'
|
27
|
+
description: " A duck typing enforcer.\n"
|
28
|
+
email: alexandre.ignjatovic@gmail.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE.txt
|
33
|
+
- README.md
|
34
|
+
files:
|
35
|
+
- Gemfile.lock
|
36
|
+
- LICENSE.txt
|
37
|
+
- README.md
|
38
|
+
- duck_enforcer.gemspec
|
39
|
+
- lib/duck_enforcer.rb
|
40
|
+
- lib/duck_enforcer/version.rb
|
41
|
+
homepage: http://github.com/bankair/duck_enforcer
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 2.0.0
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.5.1
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: A ruby microframework to enforce duck typing.
|
65
|
+
test_files: []
|