Skip to content

Commit 12abde0

Browse files
committed
Restructure setup-workflow.md into preparatory + dual-boot phases
Split the workflow into two explicit phases: **Phase 1 (preparatory)** — Steps 1-7: - Verify deprecation warnings not silenced (now a pointer to `references/deprecation-tracking.md`, removing the duplicated detection commands). - Check for existing Gemfile.next. - Add `next_rails` gem at Gemfile root (warning preserved). - Run `next_rails --init`. - Configure `DeprecationTracker` — interactive branch asks the user whether they run CI with parallel test execution. Local/single-process gets the plain config; parallel-CI gets the `node_index:` config and the `deprecations merge --delete-shards` fan-in step. Both RSpec and Minitest examples covered. - Capture the deprecation inventory (`DEPRECATION_TRACKER=save`). - Fix current-side deprecations unconditionally (Tier 1, no `NextRails.next?` conditionals yet). **Phase 2 (dual-boot)** — Steps 8-13: - Configure the Gemfile `if next?` version conditional (Rails, Ruby, core-gem examples all retained; related-gem handling preserved). - Identify and pin incompatible gems using `bundle_report compatibility --rails-version=<target>` with a fix loop: read bundler's conflict output, check RailsBump / CHANGELOG, pin inside `if next?`, re-run `BUNDLE_GEMFILE=Gemfile.next bundle install` until clean. - Install dependencies for both versions (lockfile-copy trick preserved verbatim). - Verify both sides boot and run tests. - Fix two-sided breakage using Tier 2 / Tier 3 from code-patterns.md. - Commit dual-boot setup. Also documents: - Minimum `next_rails` version requirement (>= 1.5.0, for parallel-CI-native `DeprecationTracker` and `deprecations merge`). - Explicit "CI is optional" note — local-only dual-boot is equally valid via manual two-suite invocation. Implements plan §7, §8, §10, §11, §13, §14.
1 parent 1a60055 commit 12abde0

1 file changed

Lines changed: 168 additions & 85 deletions

File tree

Lines changed: 168 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,41 @@
11
# Dual-Boot Setup Workflow
22

3-
## Prerequisites
3+
This workflow is split into two phases:
44

5-
- A Ruby application with a working `Gemfile` and `Gemfile.lock`
6-
- Know the current and target versions for the dependency you are upgrading (Rails, Ruby, or another core gem)
5+
- **Phase 1 (Preparatory):** Install `next_rails`, set up `DeprecationTracker`, and resolve current-version deprecations unconditionally (Tier 1). No Rails version hop yet. Both `Gemfile` and `Gemfile.next` resolve to identical gems.
6+
- **Phase 2 (Dual-Boot):** Pin a different Rails version in the `if next?` Gemfile block, install gems on the next side, and fix genuine two-sided breakage using Tier 2 or Tier 3 patterns.
77

8-
---
8+
Keeping deprecation resolution in Phase 1 means most deprecation fixes land as plain, unconditional migrations on the current Rails version, without any `NextRails.next?` branching. Phase 2 is then scoped to the real two-sided breakage that only dual-boot can address.
99

10-
## Step 0: Verify Deprecation Warnings Are Not Silenced
10+
## Prerequisites
1111

12-
Before setting up dual-boot, ensure deprecation warnings are visible. Silenced deprecations mean you can't track upgrade progress.
12+
- A Ruby application with a working `Gemfile` and `Gemfile.lock`.
13+
- Know the current and target versions for the dependency you are upgrading (Rails, Ruby, or another core gem).
14+
- **Minimum `next_rails` version: 1.5.0** (released 2026-04-02). The parallel-CI-aware `DeprecationTracker` configuration (`node_index:` option) and the `deprecations merge --delete-shards` CLI require v1.5.0. If your Gemfile currently pins an older version, update it before proceeding.
1315

14-
**Check for silenced deprecations:**
16+
---
1517

16-
```bash
17-
grep -rn "deprecation.*silence\|silenced.*true\|report_deprecations.*false" config/
18-
```
18+
# Phase 1: Preparatory
1919

