Feature #11923
openPut Struct accessors into separate module to allow redefining them in Struct.new's block
Description
Struct.new(*args, &block)
creates a subclass of Struct
. Some methods like accessors are defined right in newly created class. Others like #initialize
, #values
and so on are declared in a superclass, i.e. in Struct
.
The fact that #initialize
is defined in a superclass allows redefining constructor as was proposed in #11708. But it is not that simple to redefine accessors (and documentation gives no clue which methods are to be redefined and which aren't):
Point=Struct.new(:x,:y) do
def x=(val)
puts "set x = #{val}"
super
end
end
pt = Point.new(1,2)
pt.x = 3 # => NoMethodError: super: no superclass method `x=' for #<struct Point x=1, y=2>
As accessors are defined just inside a class Point
, we can't use super
. To correctly implement desired logging behavior of #x=
one need to create a module with #x=
method and to prepend it in front of class.
I propose instead one of two solutions which overcome this issue:
- (worse)
Struct.new
when takes a block should not evaluate it in a context of created class. Instead it should create an anonymous module, evaluate block in its context (so that module gets all defined methods) and prepend that module in front of created class. But this possibly can conflict with reopening a class. - (better)
Struct.new
should create an anonymous module, define struct accessors in it and then include a module into created class. Then block should be evaluated in context of the class as it's done now. Sosuper
invocation in#x=
definition from a block supplied toStruct.new
will hit method in included module instead ofNoMethodError
.
There is one possible drawbacks of implementing this (I could overlook some other problems): cutting perfomance due to additional module in ancestors chain.