force_bind 0.1.0 → 0.1.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 +24 -20
- data/Rakefile +12 -9
- data/ext/extconf.rb +3 -4
- data/ext/force_bind.c +54 -15
- data/ext/method.h +103 -0
- data/spec/force_bind_spec.rb +94 -0
- metadata +31 -9
- data/test/test_force_bind.rb +0 -25
data/README
CHANGED
@@ -3,32 +3,36 @@
|
|
3
3
|
Adds UnboundMethod#force_bind to bind an unbound method to class (or any object of any type).
|
4
4
|
It basically bypasses the argument type checking that #bind has.
|
5
5
|
|
6
|
+
== Requirements
|
7
|
+
|
8
|
+
* Ruby 1.9.1 or greater (does not work in Ruby 1.8.x)
|
9
|
+
|
6
10
|
== Example
|
7
11
|
|
8
12
|
Bind an instance method to its class (making it act like a class method):
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
+
class Foo
|
15
|
+
def bar
|
16
|
+
puts "I'm inside #{self}"
|
14
17
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
end
|
19
|
+
|
20
|
+
Foo.new.bar
|
21
|
+
#=> "I'm inside #<Foo:0x123456>"
|
22
|
+
|
23
|
+
meth = Foo.instance_method(:bar).force_bind(Foo)
|
24
|
+
meth.call
|
25
|
+
#=> "I'm inside Foo"
|
22
26
|
|
23
27
|
You can also use this to rebind the instance method of any object to any
|
24
28
|
other object:
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
class A
|
31
|
+
def foo; self end
|
32
|
+
end
|
33
|
+
|
34
|
+
class B; end
|
35
|
+
|
36
|
+
meth = A.instance_method(:foo).force_bind(B.new)
|
37
|
+
meth.call
|
38
|
+
#=> #<B:0x123456>
|
data/Rakefile
CHANGED
@@ -4,15 +4,18 @@ require 'rake/gempackagetask'
|
|
4
4
|
WINDOWS = (PLATFORM =~ /win32|cygwin/ ? true : false) rescue false
|
5
5
|
SUDO = WINDOWS ? '' : 'sudo'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
pkg.need_tar = true
|
7
|
+
desc "Builds the gem"
|
8
|
+
task :gem => :build do
|
9
|
+
load 'force_bind.gemspec'
|
10
|
+
Gem::Builder.new(SPEC).build
|
12
11
|
end
|
13
12
|
|
14
|
-
desc "
|
15
|
-
task :install => :
|
16
|
-
sh "#{SUDO} gem install
|
17
|
-
sh "rm -rf pkg/#{SPEC.name}-#{SPEC.version}" unless ENV['KEEP_FILES']
|
13
|
+
desc "Installs the gem"
|
14
|
+
task :install => :gem do
|
15
|
+
sh "#{SUDO} gem install #{SPEC.name}-#{SPEC.version}.gem --no-rdoc --no-ri"
|
18
16
|
end
|
17
|
+
|
18
|
+
desc 'Build the extension'
|
19
|
+
task :build do
|
20
|
+
sh "cd ext && ruby extconf.rb && make"
|
21
|
+
end
|
data/ext/extconf.rb
CHANGED
data/ext/force_bind.c
CHANGED
@@ -1,29 +1,68 @@
|
|
1
1
|
#include "ruby.h"
|
2
2
|
|
3
|
+
#ifdef RUBY192_OR_GREATER
|
4
|
+
|
5
|
+
#include "method.h"
|
6
|
+
|
7
|
+
struct METHOD {
|
8
|
+
VALUE recv;
|
9
|
+
VALUE rclass;
|
10
|
+
ID id;
|
11
|
+
rb_method_entry_t me;
|
12
|
+
};
|
13
|
+
|
14
|
+
/*
|
15
|
+
* Similar to +UnboundMethod#bind+, but forces the bind regardless of the type.
|
16
|
+
* @return [Method]
|
17
|
+
*/
|
18
|
+
VALUE
|
19
|
+
umethod_force_bind(VALUE method, VALUE recv)
|
20
|
+
{
|
21
|
+
struct METHOD *data, *bound;
|
22
|
+
const rb_data_type_t *type;
|
23
|
+
|
24
|
+
type = RTYPEDDATA_TYPE(method);
|
25
|
+
TypedData_Get_Struct(method, struct METHOD, type, data);
|
26
|
+
method = TypedData_Make_Struct(rb_cMethod, struct METHOD, type, bound);
|
27
|
+
*bound = *data;
|
28
|
+
if (bound->me.def) bound->me.def->alias_count++;
|
29
|
+
bound->recv = recv;
|
30
|
+
bound->rclass = CLASS_OF(recv);
|
31
|
+
|
32
|
+
return method;
|
33
|
+
}
|
34
|
+
|
35
|
+
#else
|
36
|
+
|
3
37
|
struct METHOD {
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
38
|
+
VALUE oclass;
|
39
|
+
VALUE rclass;
|
40
|
+
VALUE recv;
|
41
|
+
ID id, oid;
|
42
|
+
struct RNode *body;
|
9
43
|
};
|
10
44
|
|
11
|
-
|
45
|
+
VALUE
|
12
46
|
umethod_force_bind(VALUE method, VALUE recv)
|
13
47
|
{
|
14
|
-
|
48
|
+
struct METHOD *data, *bound;
|
15
49
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
50
|
+
Data_Get_Struct(method, struct METHOD, data);
|
51
|
+
method = Data_Make_Struct(rb_cMethod, struct METHOD, free, -1, bound);
|
52
|
+
*bound = *data;
|
53
|
+
bound->recv = recv;
|
54
|
+
bound->rclass = TYPE(recv) == T_CLASS ? RCLASS(recv) : CLASS_OF(recv);
|
21
55
|
|
22
|
-
|
56
|
+
return method;
|
23
57
|
}
|
24
58
|
|
59
|
+
#endif /* RUBY192_OR_GREATER */
|
60
|
+
|
25
61
|
void
|
26
62
|
Init_force_bind()
|
27
63
|
{
|
28
|
-
|
29
|
-
|
64
|
+
rb_define_method(rb_cUnboundMethod, "force_bind", umethod_force_bind, 1);
|
65
|
+
#if 0
|
66
|
+
rb_cUnboundMethod = rb_define_class("UnboundMethod", rb_cObject); /* for docs */
|
67
|
+
#endif
|
68
|
+
}
|
data/ext/method.h
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
/**********************************************************************
|
2
|
+
|
3
|
+
method.h -
|
4
|
+
|
5
|
+
$Author: ko1 $
|
6
|
+
created at: Wed Jul 15 20:02:33 2009
|
7
|
+
|
8
|
+
Copyright (C) 2009 Koichi Sasada
|
9
|
+
|
10
|
+
**********************************************************************/
|
11
|
+
#ifndef METHOD_H
|
12
|
+
#define METHOD_H
|
13
|
+
|
14
|
+
typedef enum {
|
15
|
+
NOEX_PUBLIC = 0x00,
|
16
|
+
NOEX_NOSUPER = 0x01,
|
17
|
+
NOEX_PRIVATE = 0x02,
|
18
|
+
NOEX_PROTECTED = 0x04,
|
19
|
+
NOEX_MASK = 0x06,
|
20
|
+
NOEX_BASIC = 0x08,
|
21
|
+
NOEX_UNDEF = NOEX_NOSUPER,
|
22
|
+
NOEX_MODFUNC = 0x12,
|
23
|
+
NOEX_SUPER = 0x20,
|
24
|
+
NOEX_VCALL = 0x40,
|
25
|
+
NOEX_RESPONDS = 0x80
|
26
|
+
} rb_method_flag_t;
|
27
|
+
|
28
|
+
#define NOEX_SAFE(n) ((int)((n) >> 8) & 0x0F)
|
29
|
+
#define NOEX_WITH(n, s) ((s << 8) | (n) | (ruby_running ? 0 : NOEX_BASIC))
|
30
|
+
#define NOEX_WITH_SAFE(n) NOEX_WITH(n, rb_safe_level())
|
31
|
+
|
32
|
+
/* method data type */
|
33
|
+
|
34
|
+
typedef enum {
|
35
|
+
VM_METHOD_TYPE_ISEQ,
|
36
|
+
VM_METHOD_TYPE_CFUNC,
|
37
|
+
VM_METHOD_TYPE_ATTRSET,
|
38
|
+
VM_METHOD_TYPE_IVAR,
|
39
|
+
VM_METHOD_TYPE_BMETHOD,
|
40
|
+
VM_METHOD_TYPE_ZSUPER,
|
41
|
+
VM_METHOD_TYPE_UNDEF,
|
42
|
+
VM_METHOD_TYPE_NOTIMPLEMENTED,
|
43
|
+
VM_METHOD_TYPE_OPTIMIZED, /* Kernel#send, Proc#call, etc */
|
44
|
+
VM_METHOD_TYPE_MISSING /* wrapper for method_missing(id) */
|
45
|
+
} rb_method_type_t;
|
46
|
+
|
47
|
+
typedef struct rb_method_cfunc_struct {
|
48
|
+
VALUE (*func)(ANYARGS);
|
49
|
+
int argc;
|
50
|
+
} rb_method_cfunc_t;
|
51
|
+
|
52
|
+
typedef struct rb_method_attr_struct {
|
53
|
+
ID id;
|
54
|
+
VALUE location;
|
55
|
+
} rb_method_attr_t;
|
56
|
+
|
57
|
+
typedef struct rb_iseq_struct rb_iseq_t;
|
58
|
+
|
59
|
+
typedef struct rb_method_definition_struct {
|
60
|
+
rb_method_type_t type; /* method type */
|
61
|
+
ID original_id;
|
62
|
+
union {
|
63
|
+
rb_iseq_t *iseq; /* should be mark */
|
64
|
+
rb_method_cfunc_t cfunc;
|
65
|
+
rb_method_attr_t attr;
|
66
|
+
VALUE proc; /* should be mark */
|
67
|
+
enum method_optimized_type {
|
68
|
+
OPTIMIZED_METHOD_TYPE_SEND,
|
69
|
+
OPTIMIZED_METHOD_TYPE_CALL
|
70
|
+
} optimize_type;
|
71
|
+
} body;
|
72
|
+
int alias_count;
|
73
|
+
} rb_method_definition_t;
|
74
|
+
|
75
|
+
typedef struct rb_method_entry_struct {
|
76
|
+
rb_method_flag_t flag;
|
77
|
+
char mark;
|
78
|
+
rb_method_definition_t *def;
|
79
|
+
ID called_id;
|
80
|
+
VALUE klass; /* should be mark */
|
81
|
+
} rb_method_entry_t;
|
82
|
+
|
83
|
+
struct unlinked_method_entry_list_entry {
|
84
|
+
struct unlinked_method_entry_list_entry *next;
|
85
|
+
rb_method_entry_t *me;
|
86
|
+
};
|
87
|
+
|
88
|
+
#define UNDEFINED_METHOD_ENTRY_P(me) (!(me) || !(me)->def || (me)->def->type == VM_METHOD_TYPE_UNDEF)
|
89
|
+
|
90
|
+
void rb_add_method_cfunc(VALUE klass, ID mid, VALUE (*func)(ANYARGS), int argc, rb_method_flag_t noex);
|
91
|
+
rb_method_entry_t *rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *option, rb_method_flag_t noex);
|
92
|
+
rb_method_entry_t *rb_method_entry(VALUE klass, ID id);
|
93
|
+
|
94
|
+
rb_method_entry_t *rb_method_entry_get_without_cache(VALUE klass, ID id);
|
95
|
+
rb_method_entry_t *rb_method_entry_set(VALUE klass, ID mid, const rb_method_entry_t *, rb_method_flag_t noex);
|
96
|
+
|
97
|
+
int rb_method_entry_arity(const rb_method_entry_t *me);
|
98
|
+
|
99
|
+
void rb_mark_method_entry(const rb_method_entry_t *me);
|
100
|
+
void rb_free_method_entry(rb_method_entry_t *me);
|
101
|
+
void rb_sweep_method_entry(void *vm);
|
102
|
+
|
103
|
+
#endif /* METHOD_H */
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require_relative '../ext/force_bind'
|
3
|
+
|
4
|
+
describe 'UnboundMethod#force_bind' do
|
5
|
+
class Mock
|
6
|
+
def the_method
|
7
|
+
self
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'binds an instance method on a class' do
|
12
|
+
Mock.instance_method(:the_method).force_bind(Mock).call.should be Mock
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'binds an instance method on an instance of the same class' do
|
16
|
+
Mock.instance_method(:the_method).force_bind(mock = Mock.new).call.should be mock
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'binds an instance method on an instance of another class' do
|
20
|
+
Mock.instance_method(:the_method).force_bind(object = Object.new).call.should be object
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'environment' do
|
24
|
+
class C
|
25
|
+
def initialize(n) @n = n end
|
26
|
+
def n; @n end
|
27
|
+
end
|
28
|
+
class D
|
29
|
+
def initialize(n) @n = n end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'refers dynamically to instance variables' do
|
33
|
+
C.instance_method(:n).force_bind(D.new(4)).call.should == 4
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'refers dynamically to methods' do
|
37
|
+
C.send(:define_method, :each) { |&b| (1..@n).each(&b) }
|
38
|
+
Enumerable.instance_method(:select).force_bind(C.new(5)).call(&:even?).should == [2,4]
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can transplant methods on instances' do
|
42
|
+
(d = D.new(4)).define_singleton_method(:attr_n, &C.instance_method(:n).force_bind(d))
|
43
|
+
d.attr_n.should == 4
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can transplant methods on classes with a specific instance' do
|
47
|
+
D.send(:define_method, :attr_n, &C.instance_method(:n).force_bind(d = D.new(4)))
|
48
|
+
d.attr_n.should == 4
|
49
|
+
D.new(:whatever).attr_n.should == 4
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can transplant methods from a class to another by rebinding' do
|
53
|
+
class Source
|
54
|
+
def steal_me
|
55
|
+
[self, @a]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
class Target
|
59
|
+
def initialize(a) @a = a end
|
60
|
+
end
|
61
|
+
|
62
|
+
steal = lambda do |source_method, target_class, method_name|
|
63
|
+
target_class.class_exec do
|
64
|
+
define_method(method_name) do |*a,&b|
|
65
|
+
source_method.force_bind(self).call(*a,&b)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
target = Target.new(2)
|
71
|
+
steal.call(Source.instance_method(:steal_me), Target, :stolen)
|
72
|
+
target.stolen.should == [target, 2]
|
73
|
+
(t = Target.new(3)).stolen.should == [t, 3]
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'can not (yet) bind methods using super on other instances' do
|
77
|
+
class Parent
|
78
|
+
def meth
|
79
|
+
:meth
|
80
|
+
end
|
81
|
+
end
|
82
|
+
class Child < Parent
|
83
|
+
def meth
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
lambda {
|
89
|
+
Child.instance_method(:meth).force_bind(C.new(1)).call
|
90
|
+
}.should raise_error(NotImplementedError,
|
91
|
+
'super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: force_bind
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Loren Segal
|
@@ -9,10 +14,22 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date:
|
17
|
+
date: 2011-01-10 00:00:00 -05:00
|
13
18
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :development
|
32
|
+
version_requirements: *id001
|
16
33
|
description:
|
17
34
|
email: lsegal@soen.ca
|
18
35
|
executables: []
|
@@ -25,8 +42,9 @@ files:
|
|
25
42
|
- Rakefile
|
26
43
|
- README
|
27
44
|
- ext/force_bind.c
|
45
|
+
- ext/method.h
|
28
46
|
- ext/extconf.rb
|
29
|
-
-
|
47
|
+
- spec/force_bind_spec.rb
|
30
48
|
has_rdoc: false
|
31
49
|
homepage: http://github.com/lsegal/force_bind
|
32
50
|
licenses: []
|
@@ -37,23 +55,27 @@ rdoc_options: []
|
|
37
55
|
require_paths:
|
38
56
|
- lib
|
39
57
|
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
40
59
|
requirements:
|
41
60
|
- - ">="
|
42
61
|
- !ruby/object:Gem::Version
|
62
|
+
segments:
|
63
|
+
- 0
|
43
64
|
version: "0"
|
44
|
-
version:
|
45
65
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
46
67
|
requirements:
|
47
68
|
- - ">="
|
48
69
|
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
49
72
|
version: "0"
|
50
|
-
version:
|
51
73
|
requirements: []
|
52
74
|
|
53
75
|
rubyforge_project:
|
54
|
-
rubygems_version: 1.3.
|
76
|
+
rubygems_version: 1.3.7
|
55
77
|
signing_key:
|
56
78
|
specification_version: 3
|
57
|
-
summary: Adds UnboundMethod#force_bind to bind an unbound method to class (or any object of any type)
|
79
|
+
summary: Adds UnboundMethod#force_bind to bind an unbound method to a class (or any object of any type)
|
58
80
|
test_files: []
|
59
81
|
|
data/test/test_force_bind.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'test/unit'
|
2
|
-
require File.dirname(__FILE__) + '/../force_bind'
|
3
|
-
|
4
|
-
class Mock
|
5
|
-
def the_method; self end
|
6
|
-
end
|
7
|
-
|
8
|
-
class TestForceBind < Test::Unit::TestCase
|
9
|
-
def test_force_bind_class
|
10
|
-
proc = Mock.instance_method(:the_method).force_bind(Mock)
|
11
|
-
assert_equal Mock, proc.call
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_force_bind_object
|
15
|
-
mock = Mock.new
|
16
|
-
proc = Mock.instance_method(:the_method).force_bind(mock)
|
17
|
-
assert_equal mock, proc.call
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_force_bind_object_from_other_class
|
21
|
-
arr = Array.new
|
22
|
-
proc = Mock.instance_method(:the_method).force_bind(arr)
|
23
|
-
assert_equal arr, proc.call
|
24
|
-
end
|
25
|
-
end
|