Run Side Effects After Commit in Rails Callbacks
Catches emails, jobs, and API calls fired from after_save or inside transactions that can roll back, and dirty checks that stop working after save.
What it flags
class Order < ApplicationRecord
belongs_to :customer
has_many :line_items, dependent: :destroy
after_create :send_confirmation_and_reserve_stock # runs before the transaction commits
after_save :notify_shipped, if: :shipped_at_changed?
private
def send_confirmation_and_reserve_stock
OrderMailer.confirmation(self).deliver_later
WarehouseClient.reserve(order_id: id)
end
def notify_shipped
ShipmentMailer.shipped(self).deliver_later
end
endclass Order < ApplicationRecord
belongs_to :customer
has_many :line_items, dependent: :destroy
after_create_commit :send_confirmation_and_reserve_stock
after_save_commit :notify_shipped, if: :saved_change_to_shipped_at?
private
def send_confirmation_and_reserve_stock
OrderMailer.confirmation(self).deliver_later
ReserveStockJob.perform_later(id)
end
def notify_shipped
ShipmentMailer.shipped(self).deliver_later
end
endWhy it matters
Callbacks such as after_create run inside the database transaction, so an email or API call can go out for a record that is then rolled back. A job enqueued before the commit can start on another worker before the row is visible, then fail with RecordNotFound or read stale data. In tests, jobs run inline or are only recorded by the test adapter, so the race between the commit and the worker never shows up.
Agent instructions
cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.
AGENTS.md
Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.
## Run Side Effects After Commit in Rails Callbacks Applies to `**/app/**/*.rb`, `**/lib/**/*.rb`. Review Active Record callbacks, models, and service objects for side effects that run before the database transaction commits. Flag changes that: - Send email (`deliver_now`, `deliver_later`), enqueue jobs, publish messages, or call external APIs from `after_save`, `after_create`, `after_update`, or `after_destroy` instead of `after_commit`, `after_create_commit`, `after_update_commit`, or `after_save_commit`. - Enqueue a job, send an email, or call an external API inside a `transaction` block. A later failure rolls back the rows but not the side effect, and a fast worker can start before the commit and not find the record. - Make HTTP requests or other slow I/O in callbacks that run inside the transaction, holding row locks and a database connection for the length of the call. - Register `after_create_commit` and `after_update_commit` with the same method name. Only the last registration takes effect; use `after_save_commit` or `after_commit on: [:create, :update]`. - Check `status_changed?`, `changes`, or `status_was` in `after_save` or `after_commit`, which already reflect the saved state; use `saved_change_to_status?`, `saved_changes`, or `status_before_last_save`. - Rely on a callback for a required side effect while the same change writes through `update_all`, `update_columns`, `insert_all`, or `upsert_all`, which skip callbacks. Do not flag: - Jobs and `deliver_later` calls enqueued through Active Job when `enqueue_after_transaction_commit` is enabled for the app or the job, which defers the enqueue until the transaction commits. - Writes to a transactional outbox table that a separate process publishes after commit. - Callbacks that only update the record's own attributes or other rows in the same database.
Checks files matching **/app/**/*.rb, **/lib/**/*.rb.
More Ruby on Rails agents
See all Ruby on Rails code review standards
Run this agent on every pull request
cubic reviews every pull request against the agents you turn on and comments on the lines that break them.