Context
Architecture rules explain what must hold, but a future maintainer also needs to know why the
constraint exists. ArchUnitPython already supports a terminal .because(...) rationale; Ruby
should provide the same product concept without copying Python's mutable implementation.
Goal
Allow every executable rule to carry an optional human-readable rationale that appears in failed
test output across the framework-neutral helper, RSpec, and Minitest.
Starting approach
- Add one common rationale seam to
Checkable rather than implementing presentation independently
in each rule family.
- Preserve Ruby's immutable-builder convention:
.because(reason) returns a new frozen rule and
leaves the original unchanged.
- Keep rationale out of violation values; pass it into the central
ResultFactory presentation
path so message construction remains centralized.
- Expose the normalized rationale for future generated architecture documentation.
Example:
rule = ArchUnit.project_files
.in_folder('app/controllers/**')
.should_not
.depend_on_files
.in_folder('app/persistence/**')
.because('controllers should stay thin and delegate persistence')
Acceptance criteria
.because(...) is available on every final value that implements Checkable.
- It trims surrounding whitespace and rejects non-strings or an empty result with
ArgumentError.
- Calling it returns a distinct frozen rule; the original rule remains unchanged and reusable.
- The reason appears exactly once in a failed framework-neutral, RSpec, and Minitest assertion.
- Positive and negated RSpec expectations retain their current behavior.
- The reason does not alter violations, extraction, graph-cache keys, or direct
rule.check output.
- Existing rule families have shared contract tests plus at least one end-to-end fluent example.
- README and generated API documentation explain the feature.
ArchUnitTS currently lists this as a TODO, so Ruby should follow the shared product vocabulary while
remaining idiomatic and immutable.
Context
Architecture rules explain what must hold, but a future maintainer also needs to know why the
constraint exists. ArchUnitPython already supports a terminal
.because(...)rationale; Rubyshould provide the same product concept without copying Python's mutable implementation.
Goal
Allow every executable rule to carry an optional human-readable rationale that appears in failed
test output across the framework-neutral helper, RSpec, and Minitest.
Starting approach
Checkablerather than implementing presentation independentlyin each rule family.
.because(reason)returns a new frozen rule andleaves the original unchanged.
ResultFactorypresentationpath so message construction remains centralized.
Example:
Acceptance criteria
.because(...)is available on every final value that implementsCheckable.ArgumentError.rule.checkoutput.ArchUnitTS currently lists this as a TODO, so Ruby should follow the shared product vocabulary while
remaining idiomatic and immutable.