Search code examples
rubyprocinstance-eval

Passing around procs between different objects


I'm trying to make the following work but I'm obviously missing something:

class Person
  def fetch
    puts 'Fetch it boy!'
  end

  def action(data)
    data.call
  end
end

class Animal
  def play
    Person.new.action(proc { fetch })
  end
end

Animal.new.play  # undefined local variable or method `fetch' for #<Animal:0x0000000001b172d0>

My goal is to get Person execute a method Animal has chosen. I've also tried using instance_exec but to no avail.

Now I know I can use eval and just pass the method name as a string but I'd like to avoid that. Any ideas?

Thanks in advance

Disclaimer (of sorts):
Please don't propose code restructuring or rewrites. The purpose of the question is to better understand how blocks behave when passed around between objects.


Solution

  • You are looking for:

    instance_eval(&data)
    

    object.instance_eval evaluates block, but replaces self within that block (which would normally be self of the context block was created in) with object:

    whoami = proc { self }
    
    whoami.call => main
    1.instance_eval(&whoami) => 1 
    

    Note however, that instance_eval also passes an object as an argument to the block, which might be problematic if you want to pass a lambda:

    whoami = lambda { self }
    1.instance_eval(&whoami) #=> ArgumentError (wrong number of arguments (given 1, expected 0))
    

    There is another similar method: instance_exec. Not only it does not pass self as an argument:

    whoami = lambda { self }
    1.instance_exec(&whoami) #=> 1
    

    but it additionally allows you to pass other arguments:

    add_number = lambda { |number| self + number }
    1.instance_exec(3, &add_number) #=> 4
    

    Naturally, both methods need to be used with extra caution and very sparingly - they are nice when you want to declare class-wide hooks in a declarative manner to be executed on each instance. They should not be used as a mean of interaction between two objects, unless you really know what you are ding and can justify it does not validate encapsulation.