double_dispatch 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/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
|