juandebravo
juandebravo

Reputation: 200

Create a Mixin with a set of methods than can call to another class methods

I want to define a set of methods that can be added to a class (C in the example) using a Mixin. These methods can be defined by any class that inherits from another class (A in the example) and should be able to call methods in the receiver instance (C instance).

I have this snippet of code:

M = Module.new

class A
  class << self
    def init(method)
      if block_given?
        M.send(:define_method, method) do
          instance_exec &Proc.new
        end
      else
        block = self.method(method).to_proc
        M.send(:define_method, method) do
          yield block.call
        end
      end
    end
  end
end

class B < A
  init(:foo) do
    "foo+".concat(c_method)
  end

  def self.bar
    "bar+".concat(c_method)
  end

  init(:bar) 
end

C = Class.new do
  def c_method
    "c_method"
  end
end

c = C.new

c.extend(M)

puts c.foo

puts c.bar

Adding methods using blocks works, but last line fails :(

foo+c_method
test.rb:28:in `bar': undefined local variable or method `c_method' for B:Class (NameError)
from test.rb:15:in `call'
from test.rb:15:in `block in init'
from test.rb:46:in `<main>'

What I'm doing wrong? Or this makes no sense?

Thanks

Juan

Upvotes: 2

Views: 155

Answers (2)

juandebravo
juandebravo

Reputation: 200

It seems that what I'm trying to do is to unbind the method :bar from B and bind to C, what it's not allowed. You can find more info in this great post

M = Module.new

class A
  class << self
    def init(method)
      if block_given?
        M.send(:define_method, method) do
          instance_exec &Proc.new
        end
      else
        block = self.method(method).unbind
        M.send(:define_method, method) do
          m = block.bind(self)
          puts m
        end
      end
    end
  end
end

class B < A
  init(:foo) do
    "foo+".concat(c_method)
  end

  def self.bar
    "bar+".concat(c_method)
  end

  init(:bar) 
end

C = Class.new do
  def c_method
    "c_method"
  end
end

c = C.new

c.extend(M)

puts c.foo

puts c.bar

foo+c_method
test.rb:16:in `bind': singleton method called for a different object (TypeError)
from test.rb:16:in `block in init'
from test.rb:48:in `<main>'

Upvotes: 0

WarHog
WarHog

Reputation: 8710

When you prepare instance_exec &Proc.new inside if statement, this statement is executed within instance of C class as context. You can verify this by adding puts self inside block for init(:foo). On the other hand, when you call yield block.call you yield thread execution into context of B class object (not to instance of this class, of course :) ). This place of your code doesn't know anything about C::c_method and this is cause of error.

Upvotes: 1

Related Questions