The callback lifecycle, in order
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_validationtoafter_save, withbefore_create/after_createnested insidebefore_save/after_save. - Updating a saved record runs the same shape, with
before_update/after_updatein place of the create pair.before_save/after_savefire either way. .destroyskips validation: onlybefore_destroyand thenafter_destroyrun.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.
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.