Identifying a Ruby module method invoked by super?

104 Views Asked by At

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!

1

There are 1 best solutions below

1
Alex On BEST ANSWER

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

https://rubyapi.org/3.1/o/module#method-c-nesting

https://rubyapi.org/3.1/o/method#method-i-super_method