Cover the Railtie and rake tasks with a real Rails boot - #36
Merged
Conversation
Everything Permittable::Railtie does happens during a Rails boot, and none of it had a spec: the filter_parameters wiring, its reach into ActiveRecord, or the rake tasks. Unit specs cannot see any of it, and the FakeController harness deliberately has no Rails at all — so the code that runs at every user's boot was the least tested code in the gem. The spec boots a real Rails application in a SUBPROCESS, the same approach the "without Rails loaded" specs use and for the same reason: a boot mutates global state (Rails.application is a singleton, initializers run once) and must not leak into the rest of the suite. One boot serves several examples, since booting per example would dominate the suite's runtime for no extra coverage. It asserts the promises rather than the mechanism: * exactly one filter proc appended, the app's own entries untouched * a sensitive: field declared by a controller loaded AFTER boot is redacted — the whole reason for a live registry rather than appended symbols, and something only a real boot can show * the proc reaches ActiveRecord::Base.filter_attributes, so a model's #inspect redacts too * both rake tasks load That third one is worth the emphasis. I first wrote it as an assertion about initializer ORDER, since the Railtie declares `before: "active_record.set_filter_attributes"` — and it failed: permittable's initializer sits at index 68, ActiveRecord's at 60. The promise holds anyway, because that hook is a lazy on_load which runs when ActiveRecord::Base is first referenced rather than at its position in the list. The spec now asserts the redaction, which is what was actually promised, and says why in a comment. railties joins the dev bundle for this. The spec skips rather than fails where railties is absent, so a host without Rails — and the compatibility gemfiles that omit it — are unaffected; verified by pointing the require at a nonexistent gem and watching all five examples go pending. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
VSN2015
commented
Sep 5, 2026
VSN2015
left a comment
Owner
Author
There was a problem hiding this comment.
Overall integration test coverage for Permittable::Railtie looks very solid. Isolated subprocess execution avoids global state pollution during the Rails boot probe.
Prevents leaked tmp directories when Rails boot fails partway through, since the manual mktmpdir call had no matching cleanup.
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.
The gap
Everything
Permittable::Railtiedoes happens during a Rails boot, and none of it had a spec — thefilter_parameterswiring, its reach into ActiveRecord, or the three rake tasks. Unit specs can't see any of it, and theFakeControllerharness deliberately has no Rails at all.So the code that runs at every user's boot was the least tested code in the gem.
The approach
A real Rails application booted in a subprocess — the same approach the "without Rails loaded" specs use, and for the same reason: a boot mutates global state (
Rails.applicationis a singleton, initializers run once) and must not leak into the rest of the suite. One boot serves several examples, since booting per example would dominate the runtime for no extra coverage.It asserts the promises, not the mechanism:
config.filter_parametersentries untouchedsensitive:field declared by a controller loaded after boot is redacted — the whole reason for a live registry rather than appended symbols, and something only a real boot can demonstrateActiveRecord::Base.filter_attributes, so#inspectredacts tooThe bit worth reading
I first wrote that third assertion as one about initializer order, since the Railtie declares
before: "active_record.set_filter_attributes". It failed:I thought I'd found a bug in the same family as #21/#33. I hadn't — the promise holds:
active_record.set_filter_attributesis a lazyon_loadhook: it runs whenActiveRecord::Baseis first referenced, not at its position in the initializer list, so the index says nothing about the effective order. The spec now asserts the redaction — which is what was actually promised — and a comment records why, so the next person to notice that index doesn't repeat the detour.The dev dependency
railtiesjoins the dev bundle. The spec skips rather than fails where railties is absent, so a host without Rails is unaffected — and so are #14's compatibility gemfiles, which don't carry it. Verified by pointing the require at a nonexistent gem and watching all five examples go pending rather than error.Worth adding
railtiesto those gemfiles when #14 lands, so the matrix exercises this too — happy to do that as a follow-up.Verification