Skip to content

Instantly share code, notes, and snippets.

@mxgrn
Created October 21, 2010 17:30
Show Gist options
  • Save mxgrn/638919 to your computer and use it in GitHub Desktop.
Save mxgrn/638919 to your computer and use it in GitHub Desktop.
Wrong scope of yield?
# (Ruby version: 1.9.2)
#
##### A million-dollar question: why the following isn't working?
#
#
class A
def self.metamethod(name)
define_method(name) do
yield
end
end
end
class B < A
def m1
"from m1"
end
metamethod :my_method do
m1
end
end
B.new.my_method #=> undefined local variable or method ‘m1’ for B:Class (* B:Class ??? *)
#
#
########### ...while the following does! #############
#
#
class A
def self.metamethod(name)
define_singleton_method(name) do
yield
end
end
end
class B < A
def self.m1
"from m1"
end
metamethod :my_method do
m1
end
end
B.my_method #=> "from m1"
@p8
Copy link

p8 commented Oct 22, 2010

It's not a bug. yield is called on A, not on the instance of A

class A
  def self.metamethod(name)
    define_method(name) do
      yield
    end
  end
end

Is similar to

class A
  def A.metamethod(name)
    A.send(:define_method,name) do
      A.yield
    end
  end
end

@mxgrn
Copy link
Author

mxgrn commented Oct 22, 2010

Right, this makes sense, I guess. Do you know a way to call yield on the instance of A? Thanks!

@p8
Copy link

p8 commented Oct 22, 2010

I'm not sure where you want to use it for (sorry, wasn't at rar10 today).
You can always use instance_eval but you'll need to use something like alias_method_chain to add stuff to the original method.

class A
  def self.metamethod
    instance_eval do
      yield
      #alias_method_chain stuff
    end
  end
end

class B < A
  def m1
    "from m1"
  end

  metamethod do
    def my_method
      m1
    end
  end
end

B.new.my_method

@mxgrn
Copy link
Author

mxgrn commented Nov 12, 2010

The solution was using instance_eval instead of yield:

class A
  def self.metamethod(name, &block)
    define_method(name) do
      instance_eval(&block)
    end
  end
end

class B < A
  def m1
    "from m1"
  end

  metamethod :my_method do
    m1
  end
end

B.new.my_method #=> "from m1"

@p8
Copy link

p8 commented Nov 14, 2010

Nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment