We have a legacy codebase where rubocop reports some of these errors which I never could wrap my head around:
Don't extend an instance initialized by
Struct.new
. Extending it introduces a superfluous class level and may also introduce weird errors if the file is required multiple times.
What exactly is meant by "a superfluous class level" and what kind of "weird errors" may be introduced?
(Asking because obviously we didn't have any such problems over the last years.)
Struct.new
creates an anonymous class that happens to be a subclass of Struct
:
s = Struct.new(:foo)
#=> #<Class:0x00007fdbc21a0270>
s.ancestors
#=> [#<Class:0x00007fdbc21a0270>, Struct, Enumerable, Object, Kernel, BasicObject]
You can assign that anonymous class to a constant in order to name it:
Foo = Struct.new(:foo)
#=> Foo
Foo.ancestors
#=> [Foo, Struct, Enumerable, Object, Kernel, BasicObject]
That's the regular way to create a Struct
subclass.
Your legacy code on the other hand seems to contain something like this:
class Foo < Struct.new(:foo)
end
Struct.new
creates an anonymous class (it's not assigned to a constant) and Foo
subclasses it, which results in:
Foo.ancestors
#=> [Foo, #<Class:0x00007fee94191f38>, Struct, Enumerable, Object, Kernel, BasicObject]
Apparently, the anonymous class doesn't serve any purpose.
It's like:
class Bar
end
class Foo < Bar # or Foo = Class.new(Bar)
end
Foo.ancestors
#=> [Foo, Bar, Object, Kernel, BasicObject]
as opposed to:
class Bar
end
class Foo < Class.new(Bar)
end
Foo.ancestors
#=> [Foo, #<Class:0x00007fdb870e7198>, Bar, Object, Kernel, BasicObject]
The anonymous class returned by Class.new(Bar)
in the latter example is not assigned to a constant and therefore neither used nor needed.
A superfluous class level is exactly this class entending Struct.new
.
Here is the reference to a more detailed explanation with a source code.
The pull request on this cop also contains a valuable example:
Person = Struct.new(:first, :last) do
SEPARATOR = ' '.freeze
def name
[first, last].join(SEPARATOR)
end
end
is not equivalent to:
class Person < Struct.new(:first, :last)
SEPARATOR = ' '.freeze
def name
[first, last].join(SEPARATOR)
end
end
The former creates ::Person
and ::SEPARATOR
, while the latter creates ::Person
and ::Person::SEPARATOR
.
I believe the constant lookup is mostly referred as “weird errors.”
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With