Skip to content

Cover the Railtie and rake tasks with a real Rails boot - #36

Merged
VSN2015 merged 2 commits into
masterfrom
test/railtie-integration
Sep 6, 2026
Merged

Cover the Railtie and rake tasks with a real Rails boot#36
VSN2015 merged 2 commits into
masterfrom
test/railtie-integration

Conversation

@VSN2015

@VSN2015 VSN2015 commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Tests and one dev dependency. No behaviour changes.

The gap

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 three rake tasks. Unit specs can't 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 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.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 runtime for no extra coverage.

It asserts the promises, not the mechanism:

  • exactly one filter proc appended, the app's own config.filter_parameters 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 demonstrate
  • the proc reaches ActiveRecord::Base.filter_attributes, so #inspect redacts too
  • both rake tasks load

The 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:

initializer order: permittable=68 ar.set_filter_attributes=60

I thought I'd found a bug in the same family as #21/#33. I hadn't — the promise holds:

ActiveRecord::Base.filter_attributes: ["#<Proc>"]
Person#inspect: #<Person id: nil, ssn: [FILTERED], name: "Ada">

active_record.set_filter_attributes is a lazy on_load hook: it runs when ActiveRecord::Base is 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

railties joins 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 railties to those gemfiles when #14 lands, so the matrix exercises this too — happy to do that as a follow-up.

Verification

  • 204 examples, 0 failures
  • RuboCop clean, 27 files

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 VSN2015 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Overall integration test coverage for Permittable::Railtie looks very solid. Isolated subprocess execution avoids global state pollution during the Rails boot probe.

Comment thread spec/railtie_spec.rb
Prevents leaked tmp directories when Rails boot fails partway through, since the manual mktmpdir call had no matching cleanup.
@VSN2015
VSN2015 merged commit c9b4bd4 into master Sep 6, 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.

1 participant