forwarder 0.1.0 → 0.1.1
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.
- data/README.md +100 -19
- data/lib/forwarder.rb +7 -0
- data/lib/forwarder/VERSION.rb +3 -0
- metadata +29 -50
data/README.md
CHANGED
@@ -17,16 +17,36 @@ first two replace and enhance the functionality of ```def_delegator``` and
|
|
17
17
|
```def_delegators```, while the third is a kind of a ```alias_method``` on
|
18
18
|
steroids.
|
19
19
|
|
20
|
+
## Parameters ##
|
21
|
+
|
22
|
+
The first parameter, (or paramters in the case of `forward_all`) is (are)
|
23
|
+
a symbol(s) or string(s) indicating the message to be forwarded. That is
|
24
|
+
a message of which the receiver is an instance of the module in which
|
25
|
+
`forward` was called.
|
26
|
+
|
27
|
+
Ater this we have a hash style parameter which needs a *target* specification,
|
28
|
+
indicated by `:to`, `:to_chain` or `:to_object`. It can contain an optional
|
29
|
+
`:as` parameter translating the method name and an equally optional `:with`
|
30
|
+
paramter allowing us to provide paramters to the forwarded message.
|
31
|
+
I refer to the `:as` parameter as the *translation* and the `:with` parameter
|
32
|
+
as the *parametrization*.
|
33
|
+
|
34
|
+
This might be confusing at first, but the follwing examples shall demonstrate
|
35
|
+
how simple things really are.
|
36
|
+
|
37
|
+
|
20
38
|
## Examples ##
|
21
39
|
|
22
|
-
|
40
|
+
### The forward Method ###
|
41
|
+
|
42
|
+
#### Target specified with :to ####
|
23
43
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
message
|
29
|
-
|
44
|
+
The `:to` keyword parameter and can be either a `Symbol` (or `String`), thus representing
|
45
|
+
an instance method or an instance variable of the receiver. It can also be a lambda that
|
46
|
+
will be evaluated in the receiver's context. If an arbitrary object shall be the receiver of the
|
47
|
+
message, than the `:to` keyword can be replaced by the `:to_object`, and if the target of
|
48
|
+
the message shall bet the result of chained method calls on the receiver `:to_chain` is
|
49
|
+
at your service.
|
30
50
|
|
31
51
|
class Employee
|
32
52
|
extend Forwarder
|
@@ -37,7 +57,9 @@ This design, implementing some wishful thinking that will probably not pass
|
|
37
57
|
acceptance tests, will forward the ```complaints``` message, sent to an instance
|
38
58
|
of ```Employee```, to the object returned by this very instance's ```boss``` method.
|
39
59
|
|
40
|
-
|
60
|
+
As feared the implementation did not live up to the expectations (hence the desperate
|
61
|
+
need of foraml specifications) and the following adjustment was made, in some desperate
|
62
|
+
hope to fix the *bug*:
|
41
63
|
|
42
64
|
class Employee
|
43
65
|
extend Forwarder
|
@@ -46,18 +68,25 @@ The following adjustment was made, in desperate hope to fix the *bug*:
|
|
46
68
|
|
47
69
|
This behavior being clearly preferable to the one implemented before because the
|
48
70
|
receiver of ```complaints``` is still forwarding the call to the result of the
|
49
|
-
call of its ```boss``` method, but to
|
71
|
+
call of its ```boss``` method, but to it's `suggestions` method. (Well that is
|
72
|
+
not precise wording, but we shall make an abstraction about how the object returned
|
73
|
+
by `boss` handles the `suggestions` message.)
|
50
74
|
|
51
75
|
Finally, however, the implementation looked like this
|
52
76
|
|
53
77
|
class Boss
|
54
78
|
extend Forwarder
|
55
|
-
|
79
|
+
forward :complaints, to: first_employee
|
80
|
+
forward :problems, to: first_employee
|
81
|
+
forward :tasks, to: first_employee
|
56
82
|
end
|
57
83
|
|
58
|
-
However this did not work as no
|
59
|
-
|
60
|
-
|
84
|
+
However this did not work as no `first_employee` was defined yet. This seems
|
85
|
+
a task so simple that a method for this seems almost too much code.
|
86
|
+
Forwarder let us allow an implementation on itself.
|
87
|
+
The other thing that catches (or should, at least) the reader's eye is the terrible code repetition.
|
88
|
+
To get rid of it, we will indulge us by looking ahead to the `forward_all` method, which of course
|
89
|
+
is just short for three `forward` calls with each of its positional parameters.
|
61
90
|
|
62
91
|
class Boss
|
63
92
|
extend Forwarder
|
@@ -65,12 +94,23 @@ are two possible implementations with ```Forwarder```
|
|
65
94
|
forward_all :complaints, :problems, :tasks, to: :first_employee
|
66
95
|
end
|
67
96
|
|
97
|
+
Here we see the first use case of a *parametrization*, paired with a *translation*.
|
98
|
+
Please note that the first does not necessarily imply the second, the following example
|
99
|
+
might be reasonable code.
|
100
|
+
|
101
|
+
class Train
|
102
|
+
extend Forwarder
|
103
|
+
forward :signal, to: :@signaller, with: {strength: 10, tone: 42}
|
104
|
+
end
|
105
|
+
|
68
106
|
As a side note, I do not enourage the exposure of instance variables as in the
|
69
|
-
|
70
|
-
|
71
|
-
|
107
|
+
examples above, but it still might make your code shorter, which is an asset
|
108
|
+
of its own of course. Furthermore it allows a faster transation from `Forwardable`
|
109
|
+
if it is used to delegate to instance variables.
|
72
110
|
|
73
|
-
|
111
|
+
The above `Boss` case was badely written of course as `Array#first` gives us the
|
112
|
+
perfect opportunity to get rid of the `with:` parameter, which is somehow a little
|
113
|
+
bit of a code smell, I admit. Let us do better:
|
74
114
|
|
75
115
|
class Boss
|
76
116
|
extend Forwarder
|
@@ -79,14 +119,55 @@ Or alternatively
|
|
79
119
|
end
|
80
120
|
|
81
121
|
|
82
|
-
|
83
|
-
|
122
|
+
### forward_all ###
|
123
|
+
|
124
|
+
`forward_all` allows us to forward more then one message to a target. It is a shortcut
|
125
|
+
for calling `forward` to each of its method parameters. As one can see in the next
|
126
|
+
example it supports all kinds of target parameters, :to, :to_chain, but also :to_object
|
127
|
+
|
128
|
+
#### Target :to_chain ####
|
129
|
+
|
130
|
+
The example above is still too verbose. For what we know there is no need to define a
|
131
|
+
delagation for the `first_employee` method. And this is the use case where `:to_chain`
|
132
|
+
seems the right tool to use, let us see it's application at work:
|
84
133
|
|
85
134
|
class Boss
|
86
135
|
extend Forwarder
|
87
136
|
forward_all :complaints, :problems, :tasks, to_chain: [:@employees, :first]
|
88
137
|
end
|
89
138
|
|
139
|
+
Now we forward, almost anonymously to `@employeees.first` without defining a method, or
|
140
|
+
forwarder to bridge this gap.
|
141
|
+
|
142
|
+
As you might guess, the `complaints` message is sent to the result of sending `first`
|
143
|
+
to the `@employees` instance variable. As (no pun intended) with the `to:` version
|
144
|
+
of `forward`, one can change the message name with the `as:` parameter, aka *translation*.
|
145
|
+
|
146
|
+
It is uncommon, but not impossible to use a *translation* in `forward_all`
|
147
|
+
|
148
|
+
class Boss
|
149
|
+
extend Forwarder
|
150
|
+
forward_all :complaints, :problems, :tasks,
|
151
|
+
to_chain: [:@employees, :first],
|
152
|
+
as: :request
|
153
|
+
end
|
154
|
+
|
155
|
+
Here we go, seems quite a realistic model to me.
|
156
|
+
|
157
|
+
|
158
|
+
## Performance ##
|
159
|
+
|
160
|
+
If you are concerned about performance, but you should not yet, I have good news for you. Using
|
161
|
+
`Forwarder` will be a performance hit. Now why should that be good news? Well it is good news
|
162
|
+
for two reasons. Firstly by using `Forwarder` the performance hit notwithstanding you show that
|
163
|
+
you are not concerned by premature optimization but much more with clean, concise and readnale
|
164
|
+
design. Secondly if you run into performance issues and profiling shows that a forward target
|
165
|
+
is hit frequently, chances are that you found one of your performance bottlenecks. Just implement
|
166
|
+
the forward manually as a method and you shoud see quite some improvement. Now I am sure
|
167
|
+
you'd wish that all your performance issues are *that* *easy* to fix.
|
168
|
+
|
169
|
+
As I said: *Two* pieces of Good News!
|
170
|
+
|
90
171
|
## License ##
|
91
172
|
|
92
173
|
This software is licensed under the MIT license, which shall be attached to any deliverable of
|
data/lib/forwarder.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
|
3
|
+
require 'forwarder/VERSION'
|
3
4
|
require 'forwarder/meta'
|
4
5
|
module Forwarder
|
5
6
|
|
7
|
+
# delegates (forwards) a message to an object (indicated by :to)
|
6
8
|
def forward message, opts={}, &blk
|
7
9
|
opts = parse_opts opts, blk
|
8
10
|
# p opts: opts
|
@@ -24,11 +26,15 @@ module Forwarder
|
|
24
26
|
|
25
27
|
private
|
26
28
|
|
29
|
+
# Triggered by the presence of :to_object in forward's parameters
|
27
30
|
def forwarding_to_object message, opts
|
28
31
|
target = opts[ :to_object ]
|
29
32
|
forwarding_with message, opts.merge( to: Meta::ObjectContainer.new(target), with: opts.fetch( :with, [] ) )
|
30
33
|
end
|
31
34
|
|
35
|
+
# Handles the cases, where Forwardable can be used behind the scenes
|
36
|
+
# as a matter of fact :to was indicating a method or instance variable
|
37
|
+
# and :as was passed in (or defaults to the original message).
|
32
38
|
def forward_with_forwardable message, opts
|
33
39
|
to = opts.fetch :to
|
34
40
|
extend Forwardable
|
@@ -36,6 +42,7 @@ module Forwarder
|
|
36
42
|
def_delegator to, as, message
|
37
43
|
end
|
38
44
|
|
45
|
+
# Triggered by the presence of :to_chain in forward's parameters
|
39
46
|
def forward_with_chain message, opts
|
40
47
|
return false unless opts[:to_chain]
|
41
48
|
forwarding_with message, opts
|
metadata
CHANGED
@@ -1,77 +1,56 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: forwarder
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
- 0
|
10
|
-
version: 0.1.0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Robert Dober
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
date: 2012-01-22 00:00:00 +01:00
|
19
|
-
default_executable:
|
12
|
+
date: 2012-01-24 00:00:00.000000000Z
|
20
13
|
dependencies: []
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
and some more sophisticated use cases
|
14
|
+
description: ! "Ruby's core Forwardable gets the job done(barely) and produces most
|
15
|
+
unreadable code.\n This is a nonintrusive (as is Forwardable) module that allos
|
16
|
+
to delegate methods to instance variables,\n objects returned by instance_methods,
|
17
|
+
other methods of the same receiver (method_alias on steroids)\n and some more
|
18
|
+
sophisticated use cases"
|
27
19
|
email: robert.dober@gmail.com
|
28
20
|
executables: []
|
29
|
-
|
30
21
|
extensions: []
|
31
|
-
|
32
22
|
extra_rdoc_files: []
|
33
|
-
|
34
|
-
files:
|
35
|
-
- lib/forwarder/meta.rb
|
36
|
-
- lib/forwarder/helpers.rb
|
23
|
+
files:
|
37
24
|
- lib/forwarder.rb
|
25
|
+
- lib/forwarder/VERSION.rb
|
26
|
+
- lib/forwarder/helpers.rb
|
27
|
+
- lib/forwarder/meta.rb
|
38
28
|
- LICENSE
|
39
29
|
- README.md
|
40
|
-
has_rdoc: true
|
41
30
|
homepage: https://github.com/RobertDober/Forwarder
|
42
|
-
licenses:
|
43
|
-
-
|
31
|
+
licenses:
|
32
|
+
- MIT
|
44
33
|
post_install_message:
|
45
34
|
rdoc_options: []
|
46
|
-
|
47
|
-
require_paths:
|
35
|
+
require_paths:
|
48
36
|
- lib
|
49
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
38
|
none: false
|
51
|
-
requirements:
|
52
|
-
- -
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
hash: 57
|
55
|
-
segments:
|
56
|
-
- 1
|
57
|
-
- 8
|
58
|
-
- 7
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
59
42
|
version: 1.8.7
|
60
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
44
|
none: false
|
62
|
-
requirements:
|
63
|
-
- -
|
64
|
-
- !ruby/object:Gem::Version
|
65
|
-
|
66
|
-
segments:
|
67
|
-
- 0
|
68
|
-
version: "0"
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
69
49
|
requirements: []
|
70
|
-
|
71
50
|
rubyforge_project:
|
72
|
-
rubygems_version: 1.
|
51
|
+
rubygems_version: 1.8.10
|
73
52
|
signing_key:
|
74
53
|
specification_version: 3
|
75
54
|
summary: Making Delegation finally readable
|
76
55
|
test_files: []
|
77
|
-
|
56
|
+
has_rdoc:
|