callwith 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +96 -0
- data/ext/callwith_ext.c +71 -0
- data/ext/extconf.rb +3 -0
- data/lib/callwith.rb +35 -0
- data/test/test_callwith.rb +81 -0
- metadata +74 -0
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
callwith
|
2
|
+
========
|
3
|
+
|
4
|
+
Synopsis
|
5
|
+
--------
|
6
|
+
|
7
|
+
`Object#callwith` is like `instance_eval`, but can still delegate back to the
|
8
|
+
original object if a method is not found. Also unlike `instance_eval`,
|
9
|
+
all instance variables accessed within the block reference the original
|
10
|
+
self object.
|
11
|
+
|
12
|
+
gem install callwith
|
13
|
+
|
14
|
+
callwith(obj) do
|
15
|
+
# stuff
|
16
|
+
end
|
17
|
+
|
18
|
+
(this method was originally called `with` when it was part of
|
19
|
+
RubyTreasures, but has been renamed to `callwith` to avoid conflict with
|
20
|
+
the `with` gem).
|
21
|
+
|
22
|
+
Background
|
23
|
+
----------
|
24
|
+
|
25
|
+
One common problem with using `instance_eval` as a tool for brevity is
|
26
|
+
that it can easily become unclear which object is `self`. For example,
|
27
|
+
the Tk library that comes with Ruby makes heavy use of `instance_eval`:
|
28
|
+
|
29
|
+
class MyApplication
|
30
|
+
def initialize
|
31
|
+
@root = TkRoot.new
|
32
|
+
TkFrame.new do
|
33
|
+
TkButton.new do
|
34
|
+
text "Ok"
|
35
|
+
|
36
|
+
# The action() method will never be called, because this
|
37
|
+
# block was called with instance_eval, so `self` is not
|
38
|
+
# the MyApplication instance.
|
39
|
+
command proc { action() }
|
40
|
+
|
41
|
+
pack
|
42
|
+
end
|
43
|
+
pack
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def action()
|
48
|
+
puts "Hello, world"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
The `callwith` method will delegate method calls to the passed object;
|
53
|
+
however, if the passed object does not have such a method, `callwith` will
|
54
|
+
fall back onto calling on `self`. For example:
|
55
|
+
|
56
|
+
class HelloWorld
|
57
|
+
def initialize
|
58
|
+
@hello_count = 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def hello(file)
|
62
|
+
callwith(file) do
|
63
|
+
write("Hello world") # writes "Hello world" to file
|
64
|
+
increment() # calls increment on the HelloWorld instance
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def increment
|
69
|
+
@hello_count += 1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
All accessed instance variables are in the `self` object, so we could
|
74
|
+
have written hello() like this:
|
75
|
+
|
76
|
+
def hello(file)
|
77
|
+
callwith(file) do
|
78
|
+
write("Hello world")
|
79
|
+
increment()
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
The `callwith` method can also iterate over multiple objects, e.g.:
|
84
|
+
|
85
|
+
def hello(*files)
|
86
|
+
callwith(*files) do
|
87
|
+
write("Hello world")
|
88
|
+
increment()
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
License
|
93
|
+
-------
|
94
|
+
|
95
|
+
License is the MIT License (http://www.opensource.org/licenses/mit-license.html)
|
96
|
+
|
data/ext/callwith_ext.c
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <st.h>
|
3
|
+
|
4
|
+
static VALUE rb_cCallWith = Qnil;
|
5
|
+
|
6
|
+
static VALUE callwith_s_create(VALUE klass, VALUE obj, VALUE self_obj)
|
7
|
+
{
|
8
|
+
/* Create a new CallWith object, bypassing the usual object creation,
|
9
|
+
* because the CallWith class is not a normal class. */
|
10
|
+
NEWOBJ(with, struct RObject);
|
11
|
+
OBJSETUP(with, klass, T_OBJECT);
|
12
|
+
VALUE self = (VALUE)with;
|
13
|
+
|
14
|
+
/* Place our delegate objects into the singleton class so we can
|
15
|
+
* access them later */
|
16
|
+
VALUE s = rb_singleton_class(self);
|
17
|
+
rb_iv_set(s, "__with_obj__", obj);
|
18
|
+
rb_iv_set(s, "__with_self_obj__", self_obj);
|
19
|
+
|
20
|
+
/* Copy the pointer to the instance variable table from self_obj. As
|
21
|
+
* long as we hold a reference to self_obj, this pointer should be
|
22
|
+
* valid. */
|
23
|
+
struct RBasic basic = *(RBASIC(self));
|
24
|
+
*(ROBJECT(self)) = *(ROBJECT(self_obj));
|
25
|
+
*(RBASIC(self)) = basic;
|
26
|
+
|
27
|
+
return self;
|
28
|
+
}
|
29
|
+
|
30
|
+
static VALUE callwith_obj(VALUE self)
|
31
|
+
{
|
32
|
+
return rb_iv_get(rb_singleton_class(self), "__with_obj__");
|
33
|
+
}
|
34
|
+
|
35
|
+
static VALUE callwith_self_obj(VALUE self)
|
36
|
+
{
|
37
|
+
return rb_iv_get(rb_singleton_class(self), "__with_self_obj__");
|
38
|
+
}
|
39
|
+
|
40
|
+
static VALUE callwith_cleanup(VALUE self)
|
41
|
+
{
|
42
|
+
/* We don't want to keep the ivar table pointer around indefinitely,
|
43
|
+
* because if we do, the GC will free the ivar table, which is
|
44
|
+
* undesirable, since the original object still references it. So we
|
45
|
+
* set the ivar table back to something that won't get freed, instead.
|
46
|
+
*/
|
47
|
+
|
48
|
+
NEWOBJ(dummy, struct RObject);
|
49
|
+
OBJSETUP(dummy, rb_cCallWith, T_OBJECT);
|
50
|
+
|
51
|
+
struct RBasic basic = *(RBASIC(self));
|
52
|
+
*(ROBJECT(self)) = *(ROBJECT(dummy));
|
53
|
+
*(RBASIC(self)) = basic;
|
54
|
+
}
|
55
|
+
|
56
|
+
void Init_with_ext()
|
57
|
+
{
|
58
|
+
VALUE super = rb_class_boot(0);
|
59
|
+
rb_cCallWith = rb_class_boot(super);
|
60
|
+
rb_name_class(rb_cCallWith, rb_intern("CallWith"));
|
61
|
+
rb_const_set(rb_cObject, rb_intern("CallWith"), rb_cCallWith);
|
62
|
+
rb_global_variable(&rb_cCallWith);
|
63
|
+
|
64
|
+
rb_undef_alloc_func(rb_cCallWith);
|
65
|
+
rb_define_singleton_method(rb_cCallWith, "create", callwith_s_create, 2);
|
66
|
+
rb_define_method(rb_cCallWith, "__instance_eval__", rb_obj_instance_eval, -1);
|
67
|
+
rb_define_method(rb_cCallWith, "__with__obj__", callwith_obj, 0);
|
68
|
+
rb_define_method(rb_cCallWith, "__with__self_obj__", callwith_self_obj, 0);
|
69
|
+
rb_define_method(rb_cCallWith, "__with__cleanup__", callwith_cleanup, 0);
|
70
|
+
}
|
71
|
+
|
data/ext/extconf.rb
ADDED
data/lib/callwith.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'with_ext'
|
2
|
+
|
3
|
+
# Iterate over the given objects and delegate all instance method calls
|
4
|
+
# in the block to the object. Instance variables accessed from inside
|
5
|
+
# the block are delegated to the receiver of the method. Returns the
|
6
|
+
# result of the last expression evaluated.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# callwith(file1, file2) do
|
11
|
+
# write("this is a test")
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
def callwith(*objs, &block)
|
15
|
+
last = nil
|
16
|
+
objs.each do |obj|
|
17
|
+
p = CallWith.create(obj, self)
|
18
|
+
last = p.__instance_eval__(&block)
|
19
|
+
end
|
20
|
+
return last
|
21
|
+
end
|
22
|
+
|
23
|
+
class CallWith
|
24
|
+
def method_missing(method, *args, &block)
|
25
|
+
obj = __with__obj__()
|
26
|
+
self_obj = __with__self_obj__()
|
27
|
+
|
28
|
+
if obj.respond_to?(method)
|
29
|
+
obj.__send__(method, *args, &block)
|
30
|
+
else
|
31
|
+
self_obj.__send__(method, *args, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'with'
|
3
|
+
|
4
|
+
class WithTest < Test::Unit::TestCase
|
5
|
+
class SelfObj
|
6
|
+
def initialize
|
7
|
+
@ivar = "self_obj_ivar"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self_obj_method
|
11
|
+
return "self_obj_method"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class DelegateObj
|
16
|
+
def initialize
|
17
|
+
@ivar = "delegate_obj_ivar"
|
18
|
+
end
|
19
|
+
|
20
|
+
def delegate_obj_method
|
21
|
+
return "delegate_obj_method"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class DelegateObj2
|
26
|
+
def initialize
|
27
|
+
@ivar = "delegate_obj_2_ivar"
|
28
|
+
end
|
29
|
+
|
30
|
+
def delegate_obj_method
|
31
|
+
return "delegate_obj_2_method"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_with_can_call_self_obj
|
36
|
+
self_obj = SelfObj.new
|
37
|
+
delegate_obj = DelegateObj.new
|
38
|
+
result = self_obj.instance_eval do
|
39
|
+
with(delegate_obj) do
|
40
|
+
self_obj_method()
|
41
|
+
end
|
42
|
+
end
|
43
|
+
assert_equal "self_obj_method", result
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_with_can_call_delegate_obj
|
47
|
+
self_obj = SelfObj.new
|
48
|
+
delegate_obj = DelegateObj.new
|
49
|
+
result = self_obj.instance_eval do
|
50
|
+
with(delegate_obj) do
|
51
|
+
delegate_obj_method()
|
52
|
+
end
|
53
|
+
end
|
54
|
+
assert_equal "delegate_obj_method", result
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_with_can_access_self_ivars
|
58
|
+
self_obj = SelfObj.new
|
59
|
+
delegate_obj = DelegateObj.new
|
60
|
+
result = self_obj.instance_eval do
|
61
|
+
with(delegate_obj) do
|
62
|
+
@ivar
|
63
|
+
end
|
64
|
+
end
|
65
|
+
assert_equal "self_obj_ivar", result
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_with_can_iterate_over_delegates
|
69
|
+
self_obj = SelfObj.new
|
70
|
+
delegate_obj_1 = DelegateObj.new
|
71
|
+
delegate_obj_2 = DelegateObj2.new
|
72
|
+
results = [ ]
|
73
|
+
self_obj.instance_eval do
|
74
|
+
with(delegate_obj_1, delegate_obj_2) do
|
75
|
+
results << delegate_obj_method()
|
76
|
+
end
|
77
|
+
end
|
78
|
+
assert_equal [ "delegate_obj_method", "delegate_obj_2_method" ], results
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: callwith
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Paul Brannan
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-06-19 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: |
|
22
|
+
Object#callwith is like instance_eval, but can still delegate back to
|
23
|
+
the original object if a method is not found. Also unlike
|
24
|
+
instance_eval, all instance variables accessed within the block
|
25
|
+
reference the original self object.
|
26
|
+
|
27
|
+
email: curlypaul924@gmail.com
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions:
|
31
|
+
- ext/extconf.rb
|
32
|
+
extra_rdoc_files:
|
33
|
+
- README.md
|
34
|
+
files:
|
35
|
+
- ext/extconf.rb
|
36
|
+
- ext/callwith_ext.c
|
37
|
+
- lib/callwith.rb
|
38
|
+
- test/test_callwith.rb
|
39
|
+
- README.md
|
40
|
+
homepage: http://github.com/cout/with/
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
hash: 3
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.8.24
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Call with objects
|
73
|
+
test_files:
|
74
|
+
- test/test_callwith.rb
|