2012-03-26 23 views
13

Hãy ví dụ này Proc:Làm cách nào tôi có thể gọi một Proc có một khối trong một ngữ cảnh khác?

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

Phải mất hai đối số, x và y, và cũng là một khối.

Tôi muốn thực thi khối đó bằng các giá trị khác nhau cho bản thân. Một cái gì đó như thế này gần như hoạt động:

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

Tuy nhiên điều đó không cho phép bạn đi qua một khối. Điều này cũng không hoạt động

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

cũng không

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

Tôi không chắc chắn những gì khác có thể làm việc ở đây. Điều này có thể, và nếu như vậy làm thế nào để bạn làm điều đó?

Chỉnh sửa: Về cơ bản nếu bạn có thể nhận tệp rspec này để chuyển bằng cách thay đổi phương thức change_scope_of_proc, bạn đã giải quyết được sự cố của mình.

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 để cung cấp kiểm tra bề mặt các vấn đề một cách rõ ràng – dbenhur

+0

1 cho kỳ thi này, cảm ơn bạn, mà làm cho nó rất dễ dàng để xem những gì bạn muốn. – joelparkerhenderson

Trả lời

7

Để giải quyết vấn đề này, bạn cần phải liên kết lại Proc với lớp mới.

Đây là giải pháp của bạn, tận dụng một số mã tốt từ 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

Thankyou, điều này là hoàn hảo! Đối với các tldrers, từ đá quý 'activesupport', nếu bạn' require 'active_support/core_ext'' bạn có thể truy cập vào phương thức 'ProC# bind', nó thực hiện chính xác những gì tôi yêu cầu. Thật không may, nó đã được [khấu hao trong HEAD] (https://github.com/rails/rails/commit/9c857db75788c21f6184279c130e79f21c750f9f) do rò rỉ bộ nhớ biểu tượng. Nó không có khả năng có một giải pháp tốt hơn mặc dù nếu đây là những gì đội đường ray kết thúc bằng cách sử dụng. Ngoài ra, mã đã được sử dụng để thay thế việc sử dụng 'ProC# bind' không vượt qua bài kiểm tra của tôi - tôi tự hỏi nếu họ biết rằng chức năng đã bị mất ... –

-2

Tôi không nghĩ rằng bạn có thể làm điều này và sự cố không chuyển nhiều khối. Proc và khối là đóng cửa và nắm bắt các ràng buộc của họ tại thời điểm sáng tạo. self là một phần của liên kết đó, vì vậy ngay cả khi bạn thay đổi bản thân với instance_eval, khi bạn call proc/chặn nó thực thi trong ràng buộc của nó, với sự tự nó đóng cửa trên:

$ 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 sẽ cho phép bạn chụp một đóng cửa của Binding với Kernel#binding, nhưng không có cách nào để đặt ràng buộc được liên kết với một Proc. Bạn có thể chỉ định một ràng buộc cho phiên bản chuỗi của Kernel#eval, nhưng điều đó vẫn không cho phép bạn thay đổi ràng buộc của một proc bạn gọi.

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

Bạn có thể thay đổi liên kết 'self' trong proc bằng cách chuyển nó trực tiếp tới' instance_exec'. Ví dụ: ''bar'.instance_exec & p' output' String: 2151790940' khi tôi thử nó, cho thấy rằng bản thân đã bị thay đổi. Điểm mấu chốt là procs được gọi bên trong proc được đưa ra cho instance_exec không có sự ràng buộc của chúng thay đổi, điều này giải thích kết quả bạn đã nhận được. Vì vậy, tôi không nghĩ rằng điều này chứng minh một cách này hay cách khác nếu những gì tôi muốn làm là có thể hay không. –

+0

Ah, quan sát tốt đẹp. Tôi nghĩ rằng tôi thấy một cách để sử dụng để có được spec của bạn để vượt qua, nhưng phải chạy ra để làm việc ngay bây giờ. Tôi sẽ fiddle với điều này sau. – dbenhur

Các vấn đề liên quan