Complete reference for lifecycle callbacks in CQL Active Record models.
Callbacks allow you to trigger logic at specific points in a record's lifecycle:
┌──────────────┐
│ before_save │ ─── Runs before INSERT or UPDATE
├──────────────┤
│ save │ ─── Database operation
├──────────────┤
│ after_save │ ─── Runs after INSERT or UPDATE
└──────────────┘
Runs before any save operation (create or update).
struct User
include CQL::ActiveRecord::Model(Int64)
before_save :normalize_email
private def normalize_email
@email = @email.downcase.strip
true # Must return true to continue
end
endUse cases:
- Data normalization
- Setting computed fields
- Validation that requires database access
Runs after any successful save operation.
struct Order
after_save :update_inventory
private def update_inventory
order_items.each do |item|
item.product.decrement_stock!(item.quantity)
end
true
end
endUse cases:
- Triggering side effects
- Updating related records
- Sending notifications
- Cache invalidation
Runs only before INSERT operations (new records).
struct User
before_create :set_defaults
private def set_defaults
@role ||= "member"
@created_at = Time.utc
true
end
endUse cases:
- Setting default values
- Generating tokens or UUIDs
- Recording creation metadata
Runs only after successful INSERT operations.
struct User
after_create :send_welcome_email
private def send_welcome_email
Mailer.welcome(@email).deliver
true
end
endUse cases:
- Sending welcome emails
- Creating related records
- Notifying other systems
Runs only before UPDATE operations (existing records).
struct Post
before_update :track_changes
private def track_changes
@previous_status = @status_was if status_changed?
true
end
endUse cases:
- Tracking changes
- Validating state transitions
- Incrementing version numbers
Runs only after successful UPDATE operations.
struct Post
after_update :notify_subscribers
private def notify_subscribers
if @published && !@published_was
subscribers.each do |sub|
Mailer.new_post(sub, self).deliver
end
end
true
end
endUse cases:
- Change notifications
- Audit logging
- Syncing with external systems
Runs before DELETE operations.
struct User
before_destroy :check_can_delete
private def check_can_delete
if posts.any?
errors.add(:base, "Cannot delete user with posts")
return false
end
true
end
endUse cases:
- Validation before deletion
- Checking dependencies
- Preventing deletion of protected records
Runs after successful DELETE operations.
struct User
after_destroy :cleanup_files
private def cleanup_files
FileUtils.rm_rf("/uploads/users/#{@id}")
true
end
endUse cases:
- Cleaning up files
- Removing from search indexes
- Cascade deletes not handled by database
Important: Callbacks must return true to continue the operation.
# Stops the save operation
private def validate_something
if some_condition_fails
errors.add(:field, "error message")
return false # Halts operation
end
true # Continues operation
endWhen saving a new record:
before_savebefore_create- (database INSERT)
after_createafter_save
When updating an existing record:
before_savebefore_update- (database UPDATE)
after_updateafter_save
When deleting:
before_destroy- (database DELETE)
after_destroy
struct Article
include CQL::ActiveRecord::Model(Int64)
db_context MyDB, :articles
property id : Int64?
property title : String
property slug : String?
property body : String
property published : Bool = false
property published_at : Time?
property view_count : Int32 = 0
# Before any save
before_save :generate_slug
# Only on create
before_create :set_defaults
after_create :notify_admin
# Only on update
before_update :check_publish_state
after_update :invalidate_cache
# On destroy
before_destroy :archive_content
after_destroy :cleanup
private def generate_slug
@slug = @title.downcase.gsub(/[^a-z0-9]+/, "-")
true
end
private def set_defaults
@view_count = 0
true
end
private def notify_admin
AdminMailer.new_article(self).deliver
true
end
private def check_publish_state
if @published && !published_was
@published_at = Time.utc
end
true
end
private def invalidate_cache
Cache.delete("article:#{@id}")
Cache.delete("articles:list")
true
end
private def archive_content
Archive.create!(content: to_json, type: "Article")
true
end
private def cleanup
Cache.delete("article:#{@id}")
true
end
end