callwith 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|