Skip to content

Restrict classes to the gem namespace - #7

Merged
saturnflyer merged 3 commits into
mainfrom
restrict-classes-to-namespace
Jul 27, 2026
Merged

Restrict classes to the gem namespace#7
saturnflyer merged 3 commits into
mainfrom
restrict-classes-to-namespace

Conversation

@saturnflyer

@saturnflyer saturnflyer commented Jul 22, 2024

Copy link
Copy Markdown
Member

Why

SOF::Parser and SOF::TimeSpan sat directly under SOF, a namespace shared with other libraries and applications. Both carry the comment "This class is not intended to be referenced directly. This is an internal implementation of Cycle behavior." An internal implementation detail of Cycle has no claim on the shared namespace.

Summary

  • SOF::ParserSOF::Cycle::Parser
  • SOF::TimeSpanSOF::Cycle::TimeSpan

SOF::Cycles stays where it is. The gem owns two names under SOF, which is a deliberate trade: the handlers each inherit from SOF::Cycle, so nesting them beneath it would put a class inside its own superclass. A second top-level constant is the cheaper price.

No aliases are left at the old names. An alias would keep holding the namespace this change exists to release.

Notes for reviewers

These are file moves, but git renders the lib/ ones as add + delete. The added nesting re-indents every line, so rename detection fails even at 40% similarity. Reviewing with whitespace ignored shows the bodies are untouched.

Low breakage risk: both classes are documented internals. Predicate-based and public-API code (SOF::Cycle.for, SOF::Cycle.legend, cycle.end_of?, SOF::Cycle::InvalidInput) is unaffected.

Review feedback

  • Freeze dormant_capable_kinds — done, in its own commit.
  • Make it a constant instead — not done, and worth recording why. Handlers register themselves through Cycle.inherited as each file in sof/cycles loads, which happens after parser.rb. At that point Cycle.cycle_handlers is empty, so a constant would capture [] permanently and dormant_capable? would return false for every E, I and W cycle. That reasoning is now a comment beside the method so it doesn't get "simplified" later.
  • The @time_span shape-variations warning — done, and extended. The warning is live on Ruby 4.0; enabling it (-W:performance) and building a control class that exceeds the limit reproduces it, and Ruby's own remediation text is "define instance variables in a consistent order, for instance by eagerly defining them all in the #initialize method." Neither class here was near the eight-variation threshold, so no warning was firing — but DatePeriod was the more interesting case than Parser: end_date and begin_date each added an ivar on first call, so the shape depended on call order. Both now declare every ivar in the constructor and take exactly one shape, and the ||= {} guards are gone.
  • Make Parser a module to avoid the nested-class antipattern — not applicable; Parser is instantiated. The related concern about nesting is why SOF::Cycles is left alone rather than folded under Cycle.

Deferred as out of scope for a namespace change: decoupling from ActiveSupport, dependency injection in .load (which must stay a plain dump/load pair for ActiveRecord serialization), casting in the constructor, and extracting a serializer object.

@bkuhlmann bkuhlmann left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some feedback on the first class only but I think some of what I'm talking about could be applied to the TimeSpan class too.

Comment thread lib/sof/cycle/parser.rb
Comment thread lib/sof/cycle/parser.rb
Comment thread lib/sof/cycle/parser.rb Outdated
Comment thread lib/sof/cycle/parser.rb
Comment thread lib/sof/cycle/parser.rb
Comment thread lib/sof/cycle/parser.rb
Comment thread lib/sof/cycle/parser.rb
Comment thread lib/sof/cycle/parser.rb
Comment thread lib/sof/cycle/parser.rb
@saturnflyer
saturnflyer force-pushed the restrict-classes-to-namespace branch from 662d16a to 3055709 Compare July 27, 2026 15:43
Parser and TimeSpan sat directly under SOF, so this gem owned three
constants in a namespace other SOF libraries and applications share.
Both are documented as internal implementations of Cycle behavior, so
neither has a claim on it.

They now nest under Cycle, where version.rb already lives. Nothing is
left behind at the old names: an alias would hold the namespace this
change exists to release.

Changed: `SOF::Parser` and `SOF::TimeSpan` are now `SOF::Cycle::Parser` and `SOF::Cycle::TimeSpan`
Version: minor
The returned array is derived state with no behavior of its own, so
nothing should be mutating it.

It stays a method rather than becoming a constant, and the reason is now
recorded next to it: handlers register through Cycle.inherited as each
file in sof/cycles loads, which happens after this file. A constant
would capture the empty set, and every dormant-capable kind would go
unrecognized.
@saturnflyer
saturnflyer force-pushed the restrict-classes-to-namespace branch 2 times, most recently from f214687 to cefc066 Compare July 27, 2026 17:36
Parser and DatePeriod each grew instance variables on first use, so their
instances took several object shapes instead of one. DatePeriod was the
worse of the two: end_date and begin_date each added an ivar when called,
so the shape depended on which ran first.

Both now declare every ivar in the constructor, which is what Ruby's own
performance warning recommends. Neither class was close to the eight
variation threshold that triggers it, so this is not a fix for an
observed warning — but the memoization guards were doing shape work for
no reason, and dropping them makes both readers shorter.

Verified with -W:performance: each class now takes exactly one shape.
@saturnflyer
saturnflyer force-pushed the restrict-classes-to-namespace branch from 6ea6c1b to 70ade10 Compare July 27, 2026 18:13
@saturnflyer
saturnflyer merged commit 44509d8 into main Jul 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants