forwarder 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|