Take this example Proc:
以这个例子Proc:
proc = Proc.new {|x,y,&block| block.call(x,y,self.instance_method)}
It takes two arguments, x and y, and also a block.
它有两个参数,x和y,还有一个block。
I want to execute that block using different values for self. Something like this nearly works:
我想用不同的self值来执行那个block。类似这样的东西几乎可以工作:
some_object.instance_exec("x arg", "y arg", &proc)
However that doesn't allow you to pass in a block. This also doesn't work
但是,这并不允许您传入一个块。这也不起作用
some_object.instance_exec("x arg", "y arg", another_proc, &proc)
nor does
也没有
some_object.instance_exec("x arg", "y arg", &another_proc, &proc)
I'm not sure what else could work here. Is this possible, and if so how do you do it?
我不知道这里还能做什么。这可能吗?如果可能的话,你怎么做呢?
Edit: Basically if you can get this rspec file to pass by changing the change_scope_of_proc
method, you have solved my problem.
编辑:基本上,如果您可以通过更改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
2 个解决方案
#1
7
To solve this, you need to re-bind the Proc to the new class.
要解决这个问题,需要将Proc重新绑定到新类。
Here's your solution, leveraging some good code from Rails core_ext:
以下是您的解决方案,利用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
#2
-2
I don't think you can do this, and the trouble isn't passing multiple blocks. Proc's and blocks are closures and capture their bindings at the point of creation. self
is part of that binding, so even if you change self with instance_eval
, when you call
the proc/block it executes in its binding, with the self it closed over:
我不认为你能做到这一点,麻烦的是你不能越过多个街区。Proc和块是闭包,在创建时捕获它们的绑定。self是绑定的一部分,所以即使你用instance_eval改变self,当你调用proc/block时它在绑定中执行,而self则关闭:
$ 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 will let you capture a closure's Binding with Kernel#binding, but offers no way to set the binding associated with a Proc. You can specify a binding for the string version of Kernel#eval, but that still doesn't let you change the binding of a proc you call.
Ruby将允许您捕获与内核#绑定的闭包绑定,但不提供设置与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
#1
7
To solve this, you need to re-bind the Proc to the new class.
要解决这个问题,需要将Proc重新绑定到新类。
Here's your solution, leveraging some good code from Rails core_ext:
以下是您的解决方案,利用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
#2
-2
I don't think you can do this, and the trouble isn't passing multiple blocks. Proc's and blocks are closures and capture their bindings at the point of creation. self
is part of that binding, so even if you change self with instance_eval
, when you call
the proc/block it executes in its binding, with the self it closed over:
我不认为你能做到这一点,麻烦的是你不能越过多个街区。Proc和块是闭包,在创建时捕获它们的绑定。self是绑定的一部分,所以即使你用instance_eval改变self,当你调用proc/block时它在绑定中执行,而self则关闭:
$ 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 will let you capture a closure's Binding with Kernel#binding, but offers no way to set the binding associated with a Proc. You can specify a binding for the string version of Kernel#eval, but that still doesn't let you change the binding of a proc you call.
Ruby将允许您捕获与内核#绑定的闭包绑定,但不提供设置与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