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 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,7 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = 'test/*_test.rb'
5
+ end
6
+
7
+ task default: :test
@@ -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