4

With a(n inherited) method, the receiver/class where it is defined can be achieved by doing:

class A
  def foo; end
end

class B < A
end

B.instance_method(:foo).owner # => A

With a(n inherited) constant, there is no counterpart to instance_method or method method, so it is not straightforward. Is it possible to achieve the class where it is defined?

class A
  Foo = true
end

class B < A
end

B.some_way_to_extract_the_owner_of_constant(:Foo) # => A
sawa
  • 165,429
  • 45
  • 277
  • 381
  • is `B::Foo` even defined? – John Dvorak Feb 21 '16 at 08:11
  • Since there's no method to do it directly I'm going to guess there is no indirect way to do it either, otherwise it would have been encapsulated in said direct method. – John Dvorak Feb 21 '16 at 08:12
  • 1
    One trick is `B.const_defined? :Foo, false` and `A.const_defined? :Foo, false`. I need to figure out, how to do it recursively all ancestors chains up. – Arup Rakshit Feb 21 '16 at 08:28

2 Answers2

5

Like, the below code:

class A
  Foo = true
end

class B < A
end

B.ancestors.find { |klass| klass.const_defined? :Foo, false }
# => A
Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
  • That is a great idea. I am wondering if it works correctly in edge cases like when multiple module are included/prepended, with each having constant definitions with the same name. – sawa Feb 21 '16 at 08:32
  • 1
    Good one, Arup. I was working with `ancestors.find` as well, but only tried `constants`, which of course doesn't work. – Cary Swoveland Feb 21 '16 at 08:36
  • Note that more than one ancestor could have a constant with the same name. This code should still work correctly though because the `ancestors` method returns ancestors in descending order of precedence. – Steve Jorgensen Feb 21 '16 at 08:37
  • @SteveJorgensen The tricky thing about `ancestors`, which I implied in my comment is that `prepend`-ed modules are not distinguished from `include`-d modules in the array returned by `ancestors`. But it may work correctly. I am just not clear by myself. – sawa Feb 21 '16 at 08:39
  • 1
    @sawa I just tried an experiment, and it looks like the order of the `ancestors` array takes care of that too. Prepended modules come first, then the target of the `ancestors` call, then included modules and parent classes. – Steve Jorgensen Feb 21 '16 at 08:48
  • @Arup, I would have beat you if only I gave `constants` an argument! – Cary Swoveland Feb 21 '16 at 08:51
  • @SteveJorgensen Thanks. I think I mistook it with some similar thing. – sawa Feb 21 '16 at 08:52
2

Similar to @Arup's answer, but I've used Module#constants.

class Base
end

class A < Base
  Foo = true
end

class B < A
  Foo = false
end

class C < B
end

C.ancestors.find { |o| o.constants(false).include?(:Foo) }
  #=> B
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100