tags:

views:

87

answers:

4

How do I use methods from two different namespaces?

class Bar
  def self.configure &block
    new.instance_eval &block
  end

  def method2
    puts "from Bar"
  end
end

class Foo
  def method1
    puts "from Foo"
  end

  def start
    Bar.configure do
      method1
      method2
    end
  end
end

Foo.new.start

In the above example the method1 can't be called because it is not from the Bar scope. How do I make methods from both scopes callable at the same time?

+1  A: 

Try this:

class Bar                              
  def initialize(foo)
    puts "init"
    @f = foo 
  end 

  def self.configure(foo, &block)
    new(foo).instance_eval &block
  end 

  def method2
    puts "from Bar"
  end 
end 

class Foo 
  def method1
    puts "from Foo"
  end 

  def start
    Bar.configure(self) do
      @f.method1
      method2
    end 
  end 
end 

This makes @f a class level instance variable of Bar, which is set when you initialize an object of Bar using new(foo) in Bar.configure. The block being passed assumes the existence of @f, which contains a reference to the object of class Foo.

It's a convoluted way of doing this - I can't think of anything better though. It'd be interesting to know the use case.

Sudhanshu
Fixed a small bug.
Sudhanshu
+3  A: 

The trick is to forward missing method calls to the instance of Foo:

class Bar
    def self.configure(&block)
        o = new
        context = eval('self', block.binding)

       class << o; self; end.send(:define_method, :method_missing) do |meth, *args|
           context.send(meth, *args)
       end

       o.instance_eval &block
    end

    def method2
        puts "from Bar"
    end
end

class Foo
    def method1
        puts "from Foo"
    end

   def start
       Bar.configure do
           method1
           method2
       end
   end
end

Foo.new.start #=> "from Foo"
              #=> "from Bar"
banister
+1 for the context.send trick. Will this continue to work if Bar also had a method1?
Sudhanshu
@Sudhanshu, yes, but `method1` in `Bar` will get precedence
banister
+1  A: 

Maybe, this is the easiest way. It doesn't need to modify Bar.

class Bar
  def self.configure &block
    new.instance_eval &block
  end

  def method2
    puts "from Bar"
  end
end

class Foo
  def method1
    puts "from Foo"
  end

  def start
    foo = self # add
    Bar.configure do
      foo.method1 # modify
      method2
    end
  end
end

Foo.new.start
Shinya Miyazaki
+1  A: 

I would simplify your code as follows:

class Bar
  def self.configure &block
    obj = new
    block.call(obj)
    obj
  end

  def method2
    puts "from Bar"
  end
end

class Foo
  def method1
    puts "from Foo"
  end

  def start
    Bar.configure do |obj|
      method1
      obj.method2
    end
  end
end

Foo.new.start

Block logic is clean and implementation doesn't require context switching. You are using the standard ruby functionality of passing parameters to the block.

KandadaBoggu