forwarder 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 +93 -0
- data/lib/forwarder/helpers.rb +10 -0
- data/lib/forwarder/meta.rb +47 -0
- data/lib/forwarder.rb +90 -0
- metadata +77 -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,93 @@
|
|
1
|
+
# Forwarder #
|
2
|
+
|
3
|
+
Delegation made readable.
|
4
|
+
|
5
|
+
## Rational ##
|
6
|
+
|
7
|
+
It seems that Ruby's built in ```Forwardable``` module does a decent job
|
8
|
+
to provide for delegation. However its syntax is more than terse, it is, IMHO,
|
9
|
+
unreadable. At a certain moment it came to me that relearning the correct
|
10
|
+
application of ```def_delegator```, frequent usage non withstanding is *not*
|
11
|
+
what I want to use my time for.
|
12
|
+
|
13
|
+
From that desire ```Forwarder``` was created. As ```Forwardable``` it is not
|
14
|
+
intruisive but once a module is extended with it the following methods spring
|
15
|
+
into live: ```forward```, ```forward_all``` and ```forward_to_self```. The
|
16
|
+
first two replace and enhance the functionality of ```def_delegator``` and
|
17
|
+
```def_delegators```, while the third is a kind of a ```alias_method``` on
|
18
|
+
steroids.
|
19
|
+
|
20
|
+
## Examples ##
|
21
|
+
|
22
|
+
* forward
|
23
|
+
|
24
|
+
Forwards to a target. The target must be specified by the ```:to``` keyword
|
25
|
+
parameter and can be either a ```Symbol``` (or ```String```), thus representing
|
26
|
+
an instance method or an instance variable, a lambda that will be evaluated
|
27
|
+
in the instance's context. If an arbitrary object shall be the receiver of the
|
28
|
+
message, than the ```:to``` keyword can be replaced by the ```:to_object```
|
29
|
+
keyword parameter.
|
30
|
+
|
31
|
+
class Employee
|
32
|
+
extend Forwarder
|
33
|
+
forward :complaints, to: :boss
|
34
|
+
end
|
35
|
+
|
36
|
+
This design, implementing some wishful thinking that will probably not pass
|
37
|
+
acceptance tests, will forward the ```complaints``` message, sent to an instance
|
38
|
+
of ```Employee```, to the object returned by this very instance's ```boss``` method.
|
39
|
+
|
40
|
+
The following adjustment was made, in desperate hope to fix the *bug*:
|
41
|
+
|
42
|
+
class Employee
|
43
|
+
extend Forwarder
|
44
|
+
forward :complaints, to: :boss, as: :suggestions
|
45
|
+
end
|
46
|
+
|
47
|
+
This behavior being clearly preferable to the one implemented before because the
|
48
|
+
receiver of ```complaints``` is still forwarding the call to the result of the
|
49
|
+
call of its ```boss``` method, but to the ```suggestion``` method.
|
50
|
+
|
51
|
+
Finally, however, the implementation looked like this
|
52
|
+
|
53
|
+
class Boss
|
54
|
+
extend Forwarder
|
55
|
+
forward_all :complaints, :problems, :tasks, to: first_employee
|
56
|
+
end
|
57
|
+
|
58
|
+
However this did not work as no ```first_employee``` was defined. This seems
|
59
|
+
simple enough a task, so that a method for this seems too much code bloat, here
|
60
|
+
are two possible implementations with ```Forwarder```
|
61
|
+
|
62
|
+
class Boss
|
63
|
+
extend Forwarder
|
64
|
+
forward :first_employee, to: :@employees, as: :[], with: 0
|
65
|
+
forward_all :complaints, :problems, :tasks, to: :first_employee
|
66
|
+
end
|
67
|
+
|
68
|
+
As a side note, I do not enourage the exposure of instance variables as in the
|
69
|
+
example above, but I do not like to impose. As ```Forwardable``` can delegate to
|
70
|
+
instance variables I decided to provide the same functionality with
|
71
|
+
```Forwarder```.
|
72
|
+
|
73
|
+
Or alternatively
|
74
|
+
|
75
|
+
class Boss
|
76
|
+
extend Forwarder
|
77
|
+
forward :first_employee, to: :@employees, as: :first
|
78
|
+
forward_all :complaints, :problems, :tasks, to: first_employee
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
The above, however is a little bit verbose, we can shorten it with the `:to_chain`
|
83
|
+
parameter
|
84
|
+
|
85
|
+
class Boss
|
86
|
+
extend Forwarder
|
87
|
+
forward_all :complaints, :problems, :tasks, to_chain: [:@employees, :first]
|
88
|
+
end
|
89
|
+
|
90
|
+
## License ##
|
91
|
+
|
92
|
+
This software is licensed under the MIT license, which shall be attached to any deliverable of
|
93
|
+
this software (LICENSE) or can be found here http://www.opensource.org/licenses/MIT
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Forwarder
|
2
|
+
module Meta extend self
|
3
|
+
ObjectContainer = Struct.new( :object )
|
4
|
+
def eval_body application, as, to, with
|
5
|
+
lambda do |*args, &blk|
|
6
|
+
arguments = ( [ with ].flatten + args ).compact
|
7
|
+
# rcv = __eval_receiver__( to )
|
8
|
+
rcv = Meta.eval_receiver to, self
|
9
|
+
# p as: as, to: to, rcv: rcv, args: arguments, app: application, self: self
|
10
|
+
rcv.send( as, *arguments, &(application||blk) )
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def eval_chain names, context
|
15
|
+
names.inject context do | ctxt, name |
|
16
|
+
Meta.eval_symbolic_receiver name, ctxt
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def eval_receiver name, context
|
21
|
+
case name
|
22
|
+
when Proc
|
23
|
+
context.instance_eval( &name )
|
24
|
+
when String, Symbol
|
25
|
+
# p name: name, context: context
|
26
|
+
Meta.eval_symbolic_receiver name, context
|
27
|
+
when ObjectContainer
|
28
|
+
name.object
|
29
|
+
when Array
|
30
|
+
Meta.eval_chain name, context
|
31
|
+
end
|
32
|
+
end
|
33
|
+
def eval_receiver_body
|
34
|
+
lambda do | name |
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def eval_symbolic_receiver name, context
|
39
|
+
case "#{name}"
|
40
|
+
when /\A@/
|
41
|
+
context.instance_variable_get name
|
42
|
+
else
|
43
|
+
context.send name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end # module Meta
|
47
|
+
end # module Forwarder
|
data/lib/forwarder.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
require 'forwarder/meta'
|
4
|
+
module Forwarder
|
5
|
+
|
6
|
+
def forward message, opts={}, &blk
|
7
|
+
opts = parse_opts opts, blk
|
8
|
+
# p opts: opts
|
9
|
+
forward_without_parsing message, opts
|
10
|
+
end
|
11
|
+
|
12
|
+
def forward_all *messages, &blk
|
13
|
+
opts = messages.pop
|
14
|
+
raise ArgumentError, "need a Hash as last arg" unless Hash === opts
|
15
|
+
opts = parse_opts opts, blk
|
16
|
+
messages.each do | msg |
|
17
|
+
forward_without_parsing msg, opts
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def forward_to_self message, opts={}
|
22
|
+
forwarding_with message, opts.merge( to: lambda{ |*args| self } )
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def forwarding_to_object message, opts
|
28
|
+
target = opts[ :to_object ]
|
29
|
+
forwarding_with message, opts.merge( to: Meta::ObjectContainer.new(target), with: opts.fetch( :with, [] ) )
|
30
|
+
end
|
31
|
+
|
32
|
+
def forward_with_forwardable message, opts
|
33
|
+
to = opts.fetch :to
|
34
|
+
extend Forwardable
|
35
|
+
as = opts.fetch( :as, message )
|
36
|
+
def_delegator to, as, message
|
37
|
+
end
|
38
|
+
|
39
|
+
def forward_with_chain message, opts
|
40
|
+
return false unless opts[:to_chain]
|
41
|
+
forwarding_with message, opts
|
42
|
+
end
|
43
|
+
# Transform blk into a normal parameter call the metaprogramming
|
44
|
+
# stuff if needed and return nil iff we can do it with Forwardable
|
45
|
+
def forward_with_meta message, opts
|
46
|
+
# p [:forward_with_meta, opts]
|
47
|
+
if opts[:applying] || opts[:with]
|
48
|
+
forwarding_with message, opts
|
49
|
+
true
|
50
|
+
elsif opts[:to_object]
|
51
|
+
forwarding_to_object message, opts
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Whenever the forward(s) cannot be implemented by def_delegator(s) eventually
|
57
|
+
# this method is called
|
58
|
+
def forwarding_with message, opts
|
59
|
+
# p opts
|
60
|
+
application, as, to, with = opts.values_at( :applying, :as, :to, :with )
|
61
|
+
as ||= message
|
62
|
+
|
63
|
+
# define_method( :__eval_receiver__, &Meta.eval_receiver_body )
|
64
|
+
define_method( message, &Meta.eval_body( application, as, to, with ) )
|
65
|
+
end
|
66
|
+
|
67
|
+
def forward_without_parsing message, opts
|
68
|
+
# p [:forward_without_parsing, opts]
|
69
|
+
return if forward_with_meta message, opts
|
70
|
+
return if forward_with_chain message, opts
|
71
|
+
forward_with_forwardable message, opts
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_opts opts, blk
|
75
|
+
params = opts.values_at :to_chain, :to, :to_object
|
76
|
+
params.compact.tap do | pms |
|
77
|
+
if pms.size != 1
|
78
|
+
raise ArgumentError,
|
79
|
+
"must passin exactly one of these kwd arguments: :to, :to_object or :to_chain, but found #{pms.join(", ")}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
opts.update applying: blk if blk
|
83
|
+
if params.first
|
84
|
+
opts.merge to: params.first
|
85
|
+
else
|
86
|
+
opts
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end # class Module
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: forwarder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Robert Dober
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-01-22 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: |-
|
23
|
+
Ruby's core Forwardable gets the job done(barely) and produces most unreadable code.
|
24
|
+
This is a nonintrusive (as is Forwardable) module that allos to delegate methods to instance variables,
|
25
|
+
objects returned by instance_methods, other methods of the same receiver (method_alias on steroids)
|
26
|
+
and some more sophisticated use cases
|
27
|
+
email: robert.dober@gmail.com
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files: []
|
33
|
+
|
34
|
+
files:
|
35
|
+
- lib/forwarder/meta.rb
|
36
|
+
- lib/forwarder/helpers.rb
|
37
|
+
- lib/forwarder.rb
|
38
|
+
- LICENSE
|
39
|
+
- README.md
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: https://github.com/RobertDober/Forwarder
|
42
|
+
licenses:
|
43
|
+
- - MIT
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 57
|
55
|
+
segments:
|
56
|
+
- 1
|
57
|
+
- 8
|
58
|
+
- 7
|
59
|
+
version: 1.8.7
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.3.7
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Making Delegation finally readable
|
76
|
+
test_files: []
|
77
|
+
|