Include ActiveRecord::TestFixtures in the fixtures RBI - #2707
Conversation
Rails mixes `ActiveRecord::TestFixtures` into `ActiveSupport::TestCase` from the `active_record.test_fixtures` railtie initializer. Initializers do not run during gem RBI generation, so the include is missing from the gem RBI and Sorbet never sees the class methods the module contributes through `mixes_in_class_methods` — most visibly `fixtures`, so `fixtures :all` in a test case is an error under `srb tc` unless every app shims it by hand. The compiler already targets `ActiveSupport::TestCase` and knows Rails is loaded, so it is the natural place to make the include explicit.
5c44031 to
adf8bf5
Compare
KaanOzkan
left a comment
There was a problem hiding this comment.
Thanks, this is a unique change but I think it's beneficial to have it in the compiler as you said.
| # ~~~ | ||
| # | ||
| # The `include` is generated because Rails mixes `ActiveRecord::TestFixtures` into | ||
| # `ActiveSupport::TestCase` from the `active_record.test_fixtures` railtie initializer, which |
There was a problem hiding this comment.
I couldn't find that initializer, I think it's being included through this load hook https://github.com/rails/rails/blob/4130768a1b0d95da640ac792920e54988cf2d12f/railties/lib/rails/test_help.rb#L15-L24. Can we change the wording here?
There was a problem hiding this comment.
You're right, there is no such initializer — thanks for digging. It's the :active_support_test_case load hook in rails/test_help.rb, which an app requires from test_helper.rb, so it never runs during RBI generation. Reworded here, in manual/ and in the PR description.
| return if method_names.empty? | ||
|
|
||
| root.create_path(constant) do |mod| | ||
| mod.create_include("ActiveRecord::TestFixtures") |
There was a problem hiding this comment.
Is it okay for this include to be skipped by the early return above?
There was a problem hiding this comment.
No, it isn't — good catch. The select! above the early return drops fixture sets whose model constant can't be resolved, so an app that has fixture files but no matching models writes fixtures :all and still got no include.
Moved the early return above the select!, which separates the two cases it was conflating: no fixture files at all keeps generating nothing, everything else gets the include. Added a spec for fixtures with no associated model, which now yields an RBI with just the include.
…ed fixtures Two fixes from review. The include does not come from a railtie initializer — there is none. It comes from the `:active_support_test_case` load hook in `rails/test_help.rb`, which an app requires from `test_helper.rb`, so it never runs during RBI generation. Reword the comment and the manual accordingly. The early return sat below the `select!` that drops fixture sets whose model constant cannot be resolved, so an app with fixture files but no matching models wrote `fixtures :all` and still got no include. Move the return above the `select!`: apps with no fixture files at all keep generating nothing, everything else gets the include. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Motivation
Rails mixes
ActiveRecord::TestFixturesintoActiveSupport::TestCasefrom the:active_support_test_caseload hook inrails/test_help.rb. That file is required from an app'stest_helper.rb, so the hook never runs during RBI generation, the include never makes it into the gem RBI, and Sorbet doesn't see the class methods the module contributes throughmixes_in_class_methods— most visiblyfixtures:Today every Rails app using fixtures shims this by hand (a
sorbet/rbi/shimsentry, or a local DSL compiler that emits nothing but the include).ActiveRecordFixturesalready targetsActiveSupport::TestCaseand already knows Rails is loaded, so it's the natural place to make the include explicit.Implementation
One line in
decorate—mod.create_include("ActiveRecord::TestFixtures")— inside the existingcreate_path.The early return now sits above the
select!that drops fixture sets whose model constant can't be resolved, so the two cases it used to conflate are separated: an app with no fixture files at all still generates no RBI, while an app that has fixture files but no matching models gets an RBI with just the include (it writesfixtures :alltoo).Tests
spec/tapioca/dsl/compilers/active_record_fixtures_spec.rbupdated for the extra line, plus a new case for fixtures with no associated model at all; the whole file passes.bin/typecheckandrubocopare clean, andmanual/is regenerated viabin/docs.