Part 7 · 1 chapters · ~8 min

Rails Internals

Rack and the middleware stack, the router, Zeitwerk autoloading and eager loading, Active Record's relation building and Arel, callbacks and their ordering, transactions and locking (lock!, with_lock), N+1 and strict loading, Active Support, and the Rails boot process.

8

Active Record underneath

code
Transfer.where(state: "completed").order(created_at: :desc).limit(50).to_sql    # see the SQL a relation builds
Transfer.includes(account: :owner).where(created_at: 1.day.ago..).each { ... }   # no N+1

# pessimistic locking for money (ordered to avoid deadlocks, Concurrency P2)
Account.transaction do
  a, b = Account.where(id: [from_id, to_id]).order(:id).lock.to_a        # SELECT ... FOR UPDATE ordered by id
  src, dst = a.id == from_id ? [a, b] : [b, a]
  raise Ledger::InsufficientFunds if src.balance_kobo < amount
  src.update!(balance_kobo: src.balance_kobo - amount)
  dst.update!(balance_kobo: dst.balance_kobo + amount)
end

bin/rails middleware          # the Rack stack
Rails.autoloaders.main.dirs   # Zeitwerk: file names map to constants (app/models/ledger/entry.rb → Ledger::Entry)
ACTIVE RECORD AND N+1
includes, preload and eager_load, and the tools that find the problem
transfers.each { t.account.owner }1 + 2N queriesincludes(account: :owner)Rails decidespreloadseparate querieseager_loadone LEFT JOINBullet gemwarns in developmentstrict_loadingraises on lazy loads
swipe the figure sideways, or tap expand for full screen
1/5
the problem
Accessing an association inside a loop loads it per record: 50 transfers, 100 extra queries (Python course P7 shows the Django version).
a query per record in a loopthe most common Rails slowdown