ruby
37 lines · 7 steps
Building an audit trail with Rails callbacks
An after_save callback records what changed on every Product write, filtering sensitive values along the way.
Explained by
highlit
1class Product < ApplicationRecord
2 belongs_to :account
3
4 after_save :record_audit_trail
5
6 private
7
8 def record_audit_trail
9 changes = saved_changes.except("created_at", "updated_at")
10 return if changes.empty?
11
12 action = saved_change_to_id? ? :create : :update
13
14 audit_logs.create!(
15 account: account,
16 actor: Current.user,
17 action: action,
18 changed_attributes: normalize(changes)
19 )
20 end
21
22 def normalize(changes)
23 changes.transform_values do |(from, to)|
24 { from: sanitize(from), to: sanitize(to) }
25 end
26 end
27
28 def sanitize(value)
29 return "[FILTERED]" if value.present? && self.class.filter_attributes.include?(value)
30
31 value
32 end
33
34 def audit_logs
35 AuditLog.where(auditable: self)
36 end
37end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Rails' dirty-tracking methods like saved_changes let you record exactly what a write changed without extra queries.
- 2Distinguishing create from update via saved_change_to_id? avoids passing an explicit flag through the callback.
- 3Sanitizing sensitive values before persisting keeps audit logs useful without leaking secrets.
Related explainers
ruby
class User < ApplicationRecord store_accessor :preferences, :theme, :timezone,
Storing user settings in a JSON column with Rails
store_accessor
json-columns
validations
Intermediate
7 steps
ruby
require "time" require "tzinfo" module TimeConverter
Converting timestamps across time zones with TZInfo
timezones
iso8601
parsing
Intermediate
8 steps
ruby
class Subscription < ApplicationRecord belongs_to :account enum :status, {
Modeling subscription lifecycle in Rails
enums
scopes
state-management
Intermediate
8 steps
ruby
class InactiveAccountCleanup BATCH_SIZE = 500 INACTIVITY_THRESHOLD = 18.months
Batch-processing dormant users in Rails
batch-processing
activerecord
service-object
Intermediate
9 steps
ruby
class ArticleSerializer < ActiveModel::Serializer attributes :id, :title, :slug, :excerpt, :reading_time, :published_at, :url belongs_to :author, serializer: UserSerializer
Shaping API output with ActiveModel::Serializer in Rails
serialization
json api
computed attributes
Intermediate
9 steps
ruby
def collatz_lengths Enumerator.new do |y| n = 1 loop do
Building infinite sequences with lazy enumerators
lazy-evaluation
enumerators
infinite-sequences
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-an-audit-trail-with-rails-callbacks-explained-ruby-636a/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.