Class: RuboCop::Cop::DevDoc::Rails::AvoidLifecycleMethodOverride
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Rails::AvoidLifecycleMethodOverride
- Defined in:
- lib/rubocop/cop/dev_doc/rails/avoid_lifecycle_method_override.rb
Overview
Avoid overriding Rails validation/persistence lifecycle methods in model files (‘run_validations!`, `valid?`, etc.).
## Rationale ‘DevDoc/Rails/AvoidRailsCallbacks` bans the callback DSL so lifecycle behaviour stays visible at explicit call sites. Overriding a lifecycle method is a loophole — it reproduces the same hidden control flow without tripping that cop:
# Flagged by AvoidRailsCallbacks:
before_validation :do_something
# NOT flagged by it — but functionally identical, and worse:
def run_validations!
do_something
super
end
‘def run_validations!; do_something; super; end` is a `before_validation :do_something` in disguise, and it’s strictly worse:
-
**Silent on typo.** Misspell the method name (drop the ‘!`, or `run_validatons!`) and it’s just a never-called method — no load-time error, no override in effect, behaviour silently reverts to stock Rails. The callback DSL fails loudly (‘NoMethodError`) on a mistyped macro or symbol, caught by the first test that validates.
-
**More obscure.** Overriding an internal Rails method hides the lifecycle hook even more than the DSL it’s avoiding.
This cop pairs with (it does not replace) ‘AvoidRailsCallbacks`: the two cover the two ways to inject lifecycle behaviour — the DSL, and the override.
## What to do instead
-
Prefer an explicit method at the call site (e.g. ‘save_with_*`) that makes the behaviour visible, or a plain `validate :some_check` where a validation-time check is all you need.
-
If an override is genuinely required (e.g. a model whose validators read access-gated attributes and must wrap the entire validation run — something ‘validate` can’t express), disable this cop inline with a written justification:
def run_validations! # rubocop:disable DevDoc/Rails/AvoidLifecycleMethodOverride # Reason: <explanation> with_access { super } end
## Not airtight A determined dodge can still ‘prepend` a module, `alias_method`, or use `method_missing`. This cop raises the bar and makes the documented path (inline disable + justification) the easy one; it does not seal every hole.
## Configuration The flagged methods are configurable via ‘Methods`. The default is the validation-lifecycle set — the cleanest 1:1 substitute for the validation callbacks `AvoidRailsCallbacks` bans, with the least false-positive noise. Projects that also want to guard persistence verbs can add `save`/`save!`/`create`/`update`/`destroy` etc.
Constant Summary collapse
- MSG =
'Avoid overriding the Rails lifecycle method `%<method>s` — it hides ' \ 'callback-like control flow and silently no-ops if mistyped. Prefer an ' \ 'explicit method or a `validate` declaration; if an override is genuinely ' \ 'required, disable this cop inline with a written justification.'.freeze
- DEFAULT_METHODS =
%w[run_validations! valid? invalid? perform_validations].freeze
Instance Method Summary collapse
Instance Method Details
#on_def(node) ⇒ Object
92 93 94 95 96 |
# File 'lib/rubocop/cop/dev_doc/rails/avoid_lifecycle_method_override.rb', line 92 def on_def(node) return unless flagged_methods.include?(node.method_name) add_offense(node.loc.name, message: format(MSG, method: node.method_name)) end |