forwarder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,10 @@
1
+ class Proc
2
+ class << self
3
+ def identity
4
+ new{ |x| x }
5
+ end
6
+ def sum
7
+ new{ |a,b| a+b }
8
+ end
9
+ end # class << self
10
+ end # class Proc
@@ -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
+