Add detection patterns for Rails 5.1 → 5.2 upgrade#43
Add detection patterns for Rails 5.1 → 5.2 upgrade#43etagwerker wants to merge 1 commit intomainfrom
Conversation
Closes #15. Adds rails-52-patterns.yml covering 14 patterns sourced from three places: - Official Rails 5.2 upgrade guide (https://guides.rubyonrails.org/v5.2.0/upgrading_ruby_on_rails.html#upgrading-from-rails-5-1-to-rails-5-2) - FastRuby.io 5.1 → 5.2 upgrade blog post - FastRuby internal upgrade-template tickets (5319, 5321, 5322, 5323, 5803, 5805, 5806, 5807, 5808, 5809, 3358, 3395), which capture deprecations the team hits on every real 5.1 → 5.2 engagement. HIGH (1): - DIRTY_TRACKING_AFTER_SAVE: *_changed?, *_was, .changes, .changed? after save return false/nil. Whether each callsite is broken depends on before-vs-after-commit context, so the explanation flags this as a noisy regex requiring per-match audit. MEDIUM (6): - DYNAMIC_ROUTE_SEGMENTS: :controller / :action segments in routes.rb - CLASS_NAME_CONSTANT: unquoted constants passed to :class_name - ERROR_ON_IGNORED_ORDER_OR_LIMIT: config rename - HALT_CALLBACK_CHAINS_ON_RETURN_FALSE: dead config (no-op since 5.0) - CALLBACK_STRING_CONDITIONS: model-callback variant of the if:/unless: string deprecation (controller-filter variant lives in PR #38's 5.1 file) - SECRETS_USAGE: Rails.application.secrets references; migration target for credentials.yml.enc LOW (7): - BOOTSNAP_PRESENCE: Gemfile presence check; absence is the signal - EVENT_REDIS_ADAPTER: deprecated ActionCable adapter in cable.yml - ERUBIS_HANDLER: explicit Erubis references - QUOTED_ID_METHOD: legacy AR override - VERIFY_METHOD_DEPRECATION: noisy regex, audit receivers - LOCK_BANG_UNPERSISTED: noisy regex, audit per call - LEGACY_SECRET_KEY_BASE: legacy Rails.application.config.secret_* reads; absence does not prove correctness Dependencies: bootsnap. Priority calibration notes: - Dirty tracking is HIGH because the post-vs-pre-commit context determines whether each match is silently wrong; HIGH per the CLAUDE.md "silently wrong with production impact" rule. - Bootsnap is LOW (opt-in performance), not HIGH as the in-repo guide suggests. Cookie-format change has no actionable code-side detection (handled by use_authenticated_cookie_encryption flag for staged rollback). - Several items in version-guides/upgrade-5.1-to-5.2.md look misattributed against primary sources (force_ssl 301, per_form_csrf_tokens, Active Storage, CSP DSL). Worth a follow-up PR to revise that guide; out of scope here. YAML parses; all 14 regexes compile under Ruby Onigmo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new detection-patterns YAML file to support identifying Rails 5.1 → 5.2 upgrade risks via regex-based scanning.
Changes:
- Introduces
rails-52-patterns.ymlwith HIGH/MEDIUM/LOW breaking-change detection patterns for Rails 5.2. - Adds a
bootsnapdependency entry (opt-in) plus a corresponding Gemfile pattern entry.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| medium_priority: | ||
| - name: "Dynamic :controller / :action segments in routes" | ||
| pattern: "\\b(:controller|:action)\\b" | ||
| exclude: "controller:|action:|action_dispatch|action_view|action_mailer|action_cable|action_pack|action_controller" |
There was a problem hiding this comment.
DYNAMIC_ROUTE_SEGMENTS will also match old-style hash-rocket route options like :controller => 'users' / :action => 'index', which are not the deprecated dynamic segment shape. The current exclude only covers controller:/action: keyword syntax, so this is likely to create false positives. Consider extending exclude to filter :controller\s*=> and :action\s*=> as well.
| exclude: "controller:|action:|action_dispatch|action_view|action_mailer|action_cable|action_pack|action_controller" | |
| exclude: "controller:|action:|:controller\\s*=>|:action\\s*=>|action_dispatch|action_view|action_mailer|action_cable|action_pack|action_controller" |
| pattern: "\\.verify\\(" | ||
| exclude: "" | ||
| search_paths: | ||
| - "app/" | ||
| - "lib/" | ||
| explanation: "Rails 5.2 deprecates passing arguments to ActiveRecord::ConnectionAdapters::ConnectionPool#verify (it ignored them anyway). NOISY REGEX: matches every `.verify(` call site, most of which are unrelated (signed cookies, OpenSSL keys, Devise, JWT, RSpec doubles). Filter results manually for AR connection-pool receivers" | ||
| fix: "Drop the argument: connection_pool.verify(timeout) → connection_pool.verify" |
There was a problem hiding this comment.
The VERIFY_METHOD_DEPRECATION entry is specifically about passing arguments to ConnectionPool#verify, but the pattern \.verify\( matches no-arg calls too. That makes the suggested fix (“Drop the argument”) incorrect for some hits and increases noise. Consider tightening the regex to require at least one non-space character before the closing ) (or adjust the text to say “if arguments are passed”).
| pattern: "\\.verify\\(" | |
| exclude: "" | |
| search_paths: | |
| - "app/" | |
| - "lib/" | |
| explanation: "Rails 5.2 deprecates passing arguments to ActiveRecord::ConnectionAdapters::ConnectionPool#verify (it ignored them anyway). NOISY REGEX: matches every `.verify(` call site, most of which are unrelated (signed cookies, OpenSSL keys, Devise, JWT, RSpec doubles). Filter results manually for AR connection-pool receivers" | |
| fix: "Drop the argument: connection_pool.verify(timeout) → connection_pool.verify" | |
| pattern: "\\.verify\\(\\s*\\S[^)]*\\)" | |
| exclude: "" | |
| search_paths: | |
| - "app/" | |
| - "lib/" | |
| explanation: "Rails 5.2 deprecates passing arguments to ActiveRecord::ConnectionAdapters::ConnectionPool#verify (it ignored them anyway). NOISY REGEX: matches `.verify(...)` call sites with at least one argument expression, many of which are still unrelated (signed cookies, OpenSSL keys, Devise, JWT, RSpec doubles). Filter results manually for AR connection-pool receivers" | |
| fix: "If arguments are passed, drop them: connection_pool.verify(timeout) → connection_pool.verify" |
| - name: "Legacy secret_key_base configuration" | ||
| pattern: "Rails\\.application\\.config\\.secret_(token|key_base)|secret_token\\.rb" | ||
| exclude: "" | ||
| search_paths: | ||
| - "config/" | ||
| - "app/" |
There was a problem hiding this comment.
LEGACY_SECRET_KEY_BASE includes secret_token\.rb in the content regex, but this won’t detect the presence of config/initializers/secret_token.rb (grep searches file contents, not filenames). If you want a file-presence/manual-review marker, consider scoping search_paths to config/initializers/secret_token.rb and using a generic marker pattern (or drop secret_token\.rb from the regex).
| breaking_changes: | ||
| high_priority: | ||
| - name: "ActiveModel::Dirty methods after save" | ||
| pattern: "\\b\\w+_(changed\\?|was|will_change!)|\\.changes\\b|\\.changed\\?" |
There was a problem hiding this comment.
DIRTY_TRACKING_AFTER_SAVE includes *_will_change! in the regex, but the explanation/fix are about post-save dirty tracking reads (*_changed?, *_was, .changes, .changed?) returning false/nil after persistence. *_will_change! is a pre-save marker method, so including it here likely creates unrelated hits without a corresponding fix. Consider removing will_change! from this pattern (or updating the explanation/fix to explicitly justify how it relates).
| pattern: "\\b\\w+_(changed\\?|was|will_change!)|\\.changes\\b|\\.changed\\?" | |
| pattern: "\\b\\w+_(changed\\?|was)|\\.changes\\b|\\.changed\\?" |
| version: "5.2" | ||
| description: "Breaking change patterns for Rails 5.1 → 5.2 upgrade" | ||
|
|
||
| breaking_changes: | ||
| high_priority: |
There was a problem hiding this comment.
This patterns file doesn’t cover several items currently listed under “Breaking Changes” in rails-upgrade/version-guides/upgrade-5.1-to-5.2.md (e.g., Cookie expiry format change at ~lines 43-58; CSP DSL at ~134-150; force_ssl redirect status at ~154-166; per_form_csrf_tokens default at ~170-187; ActiveStorage notes at ~89-103). If the intent is to close issue #15’s “cover all breaking changes from the version guide” criterion, consider adding detection entries (even as FILE MARKER/manual-review patterns) or updating the guide so the two stay in sync.
| variable_name: "VERIFY_METHOD_DEPRECATION" | ||
|
|
||
| - name: "lock! on ActiveRecord object" | ||
| pattern: "\\.lock!(\\b|\\()" |
There was a problem hiding this comment.
The LOCK_BANG_UNPERSISTED regex \.lock!(\b|\() won’t match the common record.lock! form without parentheses because \b can’t occur immediately after ! (non-word). This will miss many real call sites. Consider switching to a lookahead/end/whitespace check (e.g., allow (, whitespace, or end-of-line) so both lock! and lock!() are detected.
| pattern: "\\.lock!(\\b|\\()" | |
| pattern: "\\.lock!(?=\\(|\\s|$)" |
|
@etagwerker was about to code review this one but it seems like copilot has already some feedback. |
Summary
Closes #15. Adds
rails-upgrade/detection-scripts/patterns/rails-52-patterns.yml. 14 patterns + 1 dependency. YAML parses, all regexes compile under Ruby Onigmo.Sources
The official upgrade guide section is unusually thin (only 2 subsections: Bootsnap and cookie expiry format). FastRuby's blog confirms it: "There were not too many deprecations between 5.1 and 5.2 compared to past releases." The bulk of actionable detection comes from the FastRuby internal tickets.
Patterns
🔴 HIGH (1)
DIRTY_TRACKING_AFTER_SAVE(FastRuby ticket 3358) —*_changed?,*_was,.changes,.changed?returnfalse/nilafter save. Whether each match is broken depends on whether the call runs before or after commit. NOISY REGEX with audit-each-match guidance in the explanation. HIGH because the failure mode is silently wrong production behavior inafter_save/after_commitcallbacks, observers, or jobs that read attribute state post-persistence.🟡 MEDIUM (6)
DYNAMIC_ROUTE_SEGMENTS(5321) —:controller/:actionroute segmentsCLASS_NAME_CONSTANT(3395) — unquoted constants inclass_name:ERROR_ON_IGNORED_ORDER_OR_LIMIT(5323) — config renameHALT_CALLBACK_CHAINS_ON_RETURN_FALSE(5807) — dead config (no-op since 5.0)CALLBACK_STRING_CONDITIONS(5808) — model-callback variant of the string-if:deprecation. The controller-filter variant lives in PR Add Rails 5.0 → 5.1 detection patterns #38's 5.1 patterns file; this entry covers the model-callback case so it's caught before final removal.SECRETS_USAGE—Rails.application.secrets.*references; migration target forcredentials.yml.enc🟢 LOW (7)
BOOTSNAP_PRESENCE— Gemfile presence check; absence is the signalEVENT_REDIS_ADAPTER(5319) — deprecated ActionCable adapterERUBIS_HANDLER(5322) — explicitErubisreferencesQUOTED_ID_METHOD(5805) — legacy AR overrideVERIFY_METHOD_DEPRECATION(5803) — NOISY REGEX (most.verify(calls are unrelated)LOCK_BANG_UNPERSISTED(5806) — NOISY REGEX (audit each call)LEGACY_SECRET_KEY_BASE(5809) — best-effort: matches legacyRails.application.config.secret_token/secret_key_basereads. Absence does not prove correctness.Dependencies
bootsnap(check: false) — new 5.2 default; opt-in performance gem.Priority calibration
*_changed?call is broken depends on the before-vs-after-commit context, which is not visible from the regex match itself.use_authenticated_cookie_encryptionflag for staged rollback). Skipped.Discrepancies with
version-guides/upgrade-5.1-to-5.2.mdWhile drafting, I noticed several items in the in-repo guide that look misattributed against primary sources. Listing them here so a future PR can revise the guide; out of scope for this patterns-only PR:
attribute_changed?after save — the behavior change actually landed in Rails 5.1, not 5.2 (it's in PR Add Rails 5.0 → 5.1 detection patterns #38's scope). At 5.2 it remains a deprecation warning. Detection still belongs here as a final reminder before later-version removal, which is whatDIRTY_TRACKING_AFTER_SAVEdoes.force_ssl301 redirect — not in either primary source for 5.1 → 5.2; predates 5.2.per_form_csrf_tokensdefault flip — only triggers viaconfig.load_defaults 5.2, which is Step 6 of the workflow (post-upgrade), not breaking-change detection in Step 5.Test plan
ruby -e "require 'yaml'; YAML.safe_load_file('rails-upgrade/detection-scripts/patterns/rails-52-patterns.yml')"succeedsDIRTY_TRACKING_AFTER_SAVE,DYNAMIC_ROUTE_SEGMENTS,CALLBACK_STRING_CONDITIONS, andSECRETS_USAGEfire on representative call sites