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
!, orrun_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 plainvalidate :some_checkwhere 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
validatecan'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 |