Use after_commit instead of active record callbacks to avoid unexpected errors

AR callbacks after_create/after_update/after_destroy to generate background job etc. are useful, but these callbacks are still wrapped in the database transaction, and you may probably get unexpected errors on production servers.

Before

It’s common to generate a background job to send emails like

  class Comment < ActiveRecord::Base
    after_create :asyns_send_notification
    def async_send_notification
      Notifier.send_new_comment_notification(self).deliver
    end
   handle_asynchronously :async_send_notification
  end

  # It looks beautiful that comment record passed to the delayed job and then delayed job will fetch this
  #comment and then post on which this comment has been made and a notification will
  #send via email to the author of that post. Right?

You won’t see any issue in development, as local DB can commit fast. But in production server, db traffic might be massive, worker probably finish faster than transaction commit. e.g

  1. primary process
  2. worker process
  3. BEGIN
  INSERT comment in comments table
  # return id 10 for newly-created notification
  SELECT * FROM notifications WHERE id = 10
  COMMIT

In this case, the worker process query the newly-created notification before the main process commits the transaction, it will raise NotFoundError, because transaction in worker process can’t read free information from the transaction in the main process.

Refactor

So we should tell ActiveRecord to generate notification worker after notification insertion transaction committed.

  class Comment < ActiveRecord::Base
    after_commit :asyns_send_notification,:on => :create  # This is the main point of the whole thing.

    def async_send_notification
      Notifier.send_new_comment_notification(self).deliver
    end
    handle_asynchronously :async_send_notification
  end

Now the transactions order becomes

  1. main process
  2. worker process
  3. BEGIN
  INSERT comment into comments_table
  return id 10 for newly-created notification
  COMMIT
  SELECT * FROM notifications WHERE id = 10

Worker process won’t receive NotFoundErrors anymore. :)