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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b23a24b35895377f4668b651a7aaa75dcbd77054
4
- data.tar.gz: 9be4575cb42cf0ffbb398cb78ecac73c178f3ba0
3
+ metadata.gz: ed77b024ca7fdcad68dff17391aeb0de607e7da9
4
+ data.tar.gz: e00d0cc54ecc3dea1b83eb7a75bcea3382b784bc
5
5
  SHA512:
6
- metadata.gz: 37eb854f411be6a4480173f7a18fa94a0ef8886637f975714b1ec1ccad66cb9f6afbef019474a8ff4f269df7d41b28209afac03f947b14b4da4f7bcf3db495f5
7
- data.tar.gz: c4a7d9df1277b9fad7083e1c5aa1e76e247891fa1311d366f2ba47082b9be5ef7d0bda5d6fe4af99a666458f3d0d6c05ef88dc8ad8b9f646f1e1dee8ae872cca
6
+ metadata.gz: f79bd621cd72f4e2ca362633e25c4433ea9ba3f1242dd69ae2410061035768bef3f2c8b38cf91fe924394f01ac6c4801e71fba1fb121bc6eff1504ddc972d75b
7
+ data.tar.gz: d214ef206b663b6d7b84b7cf05239d4f4105ff68d460934200e53f36e709952c70ba3cb4a1c2bbe2d8ab3f9de4e011665bfb2ed82bf407a27c20a29a531e46d6
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - "1.9.2"
5
+ - "1.9.3"
6
+ - "2.0.0"
7
+
8
+ script: bundle exec rspec spec
data/Gemfile CHANGED
@@ -2,3 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in duckpond.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem 'rake'
8
+ gem 'rspec'
9
+ end
data/Gemfile.lock CHANGED
@@ -1,12 +1,25 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- duckpond (1.0.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
+ [![Gem Version](https://badge.fury.io/rb/duckpond.svg)](http://badge.fury.io/rb/duckpond)
2
+ [![Build Status](https://travis-ci.org/mikeyhogarth/duckpond.svg?branch=master)](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
- "duck" classes by inheriting from DuckPond::Duck. This file should be commented
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 MyDuck < DuckPond::Duck
43
- quacks_like :length
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 duck, you can use "binoculars" to see if objects quack like
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? MyDuck
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, MyDuck)
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, MyDuck)
66
+ DuckPond::Binoculars.confirm!(obj, MyContract)
62
67
 
63
68
 
64
- Ducks can be combined into composite "super ducks" - ducks which are made up of various other ducks:
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 MySuperDuck < DuckPond::Duck
67
- quacks_like MyDuck
68
- quacks_like MyOtherDuck
71
+ class MyCompositeConrtact < DuckPond::Contract
72
+ include_methods_from MyContract
73
+ include_methods_from MyOtherContract
69
74
  end
70
75
 
71
- the quacks_like method has been aliased to looks_like too - you can say something looks_like or quacks_like any item, whichever you prefer (in case you didn't want "quack" methods in your ever so serious project!).
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
@@ -8,7 +8,7 @@
8
8
  #
9
9
  module DuckPond
10
10
  class Binoculars
11
- class WrongDuckError < TypeError;end
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 WrongDuckError unless confirm(obj, duck)
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
@@ -27,8 +27,8 @@ module DuckPond
27
27
  # by the duck's quacks.
28
28
  #
29
29
  def quacks_like?(duck)
30
- duck.quacks.each do |quack|
31
- return false unless @sighted_object.respond_to? quack
30
+ duck.clauses.each do |clause|
31
+ return false unless @sighted_object.respond_to? clause
32
32
  end
33
33
  true
34
34
  end
@@ -1,3 +1,3 @@
1
1
  module Duckpond
2
- VERSION = "1.0.1"
2
+ VERSION = "1.0.2"
3
3
  end
data/lib/duckpond.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'duckpond/version'
2
- require 'duckpond/duck'
2
+ require 'duckpond/contract'
3
3
  require 'duckpond/binoculars'
4
4
  require 'duckpond/sighting'
5
5
 
@@ -24,27 +24,27 @@ module DuckPond
24
24
 
25
25
  describe '.confirm' do
26
26
 
27
- class StringDuck < Duck
28
- quacks_like :to_s
29
- quacks_like :length
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", StringDuck)).to be true
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, StringDuck)).to be false
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, StringDuck)}.to raise_error
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 Duck do
5
+ describe Contract do
6
6
  describe 'attributes' do
7
- context '.quackings' do
7
+ context '.clauses' do
8
8
  it 'responds to and memoizes the result' do
9
- expect(Duck).to respond_to :quacks
10
- expect(Duck.quacks).to be_an Array
11
- expect(Duck.quacks).to be_empty
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 MyDuck < Duck
18
- quacks_like :foo
19
- quacks_like :bar
17
+ class MyContract < Contract
18
+ has_method :foo
19
+ has_method :bar
20
20
  end
21
21
 
22
- describe MyDuck do
23
- context '.quackings' do
22
+ describe MyContract do
23
+ context '.clauses' do
24
24
  it 'quacks like foo and bar' do
25
- quacks = MyDuck.quacks
26
- expect(quacks.length).to eq 2
27
- expect(quacks).to include :foo
28
- expect(quacks).to include :bar
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(MyDuck.quacks_like? :foo).to be true
35
- expect(MyDuck.quacks_like? :chunky_bacon).to be false
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 MySimilarDuck < Duck
41
- quacks_like MyDuck
42
- quacks_like :chunky_bacon
40
+ class MySimilarContract < Contract
41
+ include_clauses_from MyContract
42
+ has_method :chunky_bacon
43
43
  end
44
44
 
45
- describe MySimilarDuck do
45
+ describe MySimilarContract do
46
46
  it 'retains its parents quackings' do
47
- expect(MySimilarDuck.quacks_like? :chunky_bacon).to be true
48
- expect(MySimilarDuck.quacks_like? :foo).to be true
49
- expect(MySimilarDuck.quacks_like? :bar).to be true
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
 
@@ -13,15 +13,15 @@ module DuckPond
13
13
 
14
14
  describe '#quacks_like?' do
15
15
 
16
- class MyChunkyBaconDuck < Duck
17
- quacks_like :chunky_bacon
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? MyChunkyBaconDuck).to be true
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? MyChunkyBaconDuck).to be false
32
+ expect(sighting.quacks_like? MyChunkyBaconContract).to be false
33
33
  end
34
34
  end
35
35
 
@@ -9,9 +9,9 @@ module DuckPond
9
9
  # First, declare a duck.
10
10
  #
11
11
 
12
- class ObjDuck < DuckPond::Duck
13
- quacks_like :to_s
14
- quacks_like :length
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?(ObjDuck)
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?(ObjDuck)
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, ObjDuck)
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, ObjDuck) }.to raise_error TypeError
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,ObjDuck)
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
- # * Ducks should be commented liberally. They form the descripiton
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.1
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-08 00:00:00.000000000 Z
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/duck.rb
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.1.10
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