Ruby 2.7+.
I have methods in a couple of modules that are mixed in, and are invoked with super
. Each of the module methods invokes super
in turn, so all methods by that name in the mixed-in modules are invoked, although perhaps not in a deterministic order.
My question is: Can a method tell programmatically (as opposed to hard-coding) from what module it's been mixed in?
module A
def initialize(*args, **kwargs)
puts("something magic")
end
end
module B
def initialize(*args, **kwargs)
puts("something magic")
end
end
class C
include A
include B
def initialize(*args, **kwargs)
puts("actual #{class.name} initialize")
super
end
end
When run, this will print three lines. What I'm seeking is something like class.name
, specific to each module, that identifies the module that supplied the initialize
method that's running. The "something magic*
strings would be replaced with this actual magic. 🙂
Thanks!
My first attempt was to call super_method
to get all the initializers up the stack:
module A
def initialize
A # return for comparison
end
end
module B
def initialize
B
end
end
class C
include A
include B
def initialize
C
end
def super_initializers
init = method(:initialize)
while init
print init.call, " == " # get hardcoded module from `initialize`
p init.owner # get the module dynamically
init = init.super_method # keep getting the super method
end
end
end
>> C.new.super_initializers
C == C
B == B
A == A
== BasicObject
Second idea is to use Module.nesting
, I think this is what you're looking for:
module A
def initialize
# i, o, m = method(:initialize), [], Module.nesting[0]
# while i; o << i.owner; i = i.super_method; end
# print "prev "; p o[o.index(m)-1] # previous super
puts "A == #{Module.nesting[0]}"
# print "next "; p o[o.index(m)+1] # next super
super
end
end
module B
def initialize
puts "B == #{Module.nesting[0]}"
super
end
end
# add a class
class Y
def initialize
puts "Y == #{Module.nesting[0]}"
super
end
end
# add some nesting
module X
class Z < Y
def initialize
puts "Z == #{Module.nesting[0]}"
super
end
end
end
class C < X::Z
include A
include B
def initialize
puts "C == #{Module.nesting[0]}"
super
end
end
>> C.new
C == C
B == B
A == A
Z == X::Z
Y == Y
Actually, never thought about that this could be useful, but it works:
def super_trace m
while m;
p m.owner; m = m.super_method
end
end
>> super_trace User.new.method(:save)
ActiveRecord::Suppressor
ActiveRecord::Transactions
ActiveRecord::Validations
ActiveRecord::Persistence