You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Swap ParameterFilter example for ignorable → ignored_columns
Review found `ActionDispatch::Http::ParameterFilter` →
`ActiveSupport::ParameterFilter` at Rails 6.0 → 6.1 was not a genuine
two-sided break: `ActiveSupport::ParameterFilter` shipped in Rails 6.0
(verified against the `activesupport/CHANGELOG.md` at the v6.0.0 tag),
so the replacement already exists on the current side and the correct
fix is an unconditional migration — the exact anti-pattern this PR is
arguing against.
Replace with the `ignorable` gem's `ignore_columns` → native
`ignored_columns=` at Rails 4.2 → 5.0. This is genuinely two-sided:
dropping the `ignorable` gem on the 5.0 side makes `ignore_columns`
raise `NoMethodError`, and `ignored_columns=` raises `NoMethodError` on
4.2 where the setter doesn't exist yet. `NextRails.next?` is required.
Updated `SKILL.md` Pattern, `examples/basic-setup.md` (scenario shifted
to 4.2 → 5.0), `references/code-patterns.md`, `README.md` Key Principle,
`workflows/cleanup-workflow.md` Step 2, and the `CHANGELOG.md` v1.1
entry.
Also added a "When NOT to Branch: Deprecations" section in
`references/code-patterns.md` using `fixture_path` → `fixture_paths` as
the counter-example, so readers learn that deprecations should be
migrated unconditionally — not wrapped in `NextRails.next?`.
Never use `respond_to?` for version branching. It's hard to understand, hard to maintain, and obscures intent.
84
+
Never use `respond_to?`, `defined?`, or other feature-detection patterns. They are fragile, hard to clean up, and obscure intent.
85
+
86
+
Only branch when the old API genuinely breaks on the next version. A plain deprecation warning is not a reason for a conditional — if the new API works on both versions, just use it directly.
Copy file name to clipboardExpand all lines: dual-boot/CHANGELOG.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
# Changelog
2
2
3
3
## v1.1 — 22 April 2026
4
-
- Replaced `fixture_path`/`fixture_paths` and `serialize coder:` examples (both are backwards-compat deprecations) with genuine breaking changes (`ActionDispatch::Http::ParameterFilter` removal in Rails 6.1). Added explicit guidance: deprecations should be fixed by direct replacement, not wrapped in `NextRails.next?`. (#2)
4
+
- Replaced the `fixture_path`/`fixture_paths` and `serialize coder:` examples (both backwards-compat deprecations that don't require a conditional) with a genuine breaking change: `ignorable` gem's `ignore_columns` → native `ignored_columns=` at Rails 4.2 → 5.0, where each side's API raises `NoMethodError` on the other. Added explicit guidance that deprecations should be fixed by direct replacement, not wrapped in `NextRails.next?` (SKILL.md "Pattern", `references/code-patterns.md` "When NOT to Branch: Deprecations"). Broadened the "no feature detection" rule to call out `defined?` alongside `respond_to?`. Standardized on `NextRails.next?` polarity across the skill (removed the "`.current?` is also fine" clause from `CLAUDE.md`). Added invariant #7 codifying "only branch for genuine breaking changes". (#2)
5
5
6
6
## v1.0 — 04 March 2025
7
7
- Initial release as standalone skill (extracted from `rails-upgrade` skill)
Copy file name to clipboardExpand all lines: dual-boot/SKILL.md
+21-16Lines changed: 21 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -46,38 +46,43 @@ When writing code that must work with both the current and target versions, **al
46
46
47
47
### Pattern
48
48
49
-
The right time for a conditional is when the old code actually **breaks**on the next version (removed constant, removed method, incompatible signature) — not when it merely emits a deprecation warning. Deprecations should be fixed by replacing the code outright, since the new form works on both versions.
49
+
Reach for `NextRails.next?` only when the old and new APIs are genuinely **two-sided**— the old one raises on the next version and the new one doesn't exist on the current version. A plain deprecation warning is *not* a reason to branch: if the new API works on both sides, migrate the call site directly.
50
50
51
-
Example: `ActionDispatch::Http::ParameterFilter` was removed in Rails 6.1 (replaced by `ActiveSupport::ParameterFilter`). Referencing the old constant under 6.1 raises `NameError`, so a conditional is required during 6.0 → 6.1 dual-boot.
52
-
53
-
❌ **WRONG — Do NOT use feature detection (`defined?`, `respond_to?`, etc.):**
51
+
❌ **WRONG — Do NOT use feature detection (`respond_to?`, `defined?`, etc.):**
This is a genuine two-sided case (Rails 4.2 → 5.0): on 4.2 the app uses the [`ignorable`](https://github.com/nthj/ignorable) gem's `ignore_columns` class method. Rails 5.0 introduced native `ignored_columns=` with different syntax, and once the gem is dropped on the 5.0 side, `ignore_columns` raises `NoMethodError`. `ignored_columns=` raises `NoMethodError` on 4.2 where it doesn't yet exist.
73
+
74
+
Put the next-version branch on top so cleanup is mechanical: after the upgrade, keep the `if` body and drop the `else`. See `references/code-patterns.md` for more examples.
75
+
71
76
### When to Apply
72
77
73
78
Use `NextRails.next?` branching for:
74
-
-**Removed constants or methods** (e.g., `ActionDispatch::Http::ParameterFilter` removed in Rails 6.1, `update_attributes` removed in Rails 6.1)
75
-
-**Incompatible signature or return-type changes**(e.g., a method that now returns a different object type)
76
-
-**Gem version differences where the old API is gone in the new version** (not just renamed-with-alias)
77
-
-**Initializer changes that break on one side**(e.g., middleware removed from defaults, config key that raises on the other version)
79
+
-**Removed methods or constants**where the replacement only exists on the new side (e.g., a gem-provided method replaced by a native Rails method with different syntax)
80
+
-**Incompatible signature or return-type changes**where the old and new forms genuinely fail on opposite sides
81
+
-**Gem version differences** where the gem's API is different across versions pinned to each Rails version
82
+
-**Initializer changes**where a config or middleware raises on one side
78
83
-**Ruby version differences** (e.g., syntax changes, stdlib removals)
79
84
80
-
**Not a reason to branch:** deprecation warnings. If the new API works on both versions, migrate the call site directly — do not wrap it in `NextRails.next?`. See `references/code-patterns.md` "When NOT to Branch: Deprecations".
85
+
**Not a reason to branch:**plain deprecation warnings. If the new API works on both versions (e.g. `config.fixture_path=` → `config.fixture_paths=`, `update_attributes` → `update` before its removal), migrate the call site directly — do not wrap it in `NextRails.next?`. See `references/code-patterns.md` "When NOT to Branch: Deprecations".
# => Some failures — fix using NextRails.next? branching
68
69
```
69
70
70
71
### 6. Fix a Breaking Change
71
72
72
-
Rails 6.1 removed `ActionDispatch::Http::ParameterFilter` — referencing it under 6.1 raises `NameError: uninitialized constant`. The replacement `ActiveSupport::ParameterFilter` does not exist on 6.0, so a conditional is required:
73
+
Rails 5.0 introduced a native `ignored_columns=` setter, replacing the `ignorable` gem's `ignore_columns` class method. With the gem dropped on the 5.0 side, `ignore_columns` raises `NoMethodError` there; `ignored_columns=` raises `NoMethodError`on 4.2 where it doesn't yet exist. A conditional is required:
Note: a pure deprecation warning (old API still works under next) is **not** a reason to branch — replace the call site directly. Reserve `NextRails.next?` for code that would otherwise fail to load or run.
Under Rails 6.1 the old constant raises `NameError`; under Rails 6.0 the new one does not yet exist. A conditional is required during dual-boot.
15
+
An app using the [`ignorable`](https://github.com/nthj/ignorable) gem to ignore columns on Rails 4.2 hits a genuine two-sided case when upgrading to 5.0, which introduced a native `ignored_columns=` with different syntax. If the gem is dropped on the 5.0 side (since Rails now has the feature), `ignore_columns :category` raises `NoMethodError` there; `self.ignored_columns += [:category]` raises `NoMethodError` on 4.2 where the setter doesn't exist yet.
Same rule applies to `serialize :preferences, coder: JSON` vs. the older positional form, `update` vs. `update_attributes` (prior to 6.1 removal), and most Rails API evolution: if both versions accept the new form, migrate call sites directly and skip the conditional.
52
+
Same rule applies to `serialize :preferences, coder: JSON` vs. the older positional form, `update` vs. `update_attributes` (prior to the 6.1 removal), and most Rails API evolution: if both versions accept the new form, migrate call sites directly and skip the conditional.
0 commit comments