2012-03-26 7 views
13

Tome este ejemplo Proc:¿Cómo puedo llamar a un Proc que toma un bloque en un contexto diferente?

proc = Proc.new {|x,y,&block| block.call(x,y,self.instance_method)} 

Se toma dos argumentos, x e y, y también un bloque.

Quiero ejecutar ese bloque usando diferentes valores para mí mismo. Algo así casi funciona:

some_object.instance_exec("x arg", "y arg", &proc) 

Sin embargo, eso no le permite pasar en un bloque. Esto también no funciona

some_object.instance_exec("x arg", "y arg", another_proc, &proc) 

tampoco

some_object.instance_exec("x arg", "y arg", &another_proc, &proc) 

No estoy seguro de qué otra cosa podría trabajar aquí. ¿Es esto posible y, de ser así, cómo lo haces?

Editar: Básicamente si puede obtener este archivo rspec para pasar cambiando el método change_scope_of_proc, ha resuelto mi problema.

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

+1 para el suministro de la prueba a la superficie de manera explícita la cuestión – dbenhur

+0

1 para la prueba, gracias, eso hace que sea muy fácil de ver lo que querer. – joelparkerhenderson

Respuesta

7

Para solucionar esto, debe volver a vincular el Proc a la nueva clase.

Aquí es su solución, aprovechando un buen código de 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

¡Gracias, esto es perfecto! Para los tldrers, de la gema 'activesupport', si' requieres 'active_support/core_ext'' obtienes acceso al método 'ProC# bind', que hace exactamente lo que yo requería. Desafortunadamente, se ha [depreciado en HEAD] (https://github.com/rails/rails/commit/9c857db75788c21f6184279c130e79f21c750f9f) debido a una pérdida de memoria de símbolo. Sin embargo, no es probable que exista una solución mejor si esto es lo que el equipo de Rails terminó usando. Además, el código que se usó para reemplazar los usos de 'ProC# bind' no pasa mi prueba; me pregunto si saben que se ha perdido la funcionalidad ... –

-2

No creo que pueda hacer esto, y el problema no está en pasar varios bloques. Proc y bloques son cierres y capturan sus enlaces en el punto de creación. self es parte de esa unión, por lo que incluso si cambia uno mismo con instance_eval, cuando call el proc/Bloque se ejecuta en su unión, con el yo que se cerró sobre: ​​

$ 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:[email protected](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 que le permite capturar un cierre de Binding con Kernel#binding, pero no ofrece ninguna forma de establecer el enlace asociado con un Proc. Puede especificar un enlace para la versión de cadena de Kernel#eval, pero eso aún no le permite cambiar el enlace de un proceso al que llame.

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

Puede cambiar el enlace de 'self' en un proceso pasando directamente a' instance_exec'. Por ejemplo, ''bar'.instance_exec & p' produce' String: 2151790940' cuando lo intenté, mostrando que el self se modificó. El punto clave es que los procs invocados dentro del proc dado a instance_exec no han cambiado su enlace, lo que explica el resultado que recibes. Así que no creo que esto pruebe de una forma u otra si lo que quiero hacer es posible o no. –

+0

Ah, buena observación. Creo que veo una forma de usar eso para que su especificación pase, pero tengo que salir corriendo a trabajar ahora. Voy a jugar con esto más tarde. – dbenhur

Cuestiones relacionadas