The callback lifecycle, in order

Free preview 3 min to read

Where the validations course stopped

ActiveRecord validations (complex) ended on one question: is this record allowed to be saved? What happens before, during and after the save itself was left for this course. So this course opens with a direct question: when .save runs, in what order does Rails do everything around it?

This app's models already depend on the answer. Identity::User declares before_save :downcase_email and after_create :create_default_preference. Enrollment::UserBadge, Enrollment::LessonCompletion and Partners::Partner each declare a before_validation, and Catalog::Lesson declares an after_save. No lesson has explained those lines yet, and this course starts by working out the order instead of assuming it.

A callback at every stage

class ScratchPost < ApplicationRecord
  before_validation { log("before_validation") }
  after_validation  { log("after_validation") }
  before_save       { log("before_save") }
  before_create     { log("before_create") }
  after_create      { log("after_create") }
  after_save        { log("after_save") }
  before_update     { log("before_update") }
  after_update      { log("after_update") }
  before_destroy    { log("before_destroy") }
  after_destroy     { log("after_destroy") }
  around_save   :around_save_wrap
  around_create :around_create_wrap
  around_update :around_update_wrap

  def log(name) = ($order << name)
  def around_save_wrap
    log("around_save (before yield)"); yield; log("around_save (after yield)")
  end
  def around_create_wrap
    log("around_create (before yield)"); yield; log("around_create (after yield)")
  end
  def around_update_wrap
    log("around_update (before yield)"); yield; log("around_update (after yield)")
  end
end

On a scratch table, with each callback appending its name to $order, ScratchPost.create!(title: "Hello") produced this:

1. before_validation
2. after_validation
3. before_save
4. around_save (before yield)
5. before_create
6. around_create (before yield)
7. around_create (after yield)
8. after_create
9. around_save (after yield)
10. after_save

Reading the shape

Validation runs first and is separate from saving. before_validation and after_validation both fire before anything is written. Once validation passes, before_save fires, and from there the stages nest. around_save opens, and the create-specific stages run inside it: before_create, then around_create opening, the INSERT, around_create closing, then after_create. around_save closes only after all of that, and after_save runs last.

before_save and after_save fire on every save, create or update. before_create and after_create fire only once, when the row is new. Within one stage, before_create and around_create run in the order the model declares them; this class declares before_create first.

The same record, updated

Calling .update!(title: "Hello Updated") on the same post, now saved, produced the same shape. The update pair takes the place of the create pair:

1. before_validation
2. after_validation
3. before_save
4. around_save (before yield)
5. before_update
6. around_update (before yield)
7. around_update (after yield)
8. after_update
9. around_save (after yield)
10. after_save

destroy skips validation

1. before_destroy
2. after_destroy

Calling .destroy on the post ran two stages and nothing else. There's no before_validation and no before_save. Deleting a row doesn't depend on whether its attributes are valid, so Rails doesn't run that part for it.

Key takeaways

  • On a scratch table, creating a record ran ten callback stages in a fixed order, from before_validation to after_save, with before_create/after_create nested inside before_save/after_save.
  • Updating a saved record runs the same shape, with before_update/after_update in place of the create pair. before_save/after_save fire either way.
  • .destroy skips validation: only before_destroy and then after_destroy run.
  • Identity::User (before_save, after_create), UserBadge/LessonCompletion (before_validation) and other models in this project already use these stages. The rest of the course takes them one at a time.
Lesson 1 of 7 · free

Keep going with lesson 2

before_save vs. after_create in this app

Explains why Identity::User normalizes email in before_save and creates its preference row in after_create.

A free account also gives you the quizzes, your progress and notes, and the discussion.

Type to start searching...