2

I found this neat delegator based 'tee' implementation on SO:

https://stackoverflow.com/a/6410202/2379703

And I'm curious what is means for @targets (instance variable) means in the context of a class method:

require 'logger'

class MultiDelegator
  def initialize(*targets)
    @targets = targets
  end

  def self.delegate(*methods)
    methods.each do |m|
      define_method(m) do |*args|
        @targets.map { |t| t.send(m, *args) }
      end
    end
    self
  end

  class <<self
    alias to new
  end
end

log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)

I get that it defining the methods write/close but @targets isn't even defined at this point since .to (aliased to new) has yet to be called so I'd assume @targets is nil.

Can anyone give an explanation as to the logistics of how this code works? Does ruby not even attempt to access/resolve @targets until the method in question is attempted to be called, which would be by the logger after it was instantiated?

1 Answer 1

1

The define_method method is called on a class to create an instance method. Inside that method, the self (and the instance variable) are instances of the class.

For example:

class Foo
  @bar = "CLASS"
  def initialize
    @bar = "INSTANCE"
  end
  def self.make_method
    define_method :whee do
      p @bar
    end
  end
end

begin
  Foo.new.whee
rescue NoMethodError=>e
  puts e
end
  #=> undefined method `whee' for #<Foo:0x007fc0719794b8 @bar="INSTANCE">

Foo.make_method
Foo.new.whee
#=> "INSTANCE"

It is correct that you can ask about instance variables that have never been created, at any time:

class Bar
  def who_dat
    puts "@dat is #{@dat.inspect}"
  end
end

Bar.new.who_dat
#=> dat is nil

The same is true of other aspects of the language. As long as the code in the method is syntactically valid, it may be defined, even if invoking it causes a runtime error:

class Jim
  def say_stuff
    stuff!
  end
end
puts "Good so far!"
#=> Good so far!

j = Jim.new
begin
  j.say_stuff
rescue Exception=>e
  puts e
end
#=> undefined method `stuff!' for #<Jim:0x007f9c498852d8>

# Let's add the method now, by re-opening the class
class Jim # this is not a new class
  def stuff!
    puts "Hello, World!"
  end
end

j.say_stuff
#=> "Hello, World!"

In the above I define a say_stuff method that is syntactically valid, but that calls a method that does not exist. This is find. The method is created, but not invoked.

Then I try to invoke the method, and it causes an error (which we catch and handle cleanly).

Then I add the stuff! method to the class. Now I can run the say_stuff method (on the same instance as before!) and it works just fine.

This last example shows how defining a method does not run it, or require that it would even work when it is run. It is dynamically evaluated each time it is invoked (and only at that time).

Sign up to request clarification or add additional context in comments.

1 Comment

Ahh define_method defines an instance method despite being called in a class method. I read a bit more and it seems that instance_eval is used under the covers so I see how @targets is the instance variable of the calling instance.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.