changelog
changelog

Reputation: 4681

Reverse delegates

I'm trying to do reverse delegation in Ruby (although fully aware that might not even be a thing.) To illustrate, say I have two classes:

class Foo
  def initialize
    @bar = Bar.new
  end

  def say_hello
    @bar.say_hello
  end

  def greeting
    "OHAI"
  end
end

class Bar
  def say_hello
    puts greeting
  end

  def greeting
    "Hello!"
  end
end

How would I go about making a macro method on Foo that tells Bar to use Foo's greeting method?

Upvotes: 1

Views: 184

Answers (1)

Cary Swoveland
Cary Swoveland

Reputation: 110685

You could change class Bar as follows:

class Bar
  def say_hello
    puts greeting
  end

  def use_foos_greeting
    self.class.class_eval do
      def greeting
        @f ||= Foo.new
        @f.greeting
      end
    end  
  end

  def use_bars_greeting
    self.class.class_eval do
      alias_method :greeting, :greeting_copy
    end  
  end

  def greeting
    "Hello!"
  end

  alias_method :greeting_copy, :greeting
end

bar = Bar.new
bar.greeting          #=> "Hello!"
bar.use_foos_greeting
bar.greeting          #=> "OHAI"
bar.use_bars_greeting
bar.greeting          #=> "Hello!"

Consider the method:

def use_foos_greeting
  self.class.class_eval do
    def greeting
      @f ||= Foo.new
      @f.greeting
    end
  end  
end

Here

@f ||= Foo.new

is shorthand for

@f = @f || Foo.new

The first time use_foos_greeting is called, @f => nil, so @f is set to Foo.new. In following calls to use_foos_greeting, @f evaluate true, so it will not be changed. The method would work if we had @f = Foo.new instead, but a new instance of Foo would be created each time the method were called (and the instance of Foo it replaced would be garbage-collected).

Note you cannot add:

def initialize
  @f = Foo.new
end

to class Bar to save the Foo instance, because class Foo has

def initialize
  @f = Bar.new
end

which would result in an endless loop.

Upvotes: 1

Related Questions