20-
**If deprecations are silenced:**
21-
1. Change `:silence` to `:stderr` or `:log` (or `:raise` if you want strict mode)
22-
2. Remove `ActiveSupport::Deprecation.silenced = true` if found
23-
3. Remove `config.active_support.report_deprecations = false` if found (Rails 7.0+)
24-
4. Run the test suite to see current deprecation warnings
20+
Phase 1 is everything you do **before** the Rails version hop. At the end of Phase 1 you have `next_rails` installed, `Gemfile.next` in place as a symlink (both sides resolve to the same gems), `DeprecationTracker` configured, and all current-side deprecations fixed unconditionally. No `NextRails.next?` conditionals appear in application code during this phase.
2521

26-
See `references/deprecation-tracking.md` for detailed configuration guidance, including a gradual approach using custom deprecation behaviors for apps with many existing warnings.
22+
## Step 1: Verify Deprecation Warnings Are Not Silenced
2723

28-
---
24+
Before setting up dual-boot, ensure deprecation warnings are visible and tracked. See `references/deprecation-tracking.md` for the full procedure (detection of silenced deprecations, how to unsilence, and setting up `DeprecationTracker` to capture a regression-safe inventory).
2925

30-
## Step 1: Check if Dual-Boot is Already Set Up
26+
## Step 2: Check if Dual-Boot is Already Set Up
3127

3228
```bash
3329
# Check if Gemfile.next already exists
3430
ls -la Gemfile.next
3531
```
3632

37-
- If `Gemfile.next` exists → Skip to Step 4
38-
- If `Gemfile.next` does NOT exist → Continue to Step 2
33+
- If `Gemfile.next` exists, skip Steps 3 and 4 (gem install and `next_rails --init`) and proceed to Step 5.
34+
- If `Gemfile.next` does not exist, continue to Step 3.
3935

40-
**⚠️ CRITICAL:** Running `next_rails --init` when dual-boot is already set up will duplicate the `next?` method definition in the Gemfile, causing errors.
36+
**CRITICAL:** Running `next_rails --init` when dual-boot is already set up will duplicate the `next?` method definition in the Gemfile, causing errors.
4137

42-
---
43-
44-
## Step 2: Add `next_rails` Gem
38+
## Step 3: Add the `next_rails` Gem
4539

4640
Add the gem to the Gemfile **at the root level, not inside any group**:
4741

