duckpond 1.0.1 → 1.0.2
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
- data/.rspec +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +15 -1
- data/README.md +40 -14
- data/lib/duckpond/binoculars.rb +2 -2
- data/lib/duckpond/contract.rb +49 -0
- data/lib/duckpond/sighting.rb +2 -2
- data/lib/duckpond/version.rb +1 -1
- data/lib/duckpond.rb +1 -1
- data/spec/binoculars_spec.rb +6 -6
- data/spec/duck_spec.rb +23 -23
- data/spec/sighting_spec.rb +4 -4
- data/spec/the_solution_spec.rb +9 -9
- metadata +6 -5
- data/lib/duckpond/duck.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed77b024ca7fdcad68dff17391aeb0de607e7da9
|
4
|
+
data.tar.gz: e00d0cc54ecc3dea1b83eb7a75bcea3382b784bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f79bd621cd72f4e2ca362633e25c4433ea9ba3f1242dd69ae2410061035768bef3f2c8b38cf91fe924394f01ac6c4801e71fba1fb121bc6eff1504ddc972d75b
|
7
|
+
data.tar.gz: d214ef206b663b6d7b84b7cf05239d4f4105ff68d460934200e53f36e709952c70ba3cb4a1c2bbe2d8ab3f9de4e011665bfb2ed82bf407a27c20a29a531e46d6
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,25 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
duckpond (1.0.
|
4
|
+
duckpond (1.0.2)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
diff-lcs (1.2.5)
|
9
10
|
rake (10.3.2)
|
11
|
+
rspec (3.0.0)
|
12
|
+
rspec-core (~> 3.0.0)
|
13
|
+
rspec-expectations (~> 3.0.0)
|
14
|
+
rspec-mocks (~> 3.0.0)
|
15
|
+
rspec-core (3.0.1)
|
16
|
+
rspec-support (~> 3.0.0)
|
17
|
+
rspec-expectations (3.0.1)
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
19
|
+
rspec-support (~> 3.0.0)
|
20
|
+
rspec-mocks (3.0.1)
|
21
|
+
rspec-support (~> 3.0.0)
|
22
|
+
rspec-support (3.0.0)
|
10
23
|
|
11
24
|
PLATFORMS
|
12
25
|
ruby
|
@@ -15,3 +28,4 @@ DEPENDENCIES
|
|
15
28
|
bundler (~> 1.3)
|
16
29
|
duckpond!
|
17
30
|
rake
|
31
|
+
rspec
|
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[](http://badge.fury.io/rb/duckpond)
|
2
|
+
[](https://travis-ci.org/mikeyhogarth/duckpond)
|
3
|
+
|
1
4
|
# Duckpond
|
2
5
|
|
3
6
|
Explicit duck typing for ruby.
|
@@ -35,40 +38,63 @@ Or install it yourself as:
|
|
35
38
|
|
36
39
|
## Usage
|
37
40
|
|
38
|
-
Usage is demonstrated in 'the_solution_spec', but in a nutshell you can create
|
39
|
-
"
|
40
|
-
extensively as it describes the contract the duck represents.
|
41
|
+
Usage is demonstrated in '[the_solution_spec](spec/the_solution_spec.rb)', but in a nutshell you can create
|
42
|
+
"contract" classes by inheriting from DuckPond::Contract. This file should be commented
|
43
|
+
extensively as it describes the contract the duck represents. The "has_method" method
|
44
|
+
can be used to specify methods as symbols.
|
41
45
|
|
42
|
-
class
|
43
|
-
|
46
|
+
class MyContract < DuckPond::Contract
|
47
|
+
has_method :length
|
48
|
+
has_method :to_s
|
44
49
|
end
|
45
50
|
|
46
|
-
Once you've declared a
|
51
|
+
Once you've declared a contract, you can use "binoculars" to see if objects quack like
|
47
52
|
that duck:
|
48
53
|
|
49
54
|
obj = "Hello World"
|
50
55
|
sighting = DuckPond::Binoculars.identify(obj)
|
51
|
-
sighting.quacks_like?
|
56
|
+
sighting.quacks_like? MyContract
|
52
57
|
=> true
|
53
58
|
|
54
59
|
There are other syntaxes:
|
55
60
|
|
56
61
|
#This syntax gets all the comparison done in one line
|
57
|
-
DuckPond::Binoculars.confirm(obj,
|
62
|
+
DuckPond::Binoculars.confirm(obj, MyContract)
|
58
63
|
=> true
|
59
64
|
|
60
65
|
#This syntax does the same thing, but raises an excaption instead of returning false
|
61
|
-
DuckPond::Binoculars.confirm!(obj,
|
66
|
+
DuckPond::Binoculars.confirm!(obj, MyContract)
|
62
67
|
|
63
68
|
|
64
|
-
Ducks can be combined into composite "super
|
69
|
+
Ducks can be combined into composite "super contracts" - contracts which are made up of various other contracts. This ties in with the reccomendation of preferring composition over inheritance:
|
65
70
|
|
66
|
-
class
|
67
|
-
|
68
|
-
|
71
|
+
class MyCompositeConrtact < DuckPond::Contract
|
72
|
+
include_methods_from MyContract
|
73
|
+
include_methods_from MyOtherContract
|
69
74
|
end
|
70
75
|
|
71
|
-
|
76
|
+
|
77
|
+
A *serious duck* might look like this:
|
78
|
+
|
79
|
+
class IEmailable < DuckPond::Contract
|
80
|
+
#send: should send the results of :message via email to :to
|
81
|
+
has_method :send
|
82
|
+
|
83
|
+
#to: Should be an email address to which this will be sent
|
84
|
+
has_method :to
|
85
|
+
|
86
|
+
#message: The message to send
|
87
|
+
has_method :message
|
88
|
+
end
|
89
|
+
|
90
|
+
And then be implemented in a method like this:
|
91
|
+
|
92
|
+
class Emailer
|
93
|
+
def send(email)
|
94
|
+
DuckPond::Binoculars.confirm!(email, IEmailable)
|
95
|
+
email.send
|
96
|
+
end
|
97
|
+
end
|
72
98
|
|
73
99
|
|
74
100
|
## Contributing
|
data/lib/duckpond/binoculars.rb
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
#
|
9
9
|
module DuckPond
|
10
10
|
class Binoculars
|
11
|
-
class
|
11
|
+
class ContractInfringementError < TypeError;end
|
12
12
|
|
13
13
|
# The sighted object
|
14
14
|
attr_reader :sighting
|
@@ -48,7 +48,7 @@ module DuckPond
|
|
48
48
|
# does not quack like the duck.
|
49
49
|
#
|
50
50
|
def self.confirm!(obj, duck)
|
51
|
-
raise
|
51
|
+
raise ContractInfringementError unless confirm(obj, duck)
|
52
52
|
end
|
53
53
|
|
54
54
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#
|
2
|
+
# DuckPond::Contract
|
3
|
+
#
|
4
|
+
# Contracts are essentially lists of properties that a class could have.
|
5
|
+
# They are not intended to ever be instantiated, and are completely
|
6
|
+
# configured at the class level.
|
7
|
+
#
|
8
|
+
module DuckPond
|
9
|
+
class Contract
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def clauses
|
13
|
+
@clauses ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# has_method
|
18
|
+
#
|
19
|
+
# Adds a method expectation to the contract
|
20
|
+
#
|
21
|
+
def has_method(method_name)
|
22
|
+
clauses << method_name
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
#
|
27
|
+
# include_clauses_from
|
28
|
+
#
|
29
|
+
# Facilitates composition of multiple contracts
|
30
|
+
#
|
31
|
+
def include_clauses_from(other_contract)
|
32
|
+
other_contract.clauses.each do |other_contracts_quacking|
|
33
|
+
has_method other_contracts_quacking
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
#
|
39
|
+
# quacks_like?
|
40
|
+
#
|
41
|
+
# The main quack checking method for a duck
|
42
|
+
#
|
43
|
+
def quacks_like?(method)
|
44
|
+
clauses.include?(method)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/duckpond/sighting.rb
CHANGED
@@ -27,8 +27,8 @@ module DuckPond
|
|
27
27
|
# by the duck's quacks.
|
28
28
|
#
|
29
29
|
def quacks_like?(duck)
|
30
|
-
duck.
|
31
|
-
return false unless @sighted_object.respond_to?
|
30
|
+
duck.clauses.each do |clause|
|
31
|
+
return false unless @sighted_object.respond_to? clause
|
32
32
|
end
|
33
33
|
true
|
34
34
|
end
|
data/lib/duckpond/version.rb
CHANGED
data/lib/duckpond.rb
CHANGED
data/spec/binoculars_spec.rb
CHANGED
@@ -24,27 +24,27 @@ module DuckPond
|
|
24
24
|
|
25
25
|
describe '.confirm' do
|
26
26
|
|
27
|
-
class
|
28
|
-
|
29
|
-
|
27
|
+
class StringContract < Contract
|
28
|
+
has_method :to_s
|
29
|
+
has_method :length
|
30
30
|
end
|
31
31
|
|
32
32
|
context 'when given a ruby object and a duck class that it quacks like' do
|
33
33
|
it 'returns true' do
|
34
|
-
expect(DuckPond::Binoculars.confirm("Hello World",
|
34
|
+
expect(DuckPond::Binoculars.confirm("Hello World", StringContract)).to be true
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
context 'when given a ruby object and a duck class that it doesnt quacks like' do
|
39
39
|
it 'returns false' do
|
40
|
-
expect(DuckPond::Binoculars.confirm(Object.new,
|
40
|
+
expect(DuckPond::Binoculars.confirm(Object.new, StringContract)).to be false
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
describe '.confirm!' do
|
46
46
|
it 'behaves like the un-banged version, but raises an error if it doesnt quack right' do
|
47
|
-
expect {DuckPond::Binoculars.confirm!(Object.new,
|
47
|
+
expect {DuckPond::Binoculars.confirm!(Object.new, StringContract)}.to raise_error
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
data/spec/duck_spec.rb
CHANGED
@@ -2,51 +2,51 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module DuckPond
|
4
4
|
|
5
|
-
describe
|
5
|
+
describe Contract do
|
6
6
|
describe 'attributes' do
|
7
|
-
context '.
|
7
|
+
context '.clauses' do
|
8
8
|
it 'responds to and memoizes the result' do
|
9
|
-
expect(
|
10
|
-
expect(
|
11
|
-
expect(
|
9
|
+
expect(Contract).to respond_to :clauses
|
10
|
+
expect(Contract.clauses).to be_an Array
|
11
|
+
expect(Contract.clauses).to be_empty
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
class
|
18
|
-
|
19
|
-
|
17
|
+
class MyContract < Contract
|
18
|
+
has_method :foo
|
19
|
+
has_method :bar
|
20
20
|
end
|
21
21
|
|
22
|
-
describe
|
23
|
-
context '.
|
22
|
+
describe MyContract do
|
23
|
+
context '.clauses' do
|
24
24
|
it 'quacks like foo and bar' do
|
25
|
-
|
26
|
-
expect(
|
27
|
-
expect(
|
28
|
-
expect(
|
25
|
+
clauses = MyContract.clauses
|
26
|
+
expect(clauses.length).to eq 2
|
27
|
+
expect(clauses).to include :foo
|
28
|
+
expect(clauses).to include :bar
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
describe '.quacks_like?' do
|
33
33
|
it 'returns true if the argument is in the quackings' do
|
34
|
-
expect(
|
35
|
-
expect(
|
34
|
+
expect(MyContract.quacks_like? :foo).to be true
|
35
|
+
expect(MyContract.quacks_like? :chunky_bacon).to be false
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
class
|
41
|
-
|
42
|
-
|
40
|
+
class MySimilarContract < Contract
|
41
|
+
include_clauses_from MyContract
|
42
|
+
has_method :chunky_bacon
|
43
43
|
end
|
44
44
|
|
45
|
-
describe
|
45
|
+
describe MySimilarContract do
|
46
46
|
it 'retains its parents quackings' do
|
47
|
-
expect(
|
48
|
-
expect(
|
49
|
-
expect(
|
47
|
+
expect(MySimilarContract.quacks_like? :chunky_bacon).to be true
|
48
|
+
expect(MySimilarContract.quacks_like? :foo).to be true
|
49
|
+
expect(MySimilarContract.quacks_like? :bar).to be true
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
data/spec/sighting_spec.rb
CHANGED
@@ -13,15 +13,15 @@ module DuckPond
|
|
13
13
|
|
14
14
|
describe '#quacks_like?' do
|
15
15
|
|
16
|
-
class
|
17
|
-
|
16
|
+
class MyChunkyBaconContract < Contract
|
17
|
+
has_method :chunky_bacon
|
18
18
|
end
|
19
19
|
|
20
20
|
context 'when the sighted object quacks like the duck' do
|
21
21
|
it 'returns true' do
|
22
22
|
obj = OpenStruct.new(:chunky_bacon => :mmm)
|
23
23
|
sighting = Sighting.new(obj)
|
24
|
-
expect(sighting.quacks_like?
|
24
|
+
expect(sighting.quacks_like? MyChunkyBaconContract).to be true
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -29,7 +29,7 @@ module DuckPond
|
|
29
29
|
it 'returns false' do
|
30
30
|
obj = OpenStruct.new(:vegan_fallafal => :yuck)
|
31
31
|
sighting = Sighting.new(obj)
|
32
|
-
expect(sighting.quacks_like?
|
32
|
+
expect(sighting.quacks_like? MyChunkyBaconContract).to be false
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
data/spec/the_solution_spec.rb
CHANGED
@@ -9,9 +9,9 @@ module DuckPond
|
|
9
9
|
# First, declare a duck.
|
10
10
|
#
|
11
11
|
|
12
|
-
class
|
13
|
-
|
14
|
-
|
12
|
+
class ObjContract < DuckPond::Contract
|
13
|
+
has_method :to_s
|
14
|
+
has_method :length
|
15
15
|
end
|
16
16
|
|
17
17
|
#
|
@@ -36,7 +36,7 @@ module DuckPond
|
|
36
36
|
# And now you can compare your sightings with your ducks!
|
37
37
|
#
|
38
38
|
|
39
|
-
result = sighting.quacks_like?(
|
39
|
+
result = sighting.quacks_like?(ObjContract)
|
40
40
|
expect(result).to be true
|
41
41
|
|
42
42
|
#
|
@@ -45,7 +45,7 @@ module DuckPond
|
|
45
45
|
|
46
46
|
binoculars = DuckPond::Binoculars.new
|
47
47
|
sighting = binoculars.identify(obj)
|
48
|
-
raise TypeError unless sighting.quacks_like?(
|
48
|
+
raise TypeError unless sighting.quacks_like?(ObjContract)
|
49
49
|
|
50
50
|
#
|
51
51
|
# But because this trio of lines are so common, a
|
@@ -53,14 +53,14 @@ module DuckPond
|
|
53
53
|
# once:
|
54
54
|
#
|
55
55
|
|
56
|
-
result = DuckPond::Binoculars.confirm(obj,
|
56
|
+
result = DuckPond::Binoculars.confirm(obj, ObjContract)
|
57
57
|
expect(result).to be true
|
58
58
|
|
59
59
|
#
|
60
60
|
# The "bang" version will raise an error if the object isn't the duck.
|
61
61
|
#
|
62
62
|
|
63
|
-
expect { DuckPond::Binoculars.confirm!(Object.new,
|
63
|
+
expect { DuckPond::Binoculars.confirm!(Object.new, ObjContract) }.to raise_error TypeError
|
64
64
|
|
65
65
|
|
66
66
|
#
|
@@ -69,7 +69,7 @@ module DuckPond
|
|
69
69
|
|
70
70
|
class Foo
|
71
71
|
def self.bar(obj)
|
72
|
-
DuckPond::Binoculars.confirm!(obj,
|
72
|
+
DuckPond::Binoculars.confirm!(obj,ObjConrtact)
|
73
73
|
|
74
74
|
obj.foo!
|
75
75
|
fooinator = Test::Fooinator.new
|
@@ -83,7 +83,7 @@ module DuckPond
|
|
83
83
|
#
|
84
84
|
# Final Tips:
|
85
85
|
#
|
86
|
-
# *
|
86
|
+
# * Contracts should be commented liberally. They form the descripiton
|
87
87
|
# of a contract just as interfaces would in strongly typed languages.
|
88
88
|
# Developers will be prompted to consult the duck when they see a pair
|
89
89
|
# of binoculars in your code, so make sure they find what they're looking
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: duckpond
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mikey Hogarth
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -46,6 +46,8 @@ extensions: []
|
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
48
|
- .gitignore
|
49
|
+
- .rspec
|
50
|
+
- .travis.yml
|
49
51
|
- Gemfile
|
50
52
|
- Gemfile.lock
|
51
53
|
- LICENSE
|
@@ -54,7 +56,7 @@ files:
|
|
54
56
|
- duckpond.gemspec
|
55
57
|
- lib/duckpond.rb
|
56
58
|
- lib/duckpond/binoculars.rb
|
57
|
-
- lib/duckpond/
|
59
|
+
- lib/duckpond/contract.rb
|
58
60
|
- lib/duckpond/sighting.rb
|
59
61
|
- lib/duckpond/version.rb
|
60
62
|
- spec/binoculars_spec.rb
|
@@ -84,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
86
|
version: '0'
|
85
87
|
requirements: []
|
86
88
|
rubyforge_project:
|
87
|
-
rubygems_version: 2.
|
89
|
+
rubygems_version: 2.2.2
|
88
90
|
signing_key:
|
89
91
|
specification_version: 4
|
90
92
|
summary: Explicit duck-typing for ruby
|
@@ -96,4 +98,3 @@ test_files:
|
|
96
98
|
- spec/spec_helper.rb
|
97
99
|
- spec/the_problem_spec.rb
|
98
100
|
- spec/the_solution_spec.rb
|
99
|
-
has_rdoc:
|
data/lib/duckpond/duck.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# DuckPond::Duck
|
3
|
-
#
|
4
|
-
# Ducks are essentially "contracts" or "interfaces". They are not intended
|
5
|
-
# to ever be instantiated, and are completely configured at the class level.
|
6
|
-
#
|
7
|
-
module DuckPond
|
8
|
-
class Duck
|
9
|
-
class << self
|
10
|
-
|
11
|
-
def quacks
|
12
|
-
@quacks ||= []
|
13
|
-
end
|
14
|
-
|
15
|
-
#
|
16
|
-
# quacks_like
|
17
|
-
#
|
18
|
-
# Use this to specify that this duck quacks with
|
19
|
-
# a certain method, or quacks like another duck.
|
20
|
-
#
|
21
|
-
def quacks_like(item)
|
22
|
-
if item.is_a? Symbol
|
23
|
-
quacks << item
|
24
|
-
elsif item.ancestors.include? DuckPond::Duck
|
25
|
-
add_quacks_from item
|
26
|
-
else
|
27
|
-
raise TypeError
|
28
|
-
end
|
29
|
-
end
|
30
|
-
alias :looks_like :quacks_like
|
31
|
-
|
32
|
-
#
|
33
|
-
# quacks_like?
|
34
|
-
#
|
35
|
-
# The main quack checking method for a duck
|
36
|
-
#
|
37
|
-
def quacks_like?(method)
|
38
|
-
quacks.include?(method)
|
39
|
-
end
|
40
|
-
alias :looks_like? :quacks_like
|
41
|
-
|
42
|
-
private
|
43
|
-
#
|
44
|
-
# add_quacks_from
|
45
|
-
#
|
46
|
-
# add the quacks from another duck
|
47
|
-
#
|
48
|
-
def add_quacks_from(other_duck)
|
49
|
-
other_duck.quacks.each do |other_ducks_quacking|
|
50
|
-
quacks_like other_ducks_quacking
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|