举个例子Proc:
proc = Proc.new {|x,y,&block| block.call(x,y,self.instance_method)}
它需要两个参数,x和y,以及一个块。
我想使用不同的self值来执行该块。这样的事情几乎可行:
some_object.instance_exec("x arg", "y arg", &proc)
然而,这不允许你传入一个块。这也行不通
some_object.instance_exec("x arg", "y arg", another_proc, &proc)
也不是
some_object.instance_exec("x arg", "y arg", &another_proc, &proc)
我不确定这里还有什么可行的。这是可能的,如果是这样,你怎么做?
编辑:基本上如果您可以通过更改change_scope_of_proc
方法来传递此rspec文件,那么您已解决了我的问题。
require 'rspec'
class SomeClass
def instance_method(x)
"Hello #{x}"
end
end
class AnotherClass
def instance_method(x)
"Goodbye #{x}"
end
def make_proc
Proc.new do |x, &block|
instance_method(block.call(x))
end
end
end
def change_scope_of_proc(new_self, proc)
# TODO fix me!!!
proc
end
describe "change_scope_of_proc" do
it "should change the instance method that is called" do
some_class = SomeClass.new
another_class = AnotherClass.new
proc = another_class.make_proc
fixed_proc = change_scope_of_proc(some_class, proc)
result = fixed_proc.call("Wor") do |x|
"#{x}ld"
end
result.should == "Hello World"
end
end
答案 0 :(得分:8)
要解决此问题,您需要将Proc重新绑定到新类。
这是您的解决方案,利用Rails core_ext中的一些优秀代码:
require 'rspec'
# Same as original post
class SomeClass
def instance_method(x)
"Hello #{x}"
end
end
# Same as original post
class AnotherClass
def instance_method(x)
"Goodbye #{x}"
end
def make_proc
Proc.new do |x, &block|
instance_method(block.call(x))
end
end
end
### SOLUTION ###
# From activesupport lib/active_support/core_ext/kernel/singleton_class.rb
module Kernel
# Returns the object's singleton class.
def singleton_class
class << self
self
end
end unless respond_to?(:singleton_class) # exists in 1.9.2
# class_eval on an object acts like singleton_class.class_eval.
def class_eval(*args, &block)
singleton_class.class_eval(*args, &block)
end
end
# From activesupport lib/active_support/core_ext/proc.rb
class Proc #:nodoc:
def bind(object)
block, time = self, Time.now
object.class_eval do
method_name = "__bind_#{time.to_i}_#{time.usec}"
define_method(method_name, &block)
method = instance_method(method_name)
remove_method(method_name)
method
end.bind(object)
end
end
# Here's the method you requested
def change_scope_of_proc(new_self, proc)
return proc.bind(new_self)
end
# Same as original post
describe "change_scope_of_proc" do
it "should change the instance method that is called" do
some_class = SomeClass.new
another_class = AnotherClass.new
proc = another_class.make_proc
fixed_proc = change_scope_of_proc(some_class, proc)
result = fixed_proc.call("Wor") do |x|
"#{x}ld"
end
result.should == "Hello World"
end
end
答案 1 :(得分:-2)
我认为你不能做到这一点,麻烦不是传递多个块。 Proc和block是闭包,并在创建时捕获它们的绑定。 self
是该绑定的一部分,因此即使您使用instance_eval
更改自身,当您call
在其绑定中执行proc / block时,它会自动关闭:
$ irb
irb(main):001:0> class Foo; def mkproc; Proc.new { puts "#{self.class}:#{object_id}" }; end; end
=> nil
irb(main):002:0> p = Foo.new.mkproc
=> #<Proc:0x00000001b04338@(irb):1>
irb(main):003:0> p.call
Foo:14164520
=> nil
irb(main):004:0> 'bar'.instance_exec { puts "#{self.class}:#{object_id}"; p.call }
String:16299940
Foo:14164520
Ruby将允许您使用Binding捕获闭包Kernel#binding,但无法设置与Proc关联的绑定。您可以为Kernel#eval的字符串版本指定绑定,但这仍然不允许您更改您调用的proc的绑定。
irb(main):005:0> class BindMe; def get_binding(p=nil); binding; end; end
=> nil
irb(main):006:0> b = BindMe.new.get_binding(p)
=> #<Binding:0x00000001f58e48>
irb(main):007:0> eval '"#{self.class}:#{object_id}"', b
=> "BindMe:14098300"
irb(main):008:0> eval '"#{self.class}:#{object_id}"', p.binding
=> "Foo:14164520"
irb(main):009:0> eval "p.call", b
Foo:14164520