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 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