Bug #14704
closedModule#ancestors looks wrong when a module is both included and prepended in the same class.
Description
Module#ancestors looks wrong when a module is both included and prepended in the same class.
Here is the example script:
module M3; end
module M1
include M3
end
module M2
prepend M3
end
class Sub
include M1
include M2
end
# [Sub, M1, M3, M2, Object, Kernel, BasicObject]
p Sub.ancestors
The output is expected to be [Sub, M2, M1, M3, Object, Kernel, BasicObject] or [Sub, M3, M2, M1, Object, Kernel, BasicObject] or [Sub, M3, M2, M1, M3, Object, Kernel, BasicObject], but the actual is [Sub, M1, M3, M2, Object, Kernel, BasicObject].
When the M1 and M2 module aren't included or prepended at all like the below script, the result is [Sub, M2, M1, Object, Kernel, BasicObject]. In the first example, the position of the M2 module seems to be wrong.
module M1; end
module M2; end
class Sub
include M1
include M2
end
# [Sub, M2, M1, Object, Kernel, BasicObject]
p Sub.ancestors
Updated by jeremyevans0 (Jeremy Evans) about 6 years ago
- Related to Bug #7844: include/prepend satisfiable module dependencies are not satisfied added
Updated by jeremyevans0 (Jeremy Evans) almost 5 years ago
- Status changed from Open to Closed
The reason for this behavior is, at the point of the Sub.include M2 call, Sub.ancestors is [Sub, M1, M3, Object, Kernel, BasicObject] and M2.ancestors is [M3, M2]. So Sub.include M2 looks in the ancestry tree for M3, since that is the first ancestor of M2. It finds the ancestor already exists, so it does not add it. Then it adds the next ancestor, M2, directly after. Hence why you get M1, M3, M2 in that order.
So this behavior isn't a bug, it's just how Module#include works. There's not a way to handle all cases perfectly. You either have to tradeoff on the order or allow modules to be added more than once:
-
M1, M3, M2(current behavior)M1appears beforeM3,M2appears afterM3, as you would expect sinceM1includesM3andM2prependsM3. -
M2, M1, M3:M3comes afterM2even thoughM2prependsM3. -
M3, M2, M1:M3comes beforeM1even thoughM1includesM3. Requires moving theM3iclass from afterM1to beforeM3(includenever moves positions of existing ancestors). -
M3, M2, M1, M3:M3appears multiple times in ancestry list.
If we are going to change the behavior, only M3, M2, M1, M3 appears a reasonable candidate, and that would be a feature request to change include to add a module even though the module is already in the receiver's ancestors. I think that approach is likely to cause backwards compatibility issues.
I'm going to close this now. If you would like this reopened as a feature request to allow include to insert modules that are already in the ancestry list, please respond.