@@ -50,37 +44,132 @@ Add the gem to the Gemfile **at the root level, not inside any group**:
5044
gem 'next_rails'
5145
```
5246

53-
**⚠️ If the gem is already present, verify its location.** Grep for it:
47+
**If the gem is already present, verify its location.** Grep for it:
5448

5549
```bash
5650
grep -n "next_rails" Gemfile
5751
```
5852

59-
If it lives inside a `group :development`, `group :test`, or `group :development, :test do ... end` block, move it outside. Any `NextRails.next?` in `config/environments/production.rb` or `config/application.rb` will raise `NameError: uninitialized constant NextRails` when the app boots in production (e.g., during `assets:precompile RAILS_ENV=production`). The gem must be loadable in every environment where conditional config runs.
53+
If it lives inside a `group :development`, `group :test`, or `group :development, :test do ... end` block, move it outside. Any `NextRails.next?` in `config/environments/production.rb` or `config/application.rb` will raise `NameError: uninitialized constant NextRails` when the app boots in production (for example, during `assets:precompile RAILS_ENV=production`). The gem must be loadable in every environment where conditional config runs.
6054

6155
Then install:
6256

6357
```bash
6458
bundle install
6559
```
6660

67-
---
68-
69-
## Step 3: Initialize Dual-Boot
61+
## Step 4: Initialize Dual-Boot
7062

7163
```bash
7264
next_rails --init
7365
```
7466

7567
This creates:
76-
- `Gemfile.next` — Symlink to your Gemfile
77-
- `Gemfile.next.lock` — Lock file for the next dependency set
68+
69+
- `Gemfile.next`, a symlink to your `Gemfile`.
70+
- `Gemfile.next.lock`, the lockfile for the next dependency set.
71+
72+
At this point both sides resolve to identical gems. The infrastructure is in place, but no version hop has happened yet.
73+
74+
## Step 5: Configure `DeprecationTracker`
75+
76+
`DeprecationTracker` (shipped with `next_rails`) captures every deprecation warning emitted during a test run and saves it to a JSON shitlist. Setting it up **before** the first test run means a single run captures the full inventory, avoiding a double pass.
77+
78+
Before choosing a configuration, ask the user:
79+
80+
> Do you run tests in CI with parallel execution (multiple nodes/containers running subsets of tests)?
81+
82+
Pick the matching configuration below.
83+
84+
### No / local only / single-process CI
85+
86+
**RSpec**, add to `spec/spec_helper.rb` (or `spec/rails_helper.rb`):
87+
88+
```ruby
89+
RSpec.configure do |config|
90+
DeprecationTracker.track_rspec(
91+
config,
92+
shitlist_path: "spec/support/deprecation_warning.shitlist.json"
93+
)
94+
end
95+
```
96+
97+
**Minitest**, add to `test/test_helper.rb`:
98+
99+
```ruby
100+
DeprecationTracker.track_minitest(
101+
shitlist_path: "test/support/deprecation_warning.shitlist.json"
102+
)
103+
```
104+
105+
### Yes, parallel CI
106+
107+
Set `node_index:` to the environment variable your CI provider uses for the per-shard index (`CI_NODE_INDEX`, `CIRCLE_NODE_INDEX`, `BUILDKITE_PARALLEL_JOB`, `SEMAPHORE_JOB_INDEX`, and so on).
108+
109+
**RSpec:**
110+
111+
```ruby
112+
RSpec.configure do |config|
113+
DeprecationTracker.track_rspec(
114+
config,
115+
shitlist_path: "spec/support/deprecation_warning.shitlist.json",
116+
node_index: ENV["CI_NODE_INDEX"] # or CIRCLE_NODE_INDEX, BUILDKITE_PARALLEL_JOB, etc.
117+
)
118+
end
119+
```
120+
121+
**Minitest:**
122+
123+
```ruby
124+
DeprecationTracker.track_minitest(
125+
shitlist_path: "test/support/deprecation_warning.shitlist.json",
126+
node_index: ENV["CI_NODE_INDEX"]
127+
)
128+
```
129+
130+
With `node_index:` set, each shard writes `deprecation_warning.shitlist.node-<N>.json` instead of the canonical file. After all nodes finish, a fan-in step runs:
131+
132+
```bash
133+
deprecations merge --delete-shards
134+
```
135+
136+
This merges every `node-<N>.json` shard into the canonical shitlist and removes the shard files. See `references/deprecation-tracking.md` for the full CI pipeline (save, merge, compare phases).
137+
138+
## Step 6: Capture the Deprecation Inventory
139+
140+
Run the test suite on current Rails with `DEPRECATION_TRACKER=save` to generate the initial shitlist:
141+
142+
```bash
143+
DEPRECATION_TRACKER=save bundle exec rspec
144+
```
145+
146+
(Substitute your project's test command: `bin/rails test`, `bin/test`, `bundle exec parallel_rspec spec/`, and so on. The `DEPRECATION_TRACKER=save` environment variable is what matters.)
147+
148+
This creates `spec/support/deprecation_warning.shitlist.json`, a JSON file listing every unique deprecation warning found during the run. Review it to understand the scope of deprecations you need to address.
149+
150+
## Step 7: Fix Current-Side Deprecations Unconditionally (Tier 1)
151+
152+
Rails uses a deprecate-then-remove policy: the replacement API ships one version before the old form is removed. That means the new call almost always works on both the current and next sides, and the correct fix is Tier 1, an unconditional migration. **Do not wrap deprecation fixes in `NextRails.next?`**; that produces a dead branch you will only have to clean up later.
153+
154+
Workflow:
155+
156+
1. Pick a category of deprecation from the shitlist (for example, `update_attributes``update`).
157+
2. Replace every call site with the new API, on the current Rails version.
158+
3. Re-run `DEPRECATION_TRACKER=save bundle exec rspec`. The shitlist should shrink.
159+
4. Commit the fix.
160+
5. Repeat until the shitlist is empty or down to deprecations that genuinely cannot be resolved on current Rails.
161+
162+
See `references/code-patterns.md` "Tier 1: Unconditional Migration" for the underlying rule and examples.
78163

79164
---
80165

81-
## Step 4: Configure Gemfile with Version Conditionals
166+
# Phase 2: Dual-Boot
167+
168+
Phase 2 is the Rails version hop. You now pin a different Rails version on the next side, install gems on that side, and fix genuine two-sided breakage that cannot be resolved by Tier 1.
82169

83-
The `next_rails` gem adds a `next?` helper method to your Gemfile. Use it to specify different versions of the dependency you are upgrading.
170+
## Step 8: Configure the Gemfile with the Version Conditional
171+
172+
`next_rails` adds a `next?` helper to your Gemfile. Use it to pin a different version of the dependency you are upgrading.
84173

85174
**Do not dual-boot `config.load_defaults`.** It is post-upgrade work (rails-upgrade Step 10 / rails-load-defaults skill).
86175

@@ -132,7 +221,7 @@ else
132221
end
133222
```
134223

