Class: RuboCop::Cop::DevDoc::Rails::NoPerformLaterInModel
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Rails::NoPerformLaterInModel
- Defined in:
- lib/rubocop/cop/dev_doc/rails/no_perform_later_in_model.rb
Overview
Avoid perform_later/deliver_later calls inside model files.
Rationale
Enqueuing a job or mailer is execution-flow control — deciding when a side effect fires — and its home is the calling controller or job entry point (see the orchestration best-practice doc), because:
- It is prone to conflicts between
transactionandperform_later(the job may run before the transaction commits and read stale data — seeDevDoc/Rails/NoDeliverLaterInTransaction). - Flow control belongs to the Controller, not the Model. This governs
the trigger only: the mail itself stays composed in the Mailer
(parameters, templates — see the controller best-practice doc); the
controller merely calls
deliver_later. Reuse across controllers goes in a controller concern — DRY alone never justifies pushing a trigger into a model. - The exception: an enqueue inseparable from a record state change
(e.g. a throttle stamp and its send that must never be split across
a boundary) stays in the model behind an explicitly named
notify_*/send_*method, so the side effect is obvious at the call site.
The boundary test: is the enqueue bound to a state change on this record, or is ordering already enforced by data flow (the caller can only enqueue with values that exist after the domain call returns)? The latter is flow control — trigger it from the controller.
❌ (in app/models/order.rb)
def finalize
save!
OrderJob.perform_later(self)
end
✔️ State-coupled enqueue behind an explicit `notify_*` name; the
side effect is obvious at the call site, and reviewers
immediately see that this must not be called inside a
`transaction` block.
def notify_finalized
update!(finalize_notified_at: Time.current)
OrderJob.perform_later(self)
end
NOTE: This cop flags any perform_later in app/models/**/*.rb,
including the legitimate "explicitly named method" case above. The
cop is intentionally conservative — disable per-line with a rubocop
comment stating the justification when you have deliberately followed
the naming convention.
Constant Summary collapse
- MSG =
'Avoid `%<method>s` in model files — it enqueues an ActiveJob (mailer deliveries ' \ 'included), with the same transaction/stale-data hazard as `perform_later`. Call it ' \ 'from the controller, or use an explicit method name to signal the side effect.'.freeze
- RESTRICT_ON_SEND =
deliver_later is ActiveJob under the hood (MailDeliveryJob) and enqueue is the primitive perform_later wraps — omitting either would leave an everyday spelling of the same async enqueue.
%i[perform_later perform_all_later deliver_later deliver_later! enqueue].freeze
Instance Method Summary collapse
Instance Method Details
#on_send(node) ⇒ Object
74 75 76 |
# File 'lib/rubocop/cop/dev_doc/rails/no_perform_later_in_model.rb', line 74 def on_send(node) add_offense(node.loc.selector, message: format(MSG, method: node.method_name)) end |