Restrict classes to the gem namespace - #7
Merged
Conversation
bkuhlmann
reviewed
Jul 22, 2024
bkuhlmann
left a comment
There was a problem hiding this comment.
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.
saturnflyer
force-pushed
the
restrict-classes-to-namespace
branch
from
July 27, 2026 15:43
662d16a to
3055709
Compare
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
force-pushed
the
restrict-classes-to-namespace
branch
2 times, most recently
from
July 27, 2026 17:36
f214687 to
cefc066
Compare
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
force-pushed
the
restrict-classes-to-namespace
branch
from
July 27, 2026 18:13
6ea6c1b to
70ade10
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
SOF::ParserandSOF::TimeSpansat directly underSOF, 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 ofCyclehas no claim on the shared namespace.Summary
SOF::Parser→SOF::Cycle::ParserSOF::TimeSpan→SOF::Cycle::TimeSpanSOF::Cyclesstays where it is. The gem owns two names underSOF, which is a deliberate trade: the handlers each inherit fromSOF::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
dormant_capable_kinds— done, in its own commit.Cycle.inheritedas each file insof/cyclesloads, which happens afterparser.rb. At that pointCycle.cycle_handlersis empty, so a constant would capture[]permanently anddormant_capable?would return false for everyE,IandWcycle. That reasoning is now a comment beside the method so it doesn't get "simplified" later.@time_spanshape-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 — butDatePeriodwas the more interesting case thanParser:end_dateandbegin_dateeach 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.Parsera module to avoid the nested-class antipattern — not applicable;Parseris instantiated. The related concern about nesting is whySOF::Cyclesis left alone rather than folded underCycle.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.