The documentation says:
Macro defs allow you to define a method for a class hierarchy which is then instantiated for each concrete subtype.
Is there a way to do this for all types in the hierarchy? I.e. if I have classes A < B < C, can I instantiate the macro method for all three classes, including C?
Pretty sure this is how it already works:
class C
def type_name
{{ @type.name.stringify }}
end
end
class B < C; end
class A < B; end
pp C.new.type_name # => "C"
pp B.new.type_name # => "B"
pp A.new.type_name # => "A"
Unless you are wanting something different?
Since the documentation says “concrete” I wanted to test this for abstract classes, but encountered the following:
class Object
def type_name
"*"
end
end
class C
def type_name
super.type_name + {{ @type.name.stringify }}
end
end
class B < C; end
class A < B; end
pp A.new.type_name # actual: "*A", expected: "*CBA"
pp B.new.type_name # "*B"
pp C.new.type_name # "*C"
Now I’m confused. The method is be instantiated for A, B, & C, but calling the superclass’ method does not work?
The super
keyword doesn’t work like how you’d expect it to. Unlike self
, super
automatically calls the parent method with the given arguments (if any), so super.type_name
in A
and B
calls #type_name
in C
first which returns a string, then it calls #type_name
again on Object
, hence why you got “*”.
Despite that technicality, it doesn’t actually solve the issue — as you’ve discovered it’s only calling #type_name
on C
because the method is only defined on C
so A
and B
just use that. Solving this requires a bit more macro magic using the macro inherited
hook:
macro def_type_name
def type_name
super + {{ @type.name.stringify }}
end
end
class C
def_type_name
macro inherited
def_type_name
end
end
Firstly we’ve moved the type_name
definition into the def_type_name
macro and call it in C
; to put it simply, it will define the method on C
for us. Next, we’re calling def_type_name
in the inherited
macro. That macro will run every time C
is inherited which, in this case, is twice. That allows the macro interpreter to evaluate {{ @type.name.stringify }}
in A
and B
.