forwarder19 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +10 -0
- data/README.md +430 -0
- data/lib/forwarder/arguments.rb +145 -0
- data/lib/forwarder/helpers.rb +4 -0
- data/lib/forwarder/meta.rb +71 -0
- data/lib/forwarder/params.rb +70 -0
- data/lib/forwarder/version.rb +3 -0
- data/lib/forwarder.rb +18 -0
- metadata +136 -0
data/LICENSE
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2012 Robert Dober
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
10
|
+
|
data/README.md
ADDED
@@ -0,0 +1,430 @@
|
|
1
|
+
# Forwarder19
|
2
|
+
|
3
|
+
This implementation is for, and needs, Ruby 1.9.2 or later. For Ruby 1.8.7 please see https://github.com/RobertDober/Forwarder.
|
4
|
+
|
5
|
+
## Abstract
|
6
|
+
|
7
|
+
Ruby's core Forwardable gets the job done(barely), but produces most unreadable code.
|
8
|
+
|
9
|
+
This is a nonintrusive (as is Forwardable) module that allows to delegate methods to instance variables,
|
10
|
+
objects returned by instance\_methods, other methods of the same receiver, the receiver itself, a chain of messages or
|
11
|
+
an arbitrary object. Paramters can be provided in the forwarding definition (parially or totally.
|
12
|
+
|
13
|
+
It also defines after and before filters. and some more sophisticated use cases}
|
14
|
+
|
15
|
+
## License
|
16
|
+
|
17
|
+
This software is released under the very liberal MIT license as indicated in the attached file LICENSE.
|
18
|
+
If you do not have the LICENSE file delivered the terms of the license are referred to here:
|
19
|
+
http://www.opensource.org/licenses/mit-license.html
|
20
|
+
|
21
|
+
## Performance
|
22
|
+
|
23
|
+
Performance is _normal_, as it is for 'Forwardable' however the goal is to have delegated methods
|
24
|
+
to run about as twice as fast as methods created by 'Forwardable.def_delegator'. This seems to be
|
25
|
+
a realistic goal as can be seen here: https://github.com/RobertDober/Forwarder19/blob/dev/bm/study.rb
|
26
|
+
|
27
|
+
|
28
|
+
## Simple Delegation As In Forwardable
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
|
32
|
+
forward <a_message>, to: <target>
|
33
|
+
forward <a_message>, to: <target>, as: <translation>
|
34
|
+
```
|
35
|
+
|
36
|
+
These two forms of the `forward` method, (and *only* these two forms) are directly implemented with
|
37
|
+
`def_delegator` method of `Forwardable`, as follows:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
def_delegator <target> <a_message>
|
41
|
+
def_delegator <translation> <a_message>
|
42
|
+
```
|
43
|
+
|
44
|
+
Furthermore the `forward_all` method is translated to the `def_delegators` method in the following form,
|
45
|
+
thusly
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
forward_all msg1, msg2, msg3, ..., to: target
|
49
|
+
```
|
50
|
+
is implemented as
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
def_delegators target, msg1, msg2, msg3, ...
|
54
|
+
```
|
55
|
+
|
56
|
+
### Additional Features
|
57
|
+
|
58
|
+
* Parameters (partial or total application)
|
59
|
+
* Custom And Chained Targets
|
60
|
+
* AOP Filters
|
61
|
+
* Helpers
|
62
|
+
|
63
|
+
|
64
|
+
## Parameters
|
65
|
+
|
66
|
+
|
67
|
+
### Passing One Parameter
|
68
|
+
|
69
|
+
Assuming a class `ArrayWrapper` and that their instances wrap the array object via the instance variable
|
70
|
+
`@ary` the Smalltalk method `second` can be implemented as follows.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
require 'forwarder'
|
74
|
+
class ArrayWrapper
|
75
|
+
extend Forwarder
|
76
|
+
forward :second, to: :@ary, as: :[], with: 1
|
77
|
+
...
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
The `with` keyword paramter is thus used to provide the first slice of arguments that will be provided
|
82
|
+
to the forwarded invocation. This slice will be extended by the actual parameters of the invocation
|
83
|
+
of the proxy method (e.g. the instance method defined by the `forward` method itself).
|
84
|
+
|
85
|
+
### Passing More Parameters
|
86
|
+
|
87
|
+
If `with:` is passed an array, it is splatted into the invocation, thus allowing us to pass more than
|
88
|
+
one parameter. This becomes clearer with an example.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
|
92
|
+
forward :add_whitespace_to_punctuation,
|
93
|
+
to: :name,
|
94
|
+
as: :gsub!,
|
95
|
+
with: [ /[,.]\b/, '\& ' ]
|
96
|
+
```
|
97
|
+
|
98
|
+
### Partial Application
|
99
|
+
|
100
|
+
This example gives us the oppurtunity to look at a use case for partial applications. Let us assume that
|
101
|
+
we do not always use whitespaces, than we can leave the second paramter to be provided by the invocation
|
102
|
+
of the defined forwarder proxy.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
forward :add_whitespace_to_punctuation,
|
106
|
+
to: :name,
|
107
|
+
as: :gsub!,
|
108
|
+
with: /[,.]\b/
|
109
|
+
```
|
110
|
+
|
111
|
+
We can achieve the same as above with the following invocation
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
o = Name.new( "the,quick, fox." )
|
115
|
+
o.add_whitespace_to_punctuation( '\1 ' )
|
116
|
+
# name: "the, quick, fox." )
|
117
|
+
```
|
118
|
+
|
119
|
+
but we can also add a hyphen after interpunctations with this invocation
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
o = Name.new( "the,quick, fox." )
|
123
|
+
o.add_whitespace_to_punctuation( '\1- ' )
|
124
|
+
# name: "the,- quick,- fox." )
|
125
|
+
```
|
126
|
+
|
127
|
+
### Passing One Array
|
128
|
+
|
129
|
+
If a real array shall be passed in as one parameter it can be wrapped into an array of one element,
|
130
|
+
or the `with_ary:` keyword parameter can be used.
|
131
|
+
|
132
|
+
Example:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
forward :append_suffix, to: :@ary, as: :concat, with: [%w{ my suffix }]
|
136
|
+
forward :append_suffix, to: :@ary, as: :concat, with_ary: %w{ my suffix }
|
137
|
+
```
|
138
|
+
|
139
|
+
### Passing A Block
|
140
|
+
|
141
|
+
In case of the necessity to provide a block to the forwarded invocation, it can be specified as the
|
142
|
+
block parameter of the `forward` invocation itself.
|
143
|
+
|
144
|
+
The following example uses inject to compute a sum of elements
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
forward :sum, to: :elements, as: :inject do |s,e| s+e end
|
148
|
+
```
|
149
|
+
|
150
|
+
Please note however that common patterns like this one can benefit of the provided
|
151
|
+
helpers, in our case it is Integer.sum.
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
require 'forwarder/helpers/integer/sum'
|
155
|
+
...
|
156
|
+
forward :sum, to: :elements, as: :inject &Integer.sum
|
157
|
+
# or
|
158
|
+
forward :sum, to: :elements, as: :inject, with_block: Integer.sum
|
159
|
+
...
|
160
|
+
```
|
161
|
+
|
162
|
+
Accounting for different tastes a block can be provided as a block parameter or
|
163
|
+
as a `lambda` to the `with_block:` keyword parameter. The later is taking preference
|
164
|
+
over the former, which no defined usage of the block in this case (at least for
|
165
|
+
the time being).
|
166
|
+
|
167
|
+
### Selective Helpers
|
168
|
+
|
169
|
+
As we do not want to be intrusive the helpers
|
170
|
+
have to be requested explicitly.
|
171
|
+
|
172
|
+
This can be done in three levels of granularity:
|
173
|
+
|
174
|
+
* Per helper
|
175
|
+
|
176
|
+
`require 'forwarder/helpers/integer/sum'`
|
177
|
+
|
178
|
+
* All helpers
|
179
|
+
|
180
|
+
`require 'forwarder/helpers'`
|
181
|
+
|
182
|
+
* Per monkey patched class
|
183
|
+
|
184
|
+
`require 'forwarder/helpers/integer'`
|
185
|
+
|
186
|
+
## Custom And Chained Targets
|
187
|
+
|
188
|
+
So far the `to:` keyword was followed by a symbol or string denoting a _symbolic receiver_, that is
|
189
|
+
an instance_variable or method with the denoted name. Custom and Chain Targets are implementing a
|
190
|
+
different story.
|
191
|
+
|
192
|
+
### Chain Targets
|
193
|
+
|
194
|
+
Chain Targets are also expressed with the `to:` keyword parameter, but by passing an array of _symbolic receivers_.
|
195
|
+
This array will resolve to the final target by sending each message to the result of the preceding message.
|
196
|
+
The following example should make this clearer:
|
197
|
+
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
forward :size, to: %w{@content children}
|
201
|
+
```
|
202
|
+
|
203
|
+
which could have been implemented by hand as follows:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
def size
|
207
|
+
@content.children.size
|
208
|
+
end
|
209
|
+
```
|
210
|
+
|
211
|
+
### Custom Targets
|
212
|
+
|
213
|
+
Allow the user to define a target that cannot be expressed as a _symbolic receiver_.
|
214
|
+
|
215
|
+
Custom targets are expressed by the means of the `to_object:` keyword parameter.
|
216
|
+
|
217
|
+
|
218
|
+
I want to give two examples here, the first
|
219
|
+
using `self`, which evaluates to the module in which `forward` is invoked of course, and might
|
220
|
+
thus be used to forward to class instance methods, as in the following example:
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
class Callback
|
224
|
+
def self.instances; @__instances__ ||= [] end
|
225
|
+
|
226
|
+
def self.register an_instance
|
227
|
+
instances << an_instance
|
228
|
+
end
|
229
|
+
|
230
|
+
extend Forwarder
|
231
|
+
forward :register, to_object: self
|
232
|
+
|
233
|
+
def initialize
|
234
|
+
register self
|
235
|
+
end
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
But when looking closely one can see that the `self.register` method is just another delegation, thus the whole code
|
240
|
+
can be rewritten even more concesily as:
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
class Callback
|
244
|
+
class << self
|
245
|
+
extend Forwarder
|
246
|
+
forward :<<, to: :instances
|
247
|
+
|
248
|
+
def instances; @__instances__ ||= [] end
|
249
|
+
end
|
250
|
+
|
251
|
+
extend Forwarder
|
252
|
+
forward :register, to_object: self, as: :<<
|
253
|
+
end
|
254
|
+
```
|
255
|
+
|
256
|
+
The second example is a forward to the instance itself, for that purpose the symbol :self
|
257
|
+
can be used. The followin is, again, an implementation of Smalltalk's `second` method. But
|
258
|
+
here we are defining it on `Array` itself, not a wrapper.
|
259
|
+
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
class Array
|
263
|
+
extend Forwarder
|
264
|
+
forward :second, to_object: :self, as: :[], with: 1
|
265
|
+
```
|
266
|
+
|
267
|
+
However the same could be accomplished by using the object/identity helper and the default
|
268
|
+
target implementation.
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
require 'forwarder/helpers/object/identity'
|
272
|
+
class Array
|
273
|
+
extend Forwarder
|
274
|
+
forward :second, to: :identity, as: :[], with: 1
|
275
|
+
```
|
276
|
+
|
277
|
+
#### Custom Targets And Closures
|
278
|
+
|
279
|
+
Another application of custom targets would be to hide a enclosed object, but as in the first
|
280
|
+
example above, such an object cannot be defined on instance level, but only on class level.
|
281
|
+
Assuming that the class itself does not need access to the object enclosed by the closure, one
|
282
|
+
could easily implement an instance count for a class as follows:
|
283
|
+
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
|
287
|
+
container = []
|
288
|
+
forward :register, to_object: container, as: :<<, with: :sentinel
|
289
|
+
forward :instance_count, to_object: container, as: :size
|
290
|
+
|
291
|
+
```
|
292
|
+
|
293
|
+
## AOP Filters
|
294
|
+
|
295
|
+
Before and After filters are implemented in this version.
|
296
|
+
|
297
|
+
The respective `before:` and
|
298
|
+
`after:` keyword parameters expect lambdas as paramters, but by specifying the `:use_block`
|
299
|
+
value the block parameter of the `forward` method can be _abused_ for this purpose.
|
300
|
+
|
301
|
+
The following examples all operate on a class wrapping a hash instance via the `hash` attribute
|
302
|
+
reader. Our first goal is to implement a `max_value` method, that will return the maxium value
|
303
|
+
of all values for given keys.
|
304
|
+
|
305
|
+
### After Filter
|
306
|
+
|
307
|
+
The lambda provided by `after:` is applied to the return value of the forwarded invocation.
|
308
|
+
The following three examples all implement the `max_value` method correctly.
|
309
|
+
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
forward :max_value, to: :hash, as: :values_at, after: lambda{ |x| x.max }
|
313
|
+
forward :max_value, to: :hash, as: :values_at, after: :use_block do | x |
|
314
|
+
x.max
|
315
|
+
end
|
316
|
+
require 'forwarder/helpers/kernel/sendmsg'
|
317
|
+
forward :max_value, to: :hash, as: :values_at, after: sendmsg( :max )
|
318
|
+
```
|
319
|
+
|
320
|
+
N.B. The `Kernel#sendmsg` method is my reply to the hated - by me that is at least - `Symbol#to_proc` kludge and its
|
321
|
+
limitations, I will talk about it more in the Helpers section.
|
322
|
+
|
323
|
+
### Before Filter.
|
324
|
+
|
325
|
+
Our next goal is to implement a method `value_of_max` that returns the value for the greatest of
|
326
|
+
all provided keys.
|
327
|
+
|
328
|
+
For this we will use a before filter, its lambda is applied to the arguments
|
329
|
+
of the implemented forwarder and the result will be passed in to the forwarded invocation. The pass in
|
330
|
+
will use a splash if appropriate.
|
331
|
+
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
forward :value_of_max, to: :hash, as: :[], before: lambda{ |*args| args.max }
|
335
|
+
require 'forwarder/helpers/kernel/sendmsg'
|
336
|
+
forward :value_of_max, to: :hash, as: :[], before: sendmsg( :max )
|
337
|
+
```
|
338
|
+
|
339
|
+
## Helpers
|
340
|
+
|
341
|
+
*N.B.* These are no longer part of Forwarder19, but have been moved into the gemdependency lab419_core.
|
342
|
+
|
343
|
+
Helpers define two type of methods. Firstly methods that return lambdas for frequently used
|
344
|
+
block patterns, e.g. `Integer.sum`. Secondly methods that are convenient to use inside `forward`
|
345
|
+
invocations, but not necessarily only there, e.g. `Kernel#sendmsg` or `Object#identity`.
|
346
|
+
|
347
|
+
### Functional Helpers
|
348
|
+
|
349
|
+
I see this second group, as small as it is, as an important enhancement for the functional
|
350
|
+
programming style. The possibilty to nullify a block that is necessarily used in a chain
|
351
|
+
of functional calls by passing in `{|x| x.identity}`, `sendmsg(:identity)` or even the
|
352
|
+
hated `&:identity` is a recurring pattern.
|
353
|
+
|
354
|
+
**Warning:** I will become evangelic now.
|
355
|
+
|
356
|
+
I do not like the `Symbol#to_proc` kludge, and that for two reasons. The first is pragamatic.
|
357
|
+
You cannot pass parameters, and that sucks. Why can I express `map(&:succ)` but not `map(&:+, 2)`.
|
358
|
+
Well the answer is clear, Ruby's syntax does not support it.
|
359
|
+
|
360
|
+
The second reason is on philosophical grounds. It feels wrong that Symbol shall be responsable
|
361
|
+
of transforming itself into a lambda.
|
362
|
+
|
363
|
+
Thus I created a helper in Kernel that takes the responsability, and doing so
|
364
|
+
with a clear name, expressing intent. This helper is `Kernel#sendmsg`.
|
365
|
+
|
366
|
+
|
367
|
+
```ruby
|
368
|
+
map do |ele|
|
369
|
+
ele.hello "World"
|
370
|
+
end
|
371
|
+
```
|
372
|
+
|
373
|
+
is the same as
|
374
|
+
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
map( &sendmsg( :hello, "World") )
|
378
|
+
```
|
379
|
+
|
380
|
+
Furthermore it might be usuful to keep the returned `lambda` around, please compare
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
adder = sendmsg( :+ )
|
384
|
+
```
|
385
|
+
versus
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
adder = :+.to_proc
|
389
|
+
```
|
390
|
+
|
391
|
+
|
392
|
+
Mapping with a `Symbol` might not only be conveniently expressed as sending a message to each
|
393
|
+
element, sometimes a different meaning might be appropriate as in the example below:
|
394
|
+
|
395
|
+
|
396
|
+
```ruby
|
397
|
+
map do | ele |
|
398
|
+
some_method ele
|
399
|
+
end
|
400
|
+
```
|
401
|
+
|
402
|
+
A different helper can do this job without any ambiguity:
|
403
|
+
|
404
|
+
|
405
|
+
```ruby
|
406
|
+
map( &applyto( :some_method ) )
|
407
|
+
```
|
408
|
+
|
409
|
+
### Commonly Used Pattern Helpers
|
410
|
+
|
411
|
+
This group of helpers is just to avoid to rewrite lambdas you/one/whoever/I have written zillions of times. Here is a short list of examples
|
412
|
+
the API doc should give you enough information if you look for something specific.
|
413
|
+
|
414
|
+
#### Integer.sum
|
415
|
+
|
416
|
+
```ruby
|
417
|
+
class Integer
|
418
|
+
def self.sum
|
419
|
+
->(a, b){ a + b }
|
420
|
+
end
|
421
|
+
end
|
422
|
+
```
|
423
|
+
|
424
|
+
#### Integer#inc
|
425
|
+
|
426
|
+
```ruby
|
427
|
+
class Integer
|
428
|
+
alias_method :inc, :succ # should have used forward ;)
|
429
|
+
end
|
430
|
+
```
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Forwarder
|
2
|
+
class Arguments
|
3
|
+
attr_reader :args, :message, :target
|
4
|
+
|
5
|
+
def all?
|
6
|
+
!args? && !lambda? && @__all__
|
7
|
+
end
|
8
|
+
|
9
|
+
def after
|
10
|
+
@after ||= @params[:after]
|
11
|
+
end
|
12
|
+
|
13
|
+
def after?
|
14
|
+
after
|
15
|
+
end
|
16
|
+
|
17
|
+
def aop?
|
18
|
+
@__aop__ ||= !aop_values.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def args?
|
22
|
+
!!args
|
23
|
+
end
|
24
|
+
|
25
|
+
def before
|
26
|
+
@before ||= @params[:before]
|
27
|
+
end
|
28
|
+
|
29
|
+
def before?
|
30
|
+
before
|
31
|
+
end
|
32
|
+
|
33
|
+
def chain?
|
34
|
+
@params[ :to_chain ]
|
35
|
+
end
|
36
|
+
|
37
|
+
def custom_target?
|
38
|
+
@params[:to_object]
|
39
|
+
end
|
40
|
+
|
41
|
+
def delegatable?
|
42
|
+
!aop? && !custom_target? && !all? && !chain? && !args && !lambda?
|
43
|
+
end
|
44
|
+
|
45
|
+
def complete_args *args
|
46
|
+
(self.args || []) + args
|
47
|
+
end
|
48
|
+
|
49
|
+
def lambda default=nil
|
50
|
+
lambda? || default
|
51
|
+
end
|
52
|
+
|
53
|
+
def lambda?
|
54
|
+
@lambda
|
55
|
+
end
|
56
|
+
|
57
|
+
# This is always nil unless we are a custom_target, in which case
|
58
|
+
# default is returned if target is :self, else target is returned
|
59
|
+
def object_target default
|
60
|
+
return unless custom_target?
|
61
|
+
target == :self ? default : target
|
62
|
+
end
|
63
|
+
|
64
|
+
def translation alternative=nil, &blk
|
65
|
+
@params[ :as ].tap do | tltion |
|
66
|
+
break alternative unless tltion
|
67
|
+
break tltion unless blk
|
68
|
+
blk.( tltion )
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def aop_values
|
74
|
+
@__aop_values__ ||= @params.values_at( :after, :before ).compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize *args, &blk
|
78
|
+
@message = args.shift
|
79
|
+
raise ArgumentError, "need one message and a hash of kwd params, plus an optional block" unless args.size == 1 && args.first.is_a?( Hash )
|
80
|
+
@params = args.first
|
81
|
+
set_message
|
82
|
+
set_target
|
83
|
+
set_args blk
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_args blk
|
87
|
+
set_lambda blk
|
88
|
+
hw = @params.has_key? :with
|
89
|
+
ha = @params.has_key? :with_ary
|
90
|
+
raise ArgumentError, "cannot use :with and :with_ary parameter" if hw && ha
|
91
|
+
set_args_normal if hw
|
92
|
+
set_args_ary if ha
|
93
|
+
end
|
94
|
+
|
95
|
+
def set_args_ary
|
96
|
+
@args = [ @params[:with_ary] ]
|
97
|
+
raise ArgumentError, ":with_ary needs an array parameter" unless Array === @args.first
|
98
|
+
end
|
99
|
+
|
100
|
+
def set_args_normal
|
101
|
+
case arg = @params[:with]
|
102
|
+
when Array
|
103
|
+
@args = arg.dup
|
104
|
+
else
|
105
|
+
@args = [ arg ]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def set_message
|
110
|
+
case @message
|
111
|
+
when Array
|
112
|
+
@__all__ = true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def set_lambda blk
|
117
|
+
|
118
|
+
if use_block?
|
119
|
+
@after = blk if @params[:after] == :use_block
|
120
|
+
@before = blk if @params[:before] == :use_block
|
121
|
+
@lambda = @params[:with_block]
|
122
|
+
else
|
123
|
+
raise ArgumentError, "cannot use :with_block and a block" if
|
124
|
+
@params[:with_block] && blk
|
125
|
+
|
126
|
+
@lambda = @params.fetch :with_block, blk
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def set_target
|
131
|
+
[:to, :to_chain, :to_object].each do | tgt_kwd |
|
132
|
+
tgt = @params[ tgt_kwd ]
|
133
|
+
next unless tgt
|
134
|
+
|
135
|
+
raise ArgumentError, "more than one target specified." if @target
|
136
|
+
@target = tgt
|
137
|
+
end
|
138
|
+
raise ArgumentError, "no target specified." unless @target
|
139
|
+
end
|
140
|
+
|
141
|
+
def use_block?
|
142
|
+
aop_values.include?( :use_block )
|
143
|
+
end
|
144
|
+
end # class Arguments
|
145
|
+
end # module Forwarder
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Forwarder
|
2
|
+
# I am the workerbee defining the methods and stuff
|
3
|
+
class Meta
|
4
|
+
|
5
|
+
attr_reader :arguments, :forwardee
|
6
|
+
|
7
|
+
|
8
|
+
# TODO: Break AOP out of this so that we do not check @ runtime
|
9
|
+
def forward
|
10
|
+
a = arguments
|
11
|
+
sr = symbolic_receiver
|
12
|
+
forwardee.module_eval do
|
13
|
+
define_method a.message do |*args, &blk|
|
14
|
+
args = a.before.(*args) if a.before?
|
15
|
+
sr
|
16
|
+
.( self, a.target )
|
17
|
+
.send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( blk ) ).tap do | result |
|
18
|
+
break a.after.( result ) if a.after?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def forward_chain
|
25
|
+
a = arguments
|
26
|
+
sr = symbolic_receiver
|
27
|
+
forwardee.module_eval do
|
28
|
+
define_method a.message do |*args, &blk|
|
29
|
+
args = a.before.(*args) if a.before?
|
30
|
+
tgt = a.target.inject( self ){ |r, sym| sr.( r, sym ) }
|
31
|
+
tgt.send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( blk ) ).tap do | result |
|
32
|
+
break a.after.( result ) if a.after?
|
33
|
+
end
|
34
|
+
#.send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( blk ) )
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def forward_object
|
40
|
+
a = arguments
|
41
|
+
forwardee.module_eval do
|
42
|
+
define_method a.message do |*args, &blk|
|
43
|
+
args = a.before.(*args) if a.before?
|
44
|
+
a.object_target( self )
|
45
|
+
.send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( blk ) ).tap do | result |
|
46
|
+
break a.after.( result ) if a.after?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def initialize forwardee, arguments
|
55
|
+
@forwardee = forwardee
|
56
|
+
@arguments = arguments
|
57
|
+
end
|
58
|
+
|
59
|
+
def symbolic_receiver
|
60
|
+
@__symbolic_receiver__ = ->(rec, sym) do
|
61
|
+
case "#{sym}"
|
62
|
+
when /\A@/
|
63
|
+
rec.instance_variable_get sym
|
64
|
+
else
|
65
|
+
rec.send sym
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end # class Meta
|
71
|
+
end # module Forwarder
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'forwarder/arguments'
|
2
|
+
require 'forwarder/meta'
|
3
|
+
module Forwarder
|
4
|
+
class Params
|
5
|
+
attr_reader :forwardee, :arguments
|
6
|
+
def forward!
|
7
|
+
return if delegate
|
8
|
+
return if delegate_all
|
9
|
+
return if delegate_chain
|
10
|
+
return if delegate_object
|
11
|
+
general_delegate
|
12
|
+
end
|
13
|
+
|
14
|
+
def prepare_forward *args, &blk
|
15
|
+
@arguments = Arguments.new( *args, &blk )
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def delegate
|
21
|
+
return unless arguments.delegatable?
|
22
|
+
delegate_to_forwardee
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def delegate_all
|
27
|
+
return unless arguments.all?
|
28
|
+
delegate_all_to_forwardee
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def delegate_all_to_forwardee
|
33
|
+
forwardee.extend Forwardable
|
34
|
+
forwardee.def_delegators( arguments.target, *arguments.message )
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def delegate_chain
|
39
|
+
return unless arguments.chain?
|
40
|
+
delegate_to_chain
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def delegate_to_chain
|
45
|
+
Meta.new( forwardee, arguments ).forward_chain
|
46
|
+
end
|
47
|
+
|
48
|
+
def delegate_object
|
49
|
+
return unless arguments.custom_target?
|
50
|
+
Meta.new( forwardee, arguments ).forward_object
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def delegate_to_forwardee
|
55
|
+
forwardee.extend Forwardable
|
56
|
+
arguments.translation do | tltion |
|
57
|
+
forwardee.def_delegator arguments.target, tltion, arguments.message
|
58
|
+
end or
|
59
|
+
forwardee.def_delegator arguments.target, arguments.message
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize forwardee
|
63
|
+
@forwardee = forwardee
|
64
|
+
end
|
65
|
+
|
66
|
+
def general_delegate
|
67
|
+
Meta.new( forwardee, arguments ).forward
|
68
|
+
end
|
69
|
+
end # class Params
|
70
|
+
end # module Forwarder
|
data/lib/forwarder.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'forwarder/params'
|
3
|
+
|
4
|
+
module Forwarder
|
5
|
+
|
6
|
+
def forward *args, &blk
|
7
|
+
params = Forwarder::Params.new self
|
8
|
+
params.prepare_forward( *args, &blk )
|
9
|
+
params.forward!
|
10
|
+
end
|
11
|
+
|
12
|
+
def forward_all *args, &blk
|
13
|
+
params = Forwarder::Params.new self
|
14
|
+
opts = args.pop
|
15
|
+
params.prepare_forward( args, opts, &blk )
|
16
|
+
params.forward!
|
17
|
+
end
|
18
|
+
end # module Forwarder
|
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: forwarder19
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Robert Dober
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-15 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: lab419_core
|
16
|
+
requirement: &82223890 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.0.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *82223890
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ruby-debug19
|
27
|
+
requirement: &82223620 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0.11'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *82223620
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &82223370 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.2.2
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *82223370
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &82223140 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.9.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *82223140
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: maruku
|
60
|
+
requirement: &82222910 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 0.6.0
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *82222910
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: wirble
|
71
|
+
requirement: &82222680 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 0.1.3
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *82222680
|
80
|
+
description: ! 'Ruby''s core Forwardable gets the job done(barely) and produces most
|
81
|
+
unreadable code. Furthermore it is about twice as slow (1.9.2-p290) or thrice as
|
82
|
+
slow (1.9.3-p0) as it needs to be.
|
83
|
+
|
84
|
+
This is a nonintrusive (as is Forwardable) module that allows to delegate methods
|
85
|
+
to instance variables,
|
86
|
+
|
87
|
+
objects returned by instance_methods, other methods of the same receiver, the receiver
|
88
|
+
itself, a chain of messages or
|
89
|
+
|
90
|
+
an arbitrary object. Paramters can be provided in the forwarding definition (parially
|
91
|
+
or totally=.
|
92
|
+
|
93
|
+
It also defines some AOP support as after and before filters.
|
94
|
+
|
95
|
+
Performance will be pushed to 2~3 times of Fowardable with the 0.2 branch of this
|
96
|
+
gem'
|
97
|
+
email: robert.dober@gmail.com
|
98
|
+
executables: []
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- lib/forwarder/arguments.rb
|
103
|
+
- lib/forwarder/meta.rb
|
104
|
+
- lib/forwarder/params.rb
|
105
|
+
- lib/forwarder/helpers.rb
|
106
|
+
- lib/forwarder/version.rb
|
107
|
+
- lib/forwarder.rb
|
108
|
+
- LICENSE
|
109
|
+
- README.md
|
110
|
+
homepage: https://github.com/RobertDober/Forwarder
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 1.9.2
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 1.8.11
|
132
|
+
signing_key:
|
133
|
+
specification_version: 3
|
134
|
+
summary: Delegation And AOP Filters For It
|
135
|
+
test_files: []
|
136
|
+
has_rdoc:
|