force_bind 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
- class Foo
11
- def bar
12
- puts "I'm inside #{self}"
13
- end
14
+ class Foo
15
+ def bar
16
+ puts "I'm inside #{self}"
14
17
  end
15
-
16
- Foo.new.bar
17
- #=> "I'm inside #<Foo:0x123456>"
18
-
19
- meth = Foo.instance_method(:bar).force_bind(Foo)
20
- meth.call
21
- #=> "I'm inside Foo"
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
- class A
27
- def foo; self end
28
- end
29
-
30
- class B; end
31
-
32
- meth = A.instance_method(:foo).force_bind(B.new)
33
- meth.call
34
- #=> #<B:0x123456>
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
- load 'force_bind.gemspec'
8
- Rake::GemPackageTask.new(SPEC) do |pkg|
9
- pkg.gem_spec = SPEC
10
- pkg.need_zip = true
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 "Install the gem locally"
15
- task :install => :package do
16
- sh "#{SUDO} gem install pkg/#{SPEC.name}-#{SPEC.version}.gem --local"
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
@@ -1,5 +1,4 @@
1
1
  require 'mkmf'
2
- $CPPFLAGS += " -DRUBY_19" if RUBY_VERSION =~ /1.9/
3
- ext = 'force_bind'
4
- dir_config(ext)
5
- create_makefile(ext)
2
+
3
+ $defs << "-DRUBY192_OR_GREATER" if RUBY_VERSION >= "1.9.2"
4
+ create_makefile('force_bind')
@@ -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
- VALUE oclass;
5
- VALUE rclass;
6
- VALUE recv;
7
- ID id, oid;
8
- struct RNode *body;
38
+ VALUE oclass;
39
+ VALUE rclass;
40
+ VALUE recv;
41
+ ID id, oid;
42
+ struct RNode *body;
9
43
  };
10
44
 
11
- static VALUE
45
+ VALUE
12
46
  umethod_force_bind(VALUE method, VALUE recv)
13
47
  {
14
- struct METHOD *data, *bound;
48
+ struct METHOD *data, *bound;
15
49
 
16
- Data_Get_Struct(method, struct METHOD, data);
17
- method = Data_Make_Struct(rb_cMethod, struct METHOD, free, -1, bound);
18
- *bound = *data;
19
- bound->recv = recv;
20
- bound->rclass = TYPE(recv) == T_CLASS ? RCLASS(recv) : CLASS_OF(recv);
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
- return method;
56
+ return method;
23
57
  }
24
58
 
59
+ #endif /* RUBY192_OR_GREATER */
60
+
25
61
  void
26
62
  Init_force_bind()
27
63
  {
28
- rb_define_method(rb_cUnboundMethod, "force_bind", umethod_force_bind, 1);
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
+ }
@@ -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
- version: 0.1.0
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: 2009-07-15 00:00:00 -04:00
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
- - test/test_force_bind.rb
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.5
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
 
@@ -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