135-
### Handling Related Gem Version Differences
224+
### Handling related gem version differences
136225

137226
If other gems also need different versions to stay compatible:
138227

@@ -148,9 +237,37 @@ end
148237

149238
See `references/gemfile-examples.md` for more patterns.
150239

151-
---
240+
## Step 9: Identify and Pin Incompatible Gems
241+
242+
`BUNDLE_GEMFILE=Gemfile.next bundle install` often fails because the target Rails version pulls in incompatible majors of other gems (Rack, sprockets, and so on). Surface these conflicts **before** attempting the next-side install.
243+
244+
1. **Run `bundle_report compatibility` up front.** `next_rails` ships a `bundle_report` CLI that flags which gems need version pinning for the target Rails:
245+
246+
```bash
247+
bundle_report compatibility --rails-version=<target_rails_version>
248+
```
249+
250+
Example: `bundle_report compatibility --rails-version=7.1`. The output lists gems whose current pins are incompatible with the target Rails, along with known-compatible versions.
251+
252+
2. **If conflicts still arise during `BUNDLE_GEMFILE=Gemfile.next bundle install`**, read bundler's conflict output to identify the offending gem.
152253

153-
## Step 5: Install Dependencies for Both Versions
254+
3. **Look up a compatible version** via [RailsBump](https://railsbump.org/) or the gem's CHANGELOG.
255+
256+
4. **Pin the next-compatible version inside the `if next?` block:**
257+
258+
```ruby
259+
if next?
260+
gem 'rails', '~> 7.1.0'
261+
gem 'some_gem', '~> 3.0'
262+
else
263+
gem 'rails', '~> 7.0.0'
264+
gem 'some_gem', '~> 2.5'
265+
end
266+
```
267+
268+
5. **Re-run** `BUNDLE_GEMFILE=Gemfile.next bundle install` until it resolves cleanly.
269+
270+
## Step 10: Install Dependencies for Both Versions
154271

155272
```bash
156273
# Install current version dependencies
@@ -165,9 +282,9 @@ cp Gemfile.lock Gemfile.next.lock
165282
BUNDLE_GEMFILE=Gemfile.next bundle install
166283
```
167284

168-
---
285+
If `BUNDLE_GEMFILE=Gemfile.next bundle install` fails with a resolution error, loop back to Step 9.
169286

170-
## Step 6: Verify Both Dependency Sets Work
287+
## Step 11: Verify Both Dependency Sets Boot and Run Tests
171288

172289
Use the project's own test runner. Detect it first: `rspec-rails` in the Gemfile and a `spec/` directory → RSpec; `test/` and no `spec/` → Minitest; a `bin/test` wrapper → prefer it; `parallel_tests` / `turbo_tests` → use their binaries.
173290

@@ -191,66 +308,32 @@ bin/rails test
191308
BUNDLE_GEMFILE=Gemfile.next bin/rails test
192309
```
193310

194-
If the project uses `rake test` instead of `bin/rails test`, or a parallel runner (`parallel_rspec`, `parallel_test`, `turbo_tests`), substitute that binary — the `BUNDLE_GEMFILE=Gemfile.next` prefix (or the `next` CLI) works with any of them.
311+
If the project uses `rake test` instead of `bin/rails test`, or a parallel runner (`parallel_rspec`, `parallel_test`, `turbo_tests`), substitute that binary. The `BUNDLE_GEMFILE=Gemfile.next` prefix (or the `next` CLI) works with any of them.
195312

196-
---
313+
## Step 12: Fix Two-Sided Breakage (Tier 2 / Tier 3)
197314

198-
## Step 7: Set Up Deprecation Tracking with DeprecationTracker
315+
With current-side deprecations already resolved in Phase 1, anything still failing on the next side is genuine two-sided breakage: an API gone on the next side whose replacement does not backport to current, a behavior change between the two versions, or a gem-version gap where the gems pinned on each side expose different APIs.
199316

200-
The `next_rails` gem includes `DeprecationTracker`, which captures all deprecation warnings during test runs and saves them to a JSON file. Set it up now so you have a complete deprecation inventory from the start.
317+
Use `references/code-patterns.md` "Three-Tier Approach" to decide how to handle each one:
201318

202-
### Configure RSpec
319+
- **Tier 2**, a `NextRails.next?` conditional at the call site. Fits small gaps (up to about 20 call sites).
320+
- **Tier 3**, a backport or forwardport shim in a single file. Fits gaps that span an application layer (all controllers, all mailers) or 20+ call sites. Cleanup is a single-file delete.
203321

204-
Add the following to `spec/spec_helper.rb` (or `spec/rails_helper.rb`):
322+
Do not fix deprecation warnings emitted by the **next** version here. Those belong to the preparatory phase of the _following_ upgrade (current-version deprecations for that hop), not this one's dual-boot work.
205323

206-
```ruby
207-
RSpec.configure do |config|
208-
DeprecationTracker.track_rspec(
209-
config,
210-
shitlist_path: "spec/support/deprecation_warning.shitlist.json",
211-
mode: ENV.fetch("DEPRECATION_TRACKER", "save"),
212-
transform_message: -> (message) { message.gsub("#{Rails.root}/", "") }
213-
)
214-
end
215-
```
216-
217-
### Generate the Initial Deprecation Inventory
218-
219-
```bash
220-
DEPRECATION_TRACKER=save bundle exec rspec
221-
```
222-
223-
This creates `spec/support/deprecation_warning.shitlist.json` — a JSON file listing every unique deprecation warning found during the run. Review it to understand the scope of deprecations you need to address.
224-
225-
### Minitest
226-
227-
For Minitest apps, use `DeprecationTracker.track_minitest` in `test/test_helper.rb`:
228-
229-
```ruby
230-
DeprecationTracker.track_minitest(
231-
shitlist_path: "test/support/deprecation_warning.shitlist.json",
232-
mode: ENV.fetch("DEPRECATION_TRACKER", "save"),
233-
transform_message: -> (message) { message.gsub("#{Rails.root}/", "") }
234-
)
235-
```
236-
237-
Then generate the inventory with `DEPRECATION_TRACKER=save bin/rails test`. If `track_minitest` doesn't fit your setup (e.g., `minitest/parallel_fork`), fall back to the custom deprecation behavior approach in `references/deprecation-tracking.md` (Step 3).
238-
239-
See `references/deprecation-tracking.md` for the full workflow (updating the shitlist, preventing regressions, CI with parallel execution, and alternative approaches).
240-
241-
---
242-
243-
## Step 8: Commit Dual-Boot Setup
324+
## Step 13: Commit Dual-Boot Setup
244325

245326
```bash
246327
git add Gemfile Gemfile.next Gemfile.next.lock
247328
git commit -m "Add dual-boot setup for upgrade"
248329
```
249330

331+
Commit Tier 2 and Tier 3 fixes separately as you make them, ideally one per category.
332+
250333
---
251334

252335
## Next Steps
253336

254-
- Configure CI to test both versions (see `references/ci-configuration.md`)
255-
- Start fixing breaking changes using `NextRails.next?` branching
256-
- See `references/code-patterns.md` for code examples
337+
- **CI is optional.** Local-only dual-boot is fully valid. Running both suites manually (`bundle exec rspec` and `BUNDLE_GEMFILE=Gemfile.next bundle exec rspec`) is a complete workflow. `references/ci-configuration.md` is a pointer for readers who want to automate, not a mandate.
338+
- Continue fixing breaking changes using the three-tier approach in `references/code-patterns.md`.
339+
- When the upgrade is complete, remove dual-boot scaffolding via `workflows/cleanup-workflow.md` (Tier 2: drop `else` branches; Tier 3: delete shim files; Gemfile: collapse the `if next?` block).

0 commit comments

Comments
 (0)