double_dispatch 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +147 -0
- data/double_dispatch.gemspec +20 -0
- data/lib/double_dispatch.rb +19 -0
- data/rakefile +7 -0
- data/test/double_dispatch_test.rb +88 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 936769b4aaeb8b7f0e2a8c8b16a175f5f17d27c7
|
4
|
+
data.tar.gz: 3ef496d5c58ccc08cb80abc265c2e88aa39bb231
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 702bb989a35229bbca5f3a0baf612d438dc1f76bed60430a8bd6fa4ecd8453ce3df2ce39fb1c08a1ed39cbe5c1432ad91e0e4d353f542a1c8726095b48373045
|
7
|
+
data.tar.gz: b73fed857af9aab57f9e70657aa1b468fae4a45422aa5003cb08ef66023d686d9ef2c4156316e2354739e5060429c5ec18e08859b01ceac7445f7f117d15b5c3
|
data/README.md
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
double_dispatch
|
2
|
+
========
|
3
|
+
|
4
|
+
Call different functions depending on the runtime types of two objects.
|
5
|
+
Extremely simple to use and extend.
|
6
|
+
|
7
|
+
I personally use it to compensate the lack of _method overloading_ in Ruby and
|
8
|
+
separate concerns into smaller `modules`.
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
1. Define a unique `dispatch_id` for each class using the `dispatch_as` method.
|
13
|
+
|
14
|
+
class Dog
|
15
|
+
include DoubleDispatch
|
16
|
+
|
17
|
+
dispatch_as :dog
|
18
|
+
|
19
|
+
def pet
|
20
|
+
#...
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Human
|
25
|
+
include DoubleDispatch
|
26
|
+
|
27
|
+
dispatch_as :human
|
28
|
+
|
29
|
+
attr_accessor :name
|
30
|
+
|
31
|
+
def initialize(name)
|
32
|
+
@name = name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
2. Write concrete functions for each class you want handle
|
37
|
+
|
38
|
+
module Salutations
|
39
|
+
def salute_to_human(human)
|
40
|
+
"Hi #{human.name}!"
|
41
|
+
end
|
42
|
+
|
43
|
+
def salute_to_dog(dog)
|
44
|
+
dog.pet
|
45
|
+
|
46
|
+
"Woof woof!"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
3. Call `double_dispatch` to handle different non-necessary-polymorphic objects.
|
51
|
+
|
52
|
+
Dog.new.double_dispatch(:salute_to, Salutations)
|
53
|
+
# => "Woof woof!"
|
54
|
+
|
55
|
+
Human.new("Emiliano").double_dispatch(:salute_to, Salutations)
|
56
|
+
# => "Hi Emiliano!"
|
57
|
+
|
58
|
+
|
59
|
+
## Common patterns
|
60
|
+
|
61
|
+
### Create modules to encapsulate the logic
|
62
|
+
|
63
|
+
This is my favourite pattern.
|
64
|
+
Using the same example described above, we can create a better internal API if we
|
65
|
+
encapsulate all the salutation logic into a single `module`
|
66
|
+
|
67
|
+
module Salutations
|
68
|
+
def self.salute(somebody)
|
69
|
+
somebody.double_dispatch(:salute_to, self)
|
70
|
+
end
|
71
|
+
|
72
|
+
def salute_to_human(human)
|
73
|
+
"Hi #{human.name}!"
|
74
|
+
end
|
75
|
+
|
76
|
+
def salute_to_dog(dog)
|
77
|
+
dog.pet
|
78
|
+
|
79
|
+
"Woof woof!"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
And then, we use the module in a cleaner way:
|
84
|
+
|
85
|
+
Salutations.salute Dog.new
|
86
|
+
# => "Woof woof!"
|
87
|
+
|
88
|
+
Salutations.salute Human.new("Emiliano")
|
89
|
+
# => "Hi Emiliano!"
|
90
|
+
|
91
|
+
### Use `class.name` as `dispatch_id`
|
92
|
+
|
93
|
+
I frequently find myself using the same `dispatch_id` as the _class name_, so
|
94
|
+
I used to extend `DoubleDispatch` with the following snippet
|
95
|
+
|
96
|
+
module DoubleDispatch
|
97
|
+
module ByClassName
|
98
|
+
module ClassMethods
|
99
|
+
def dispatch_id
|
100
|
+
@dispatch_id ||= self.name.split('::').last.downcase
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.included(base)
|
105
|
+
base.include(::DoubleDispatch)
|
106
|
+
base.extend(ClassMethods)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
### Use _table's name_ as `dispatch_id`
|
113
|
+
|
114
|
+
Most of the time, we will use Active Record objects (or Sequel models, etc) in
|
115
|
+
our system and we want to identify these models by the table name.
|
116
|
+
|
117
|
+
Since this gem is flexible and easy to extend, I suggest to extend `DoubleDispatch`
|
118
|
+
with a specific module using the ORM-specific methods.
|
119
|
+
|
120
|
+
For example, an extension for `Sequel` models would be:
|
121
|
+
|
122
|
+
module DoubleDispatch
|
123
|
+
module ByTableName::Sequel
|
124
|
+
module ClassMethods
|
125
|
+
def dispatch_id
|
126
|
+
@dispatch_id ||= self.table_name
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.included(base)
|
131
|
+
base.include(::DoubleDispatch)
|
132
|
+
base.extend(ClassMethods)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
And use this logic in a single line:
|
138
|
+
|
139
|
+
class User < Sequel::Model
|
140
|
+
include DoubleDispatch::ByTableName::Sequel
|
141
|
+
|
142
|
+
...
|
143
|
+
end
|
144
|
+
|
145
|
+
As you can see, it won't **need** to call `dispatch_as` method, but you can always
|
146
|
+
call it and overwrite the `dispatch_id`. This is extremely useful when you define
|
147
|
+
more than a model over the same _table name_.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'double_dispatch'
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
5
|
+
s.summary = 'Method overloading with runtime types made easy'
|
6
|
+
s.description = '`achetepe` is a small and simple library to execute a block of code after an asynchronous HTTP request.'
|
7
|
+
s.description = 'Call different functions depending on the runtime types of two objects, use method overloading, separate concerns, etc.'
|
8
|
+
s.authors = ['Emiliano Mancuso']
|
9
|
+
s.email = ['emiliano.mancuso@gmail.com']
|
10
|
+
s.homepage = 'http://github.com/emancu/double_dispatch'
|
11
|
+
s.license = 'MIT'
|
12
|
+
|
13
|
+
s.files = Dir[
|
14
|
+
'README.md',
|
15
|
+
'rakefile',
|
16
|
+
'lib/double_dispatch.rb',
|
17
|
+
'double_dispatch.gemspec'
|
18
|
+
]
|
19
|
+
s.test_files = Dir['test/double_dispatch_test.rb']
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DoubleDispatch
|
2
|
+
module ClassMethods
|
3
|
+
def dispatch_as(id)
|
4
|
+
@dispatch_id = id
|
5
|
+
end
|
6
|
+
|
7
|
+
def dispatch_id
|
8
|
+
@dispatch_id || fail('undefined :dispatch_id for class: ' + self.name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
def double_dispatch(method_name, resolver, *args)
|
17
|
+
resolver.send("#{method_name}_#{self.class.dispatch_id}", self, *args)
|
18
|
+
end
|
19
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative '../lib/double_dispatch'
|
3
|
+
|
4
|
+
module DoubleDispatch
|
5
|
+
module ByClassName
|
6
|
+
module ClassMethods
|
7
|
+
def dispatch_id
|
8
|
+
@dispatch_id ||= self.name.split('::').last.downcase
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.include(::DoubleDispatch)
|
14
|
+
base.extend(ClassMethods)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TestDoubleDispatch < Minitest::Test
|
20
|
+
class FailureTestingModel
|
21
|
+
include DoubleDispatch
|
22
|
+
end
|
23
|
+
|
24
|
+
module Resolver
|
25
|
+
module_function
|
26
|
+
|
27
|
+
def perform_for_dog(dog, *args)
|
28
|
+
[dog, args]
|
29
|
+
end
|
30
|
+
|
31
|
+
def perform_for_not_a_dog(dog, *args)
|
32
|
+
'Not a dog'
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_for_human(human)
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Dog
|
41
|
+
include DoubleDispatch
|
42
|
+
|
43
|
+
dispatch_as :dog
|
44
|
+
end
|
45
|
+
|
46
|
+
class Human
|
47
|
+
include DoubleDispatch::ByClassName
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_fails_when_dispatch_id_is_undefined
|
51
|
+
assert_raises { FailureTestingModel.new.double_dispatch(:please_fail, Resolver) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_double_dispatch
|
55
|
+
resolver = Object.new
|
56
|
+
instance = Dog.new
|
57
|
+
|
58
|
+
result = instance.double_dispatch(:perform_for, Resolver)
|
59
|
+
|
60
|
+
assert_equal [instance, []], result
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_double_dispatch_with_more_arguments
|
64
|
+
instance = Dog.new
|
65
|
+
|
66
|
+
result = instance.double_dispatch(:perform_for, Resolver, 2, :other_arg)
|
67
|
+
|
68
|
+
assert_equal [instance, [2, :other_arg]], result
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_overwrite_dispatch_id
|
72
|
+
Dog.dispatch_as :not_a_dog
|
73
|
+
|
74
|
+
instance = Dog.new
|
75
|
+
|
76
|
+
result = instance.double_dispatch(:perform_for, Resolver)
|
77
|
+
|
78
|
+
assert_equal 'Not a dog', result
|
79
|
+
|
80
|
+
Dog.dispatch_as :dog
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_extension_by_class_name
|
84
|
+
assert_equal 'human', Human.dispatch_id
|
85
|
+
|
86
|
+
assert Human.new.double_dispatch(:test_for, Resolver)
|
87
|
+
end
|
88
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: double_dispatch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Emiliano Mancuso
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-05-04 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Call different functions depending on the runtime types of two objects,
|
14
|
+
use method overloading, separate concerns, etc.
|
15
|
+
email:
|
16
|
+
- emiliano.mancuso@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.md
|
22
|
+
- double_dispatch.gemspec
|
23
|
+
- lib/double_dispatch.rb
|
24
|
+
- rakefile
|
25
|
+
- test/double_dispatch_test.rb
|
26
|
+
homepage: http://github.com/emancu/double_dispatch
|
27
|
+
licenses:
|
28
|
+
- MIT
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 2.5.1
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: Method overloading with runtime types made easy
|
50
|
+
test_files:
|
51
|
+
- test/double_dispatch_test.rb
|