From: "shugo (Shugo Maeda) via ruby-core" Date: 2026-07-11T11:33:32+00:00 Subject: [ruby-core:126047] [Ruby Feature#9779] Add Module#descendants Issue #9779 has been updated by shugo (Shugo Maeda). Subject changed from Add Module#descendents to Add Module#descendants It seems that there's no progress since `Class#descendants` ([Feature #14394]) was reverted, so I've created a new pull request: https://github.com/ruby/ruby/pull/17807 I prefer `Module#descendants` because `Class` would automatically inherit `Module#descendants` but the converse does not hold. I also believe `Module#descendants` is a more natural API than `Class#descendants`, because `descendants` should ideally be the dual of `Module#ancestors`; i.e., `y ��� x.descendants ��� x ��� y.ancestors`. Defining it at the `Module` level also answers the design question left open when [Feature #14394] was reverted (https://bugs.ruby-lang.org/issues/14394#note-33). Assuming that `Module#descendants` is the dual of `Module#ancestors`, it should include `self` in its return value. However, I implemented `Module#descendants` not to include `self`, following ActiveSupport's `Class#descendants` and the reverted implementation of [Feature #14394]. Singleton classes are not included either, following the documented behavior of `Class#subclasses`. Note that `[mod, *mod.descendants]` can be used when `self` is needed. The implementation reuses the existing subclass lists maintained for method cache invalidation, so no additional bookkeeping is introduced. Like `Class#subclasses`, the receiver does not hold strong references to its descendants, so the result may change after GC. Refinements are not included. One use case of `Module#descendants` is Aspect Oriented Programming (illustrative, not a working example): ```ruby module Aspect def self.around(type, method_pattern, &advice) type.descendants.grep(Class).each do |klass| methods = klass.instance_methods(false).grep(method_pattern) next if methods.empty? wrapper = Module.new do methods.each do |name| define_method(name) do |*args, &blk| advice.call(self, name, args) { super(*args, &blk) } end end end klass.prepend(wrapper) end end end Aspect.around(Persistable, /\Asave/) do |obj, name, args, &proceed| logger.info("#{obj.class}##{name} start") proceed.call ensure logger.info("#{obj.class}##{name} end") end ``` A similar approach could also be used by instrumentation libraries such as OpenTelemetry and Datadog's dd-trace-rb. Note that we have to use hooks like `inherited` or `included` to patch classes which will be loaded later. `Module#descendants` is also useful for diagnostics, e.g., finding all classes a monkey patch is prepended to, or all models that include a specific Rails concern. ---------------------------------------- Feature #9779: Add Module#descendants https://bugs.ruby-lang.org/issues/9779#change-118040 * Author: shugo (Shugo Maeda) * Status: Open ---------------------------------------- Now classes have links to their subclasses, so how about to add Module#descendents? ```ruby class X end class Y < X end class Z < Y end p X.descendents #=> [X, Y, Z] module A end module B include A end module C include A end p A.descendents #=> [A, C, B] ``` One of my own use cases is to implement AspectJ like pointcut which matches all subclasses of the given class. Considerations: * Should the return value of Module#descendents include self? * Should the return value of Module#descendents include singleton classes? I attach a patch, where the return value includes both self and singleton classes. ---Files-------------------------------- 0001-Add-Module-descendents.patch (3.75 KB) -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/