More on super-and-extend
A few days ago I posted an article on method wrapping, and I thought I’d touch a bit more on the super-and-extend technique; I’ve had a few questions on it that warrant some clarification.
Multiple Wrappers
Yes, the technique works with multiple wrappers on a single class; when the class is extended with additional modules, they stack and super works as expected (wrappers are applied on the “outside,” so invocations occur in LIFO order).
You can see a full example of this as a Gist. Here is a sample irb session showing that the multiple wrappers are applied correctly:
>> (class << Widget; self; end).ancestors
=> [BazWrapper, BarWrapper, FooWrapper, Class,
Module, Object, Kernel]
>> widget = Widget.new
>> (class << widget; self; end).ancestors
=> [BazWrapper::WrappingB, BazWrapper::WrappingA,
BarWrapper::Wrapping, FooWrapper::Wrapping,
Widget, Object, Kernel]
>> widget.render_on(nil)
BazWrapper::WrappingB
BazWrapper::WrappingA
BarWrapper::Wrapping
FooWrapper::Wrapping
Original
Wrapped Class Inheritance
If you wrap a class, you wrap inherited classes as well, regardless of whether the classes were defined before or after the point the wrapping is applied.
Given the following class definitions and placement of the wrapping, all three
classes would be wrapped as expected.
class Widget
end
class WidgetSubclassDefinedBeforeWrapping < Widget
end
Widget.extend YourWrapperModule
class WidgetSubclassDefinedAfterWrapping < Widget
end
There’s no special sauce here, just the normal laws of inheritance.
The Vagaries of Overriding new
As Eric Anderson pointed out in comments, super-and-extend will not work if the new class method is defined on the wrapped class… but only if super isn’t called. It’s not all dire straights; here are the three scenarios for a wrapped class:
-
The class’ new method is not defined, and the wrapper works as expected. This is the normal case.
-
The class’ new method is defined, and super it used (ostensibly to reach Class#new for an instance… and our wrapper intercepts it first). Since we don’t care about the order that the wrapper’s new(just a hook to extend the instance) fires, the wrapper works as expected. This is an unusual case. For an example of the scenario, see this gist.
-
The class’ new method is defined, and does not call super (likely to block instantiation). In this case, the internal wrapping module must be applied manually to an instance.
Luckily, since you’re likely blocking instantiation to make use of the singleton pattern, the single instance may be easily accessed anyhow (conventionally, as ClassName.instance), and something like this may be enough:
ClassName.instance.extend(YourWrapper::InternalWrapping)
Keep in mind that defining new is generally considered verboten or at least in poor taste. Ask yourself if you actually need
to use a strict, enforced singleton pattern by sabotaging new; in my opinion it’s often an unnecessary formal constraint.






