3

This is a follow-up question to How to determine the class a method was defined in? (hope you don't mind the similarity)

Given a class hierarchy, can a method retrieve its own Method instance?

class A
  def foo
    puts "A#foo: `I am #{method(__method__)}'"
  end
end

class B < A
  def foo
    puts "B#foo: `I am #{method(__method__)}'"
    super
  end
end

A.new.foo
# A#foo: `I am #<Method: A#foo>'

B.new.foo
# B#foo: `I am #<Method: B#foo>'
# A#foo: `I am #<Method: B#foo>' # <- A#foo retrieves B#foo

So that B.new.foo instead prints

# B#foo: `I am #<Method: B#foo>'
# A#foo: `I am #<Method: A#foo>' # <- this is what I want

In the previous question, Jörg W Mittag suspected that retrieving the class a method was defined in might violate object-oriented paradigms. Does this apply here, too?

Shouldn't a method "know itself"?

Community
  • 1
  • 1
Stefan
  • 109,145
  • 14
  • 143
  • 218
  • Perhaps, a cheat will be somehow making that an interned string, and then calling it once from the original location. – sawa Jan 12 '16 at 14:02
  • 1
    @sawa printing the method instance is not the point, although a frozen string could probably work in that case ;-) – Stefan Jan 12 '16 at 14:05

2 Answers2

3

I found a method that exactly does that.

class A
  def foo
    puts "A#foo: `I am #{method(__method__).super_method || method(__method__)}'"
  end
end

I really admire Matz and the Ruby core developers. The existence of such method means that they had in mind such situation, and had thought about what to do with it.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • 3
    Ruby 2.2 + only. Not sure output is as expected, never prints `B#foo`. Also, does not work - if we had `class A < C` where `C` is new parent class. – Wand Maker Jan 12 '16 at 14:16
  • @WandMaker Probably the definition on class `A` was the main point of Stefan. So I removed the definition for `B`. If it is needed, Stefan's original code can be used for that part – sawa Jan 12 '16 at 14:20
  • @WandMaker Regarding your point with `C`, that is not a problem. We can define the method in two different ways depending on the need. If we want it to return the lexical method name, we can use what I gave in the answer. If we want the method name to depend on the receiver, we can use Stefan's original code. – sawa Jan 12 '16 at 14:23
  • I was just comparing the output of your method with the expected output in question. – Wand Maker Jan 12 '16 at 14:25
  • @WandMaker My answer before I removed the `B` part was wrong in that sense, but as I wrote, you can just use Stefan's definition for `B`, and you will get the expected result. – sawa Jan 12 '16 at 14:27
  • I don't think answer for this question can be that trivial (not generic) if Stefan is trying to improve answer from this thread - http://stackoverflow.com/questions/34736850/is-the-current-ruby-method-called-via-super/34741488#34741488. Only @stefan can confirm whether this answer is what he needed. – Wand Maker Jan 12 '16 at 14:38
  • @WandMaker I don't know. I am starting to get tired. But I think this answer is going in the right direction, if not correct. – sawa Jan 12 '16 at 14:45
  • 1
    `super_method` seems to be the way to go, although it feels more like a hack (invoking `super_method` from `A#foo` seems illogical because it has no super method; or to put it another way: sometimes the method's body is executed as `A#foo` and sometimes as `B#foo`). @WandMaker however is right regarding `C`: if we had `class A < C` and `class C; def foo; end; end`, then `A.new.foo` would print ``A#foo: `I am #'`` – Stefan Jan 12 '16 at 17:08
2

Building on answer of How to determine the class a method was defined in? and @sawa's answer with respect to super_method, you can use:

def meth(m, clazz)
    while (m && m.owner != clazz) do 
        m = m.super_method
    end
    return m
end

class A 
  def foo
    puts "A#foo: `I am #{meth(method(__method__), Module.nesting.first)}'"
  end
end

class B < A
  def foo
    puts "B#foo: `I am #{meth(method(__method__), Module.nesting.first)}'"
    super
  end
end


B.new.foo
# B#foo: `I am #<Method: B#foo>'
# A#foo: `I am #<Method: A#foo>'

Idea here is that since we know the class/module where the method is defined by Module.nesting.first, we take the current method object found by method(__method__) and iterate through its super chain to find that instance of method whose owner is same as the class/module that defined the method.

Community
  • 1
  • 1
Wand Maker
  • 18,476
  • 8
  • 53
  • 87