6

I would like to dynamically determine the class the current method was defined in.

Here's a static example of what I'm trying to do:

class A
  def foo
    puts "I was defined in A"
  end
end

class B < A
  def foo
    puts "I was defined in B"
    super
  end
end

A.new.foo
# I was defined in A

B.new.foo
# I was defined in B
# I was defined in A  <- this is the tricky one

How can I replace A and B in the strings above with a dynamic expression?

Apparently, #{self.class} does not work. (it would print I was defined in B twice for B)

I suspect that the answer is "you can't", but maybe I'm overlooking something.

Stefan
  • 109,145
  • 14
  • 143
  • 218

3 Answers3

5

What about this?

class A
  def foo
    puts "I was defined in #{Module.nesting.first}"
  end
end

class B < A
  def foo
    puts "I was defined in #{Module.nesting.first}"
    super
  end
end

Corrected following WandMaker's suggestion.

sawa
  • 165,429
  • 45
  • 277
  • 381
  • 1
    I've selected this answer because `Module.nesting` solves the shown problem and because sawa posted first. However, it does not solve the actual problem I was addressing, so I've posted a [follow-up question](http://stackoverflow.com/q/34745608/477037). – Stefan Jan 12 '16 at 13:56
3

You could use Module.nesting.first.

However, note that this works purely lexically, the same way constants resolution works, so it won't cut it if you have more dynamic needs:

Foo = Class.new do
  def foo
    Module.nesting
  end  
end

Foo.new.foo # => []
ndnenkov
  • 35,425
  • 9
  • 72
  • 104
  • In case of nested modules, it may not give right answer. Really depends on what OP needs - class name or module name. – Wand Maker Jan 12 '16 at 11:56
  • 3
    Try defining `class A` inside a `module C` - it will say method was defined in `C` when you use `Module.nesting.last`. May be one need to use `Module.nesting.first` – Wand Maker Jan 12 '16 at 11:59
3

I have this nagging feeling that if you could do this, it would violate object-orientated encapsulation, although I can't quite place my finger on exactly why. So, it shouldn't come as a surprise that it's hard.

I can see a way if you are open to modifying the method definitions:

class A
  this = self
  define_method(:foo) do
    puts "I was defined in #{this}"
  end
end

class B < A
  this = self
  define_method(:foo) do
    puts "I was defined in #{this}"
    super()
  end
end

A.new.foo
# I was defined in A

B.new.foo
# I was defined in B
# I was defined in A
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Nice one, but still wont work in some dynamic cases. Like defining a method on a class from outside the class. (: – ndnenkov Jan 12 '16 at 12:26
  • My understanding is that `Module.nesting` is among the few methods/keywords that work lexically (i.e., behaviour depends on its location in the source code), and hence cannot be passed directly from method to method; its value has to be passed as a variable. Those are `binding`, `__FILE__`, `__LINE__`, `__dir__`, `Module.nesting`, and perhaps some more. – sawa Jan 12 '16 at 12:45
  • 1
    @sawa `__method__` is another one. – Stefan Jan 12 '16 at 13:13
  • @Stefan I thought that the reason WandMaker's deleted answer did not work was because `__method__` isn't lexical. It picks up where it was called from, not where it is written. – sawa Jan 12 '16 at 13:31
  • 1
    @sawa I think it's lexical, too (in a way). If you have a method `def foo ; __method__ ; end` and you define a new method `define_method(:bar, method(:foo))`, then `bar` will still return `:foo` – Stefan Jan 12 '16 at 13:39