Ruby on Rails Module 1 – Rails foundations
Conventions, autoloading and Zeitwerk
Apply the naming rules that tie a Rails model to its table, controller and files, see how Zeitwerk loads code, and fix autoloading errors.
What you will learn
- Apply Rails naming conventions to models, tables, foreign keys, controllers, views and files
- Explain how Zeitwerk maps a file path to the constant it must define, and loads it on first use
- Predict what eager loading in production changes for a misnamed file
- Fix autoloading errors with bin/rails zeitwerk:check and acronym inflections
- Move code that needs app classes at boot into a to_prepare block
Before you start
What you’ll learn · Before you start
What you will learn
- Apply Rails naming conventions to models, tables, foreign keys, controllers, views and files
- Explain how Zeitwerk maps a file path to the constant it must define, and loads it on first use
- Predict what eager loading in production changes for a misnamed file
- Fix autoloading errors with bin/rails zeitwerk:check and acronym inflections
- Move code that needs app classes at boot into a to_prepare block
Before you start
On this page
Name a model Batch, and Rails already knows six more names: the table batches, the foreign key batch_id, the
controller BatchesController, the folder app/views/batches, the file app/models/batch.rb and the route helper
batches_path. Nobody writes these down, so one misspelt file name breaks a feature in a way that can stay hidden in
development and stop production at boot.
This lesson takes those conventions apart on recorded runs: the names the inflector derives, how Zeitwerk turns file paths into constants and loads them, what changes when production loads everything at boot, and how to find and fix three naming mistakes that break autoloading.
One name ties a model to its table, controller, views and files
Rails derives every related name from the model’s name with Active Support’s inflector: The part of Active Support that turns one word into another by rules: plural and singular, CamelCase and snake_case, so that Batch gives batches and batch_id.,
the same code your app can call: tableize gives the table, foreign_key the column that points at it, pluralize
the controller, underscore the file. The Rails Doctrine gives the reason in its own example: a belongs_to :account
needs no setting, because the foreign key is account_id. This program asks the inflector for five models of Kaksha:
# One model name and every name Rails derives from it: the table, the foreign key, the controller and the file.
# Active Support's inflector does the work, with the same rules Rails itself uses.
require "active_support/core_ext/string"
row = "%-11s %-12s %-14s %-22s %s"
puts format(row, "Model", "Table", "Foreign key", "Controller", "File")
%w[Batch FeePlan Enrollment Person Category].each do |model|
puts format(row, model, model.tableize, model.foreign_key, "#{model.pluralize}Controller", "app/models/#{model.underscore}.rb")
end
puts
puts "Fees::ReceiptBuilder lives in app/services/#{'Fees::ReceiptBuilder'.underscore}.rb"
puts "The views of BatchesController are in app/views/#{'BatchesController'.delete_suffix('Controller').underscore}/" Output
Model Table Foreign key Controller File Batch batches batch_id BatchesController app/models/batch.rb FeePlan fee_plans fee_plan_id FeePlansController app/models/fee_plan.rb Enrollment enrollments enrollment_id EnrollmentsController app/models/enrollment.rb Person people person_id PeopleController app/models/person.rb Category categories category_id CategoriesController app/models/category.rb Fees::ReceiptBuilder lives in app/services/fees/receipt_builder.rb The views of BatchesController are in app/views/batches/
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby conventions.rb
Read the rows. Table names are plural and snake_case (fee_plans); foreign keys are singular with _id; controllers
are plural (FeePlansController); files are singular snake_case. Person shows that the inflector knows irregular
plurals (people), and Category shows the y to ies rule. A join model such as Enrollment gets its own ordinary
names, so name join models for what they mean rather than as BatchStudent.
The last two lines show the rule for namespaces and views: Fees::ReceiptBuilder lives in
app/services/fees/receipt_builder.rb, and BatchesController renders from app/views/batches/.
Ask the inflector before you name
When a name is unusual, run bin/rails runner 'puts "Syllabus".tableize' first. If the answer surprises you, pick
another name or add an inflection before any table exists.
Checkpoint
Check: Names derived from a model's name
Zeitwerk maps each file path to one constant
Rails code has no require lines for your own classes. Zeitwerk: The code loader Rails uses: it expects each file to define the constant its path names, loads the file on first use, and in development reloads the code after a file changes., the code loader Rails sets
up, reads the autoload path: A root directory whose files Zeitwerk loads by name, such as app/models; a new app has every subdirectory of app/ except assets, javascript and views. instead: by default every subdirectory of app/ that
exists at boot except assets, javascript and views, plus lib/ through the generated
config.autoload_lib(ignore: %w[assets tasks]). Each file under one of those roots must define the one constant its
relative path names. Step through three files:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Start from a root directory. Zeitwerk knows a list of root directories; in a Rails app they are the subdirectories of app/, among them app/models and app/services. A path is read from its root, never from app/.
- The file name, camelized. Below app/models, fee_plan.rb names one constant: fee_plan camelized is FeePlan. The file must define exactly that, and nothing has to require it.
- Concerns are a root of their own. app/models/concerns is itself a root directory, so payable.rb defines Payable at the top level, not Concerns::Payable.
- Folders become namespaces. Below app/services, the folder fees becomes the module Fees and the file receipt_builder.rb the class inside it: Fees::ReceiptBuilder.
- Loaded on first use. Setting up loads nothing. The first time code names Fees::ReceiptBuilder, Zeitwerk loads its file, and that file then loads Payable and FeePlan as it uses them, as the recorded run printed.
The program below uses Zeitwerk on its own, outside Rails, with the same three roots. It prints the constant each file
must define, then uses Fees::ReceiptBuilder once and reports each file as Zeitwerk loads it:
# Zeitwerk on its own, outside Rails, with three root directories laid out as a Rails app lays them out:
# app/models, app/models/concerns and app/services. It maps each file to the constant its path names, then loads a
# file only when its constant is first used.
require "zeitwerk"
loader = Zeitwerk::Loader.new
%w[app/models app/models/concerns app/services].each { |dir| loader.push_dir(File.join(__dir__, "lite", dir)) }
loader.on_load { |cpath, _value, abspath| puts " loaded #{cpath.ljust(20)} from #{abspath.delete_prefix("#{__dir__}/")}" }
loader.setup
Dir.glob("lite/app/**/*.rb", base: __dir__).sort.each do |file|
puts "#{file.ljust(42)} defines #{loader.cpath_expected_at(File.join(__dir__, file))}"
end
puts
puts "Nothing is loaded yet: FeePlan is #{Object.autoload?(:FeePlan) ? 'waiting to autoload' : 'loaded'}"
puts "First use of Fees::ReceiptBuilder:"
puts " #{Fees::ReceiptBuilder.new.call}"
puts "payable? #{Fees::ReceiptBuilder.new.payable?}" Output
lite/app/models/concerns/payable.rb defines Payable lite/app/models/fee_plan.rb defines FeePlan lite/app/services/fees/receipt_builder.rb defines Fees::ReceiptBuilder Nothing is loaded yet: FeePlan is waiting to autoload First use of Fees::ReceiptBuilder: loaded Fees from lite/app/services/fees loaded Payable from lite/app/models/concerns/payable.rb loaded Fees::ReceiptBuilder from lite/app/services/fees/receipt_builder.rb loaded FeePlan from lite/app/models/fee_plan.rb Receipt for 1500000 paise payable? true
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby zeitwerk_demo.rb
Before the first use, FeePlan was only set to autoload: Zeitwerk had told Ruby which file holds it, and read nothing.
Using Fees::ReceiptBuilder loaded four things in order: the module Fees (made from the folder), Payable (included
by the class body), the class itself, then FeePlan when call ran. Each file was read once, at the moment it was
needed.
Key idea
Below its root directory, a file’s path names exactly one constant: folders become modules, the file name becomes the class, and Zeitwerk loads the file the first time that constant is used.
Checkpoint
Check: How Zeitwerk maps paths to constants
Development loads on first use; production loads everything at boot
Development is set for quick edits: config.eager_load = false, so Zeitwerk loads files as they are used, and after
you change a file Rails unloads the app’s classes before the next request, so they load again from the files.
Production is set to load once: config.eager_load = true in the generated production.rb, so the app
eager loading: Loading every file of the autoload paths when the app boots instead of on first use, as production does with config.eager_load = true.: it loads every file at boot, before it answers anything. The Autoloading
guide gives two reasons: every class is ready before the first request, and eager loading is friendly to
copy-on-write, so worker processes that a server forks after the boot share the loaded code instead of each loading
its own copy. Tests eager-load only on CI: the generated test.rb sets config.eager_load = ENV["CI"].present?.
That difference decides when a naming mistake shows up. Three files in ../broken break the rule, and the session
copies them into Kaksha and boots it both ways. Step through the two boots first:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- Development boots. With config.eager_load = false, booting only asks Zeitwerk to set up: for each root, such as app/models, it tells Ruby which file or folder holds each top-level constant, and it reads no file.
- The script runs. The runner prints booted. Nothing used FeePlan, so the wrong file was never opened, and nothing went wrong.
- Production boots. Production has config.eager_load = true, so after setup Rails asks Zeitwerk to load every file at once, before the app answers anything.
- The wrong file is read. Zeitwerk loads app/models/fee_plan.rb and checks what it defined. It finds Feeplan, not FeePlan.
- The boot stops. Zeitwerk raises Zeitwerk::NameError naming the file and the constant it expected, and the production boot ends there with status 1.
# Three files that break Zeitwerk's naming rule, copied into the Kaksha app (../broken holds them). Development boots
# anyway, because it loads a file only when its constant is used; production loads every file at boot.
# SECRET_KEY_BASE_DUMMY=1 stands in for the production secret, as the generated Dockerfile does, and Ruby's
# --backtrace-limit=1 keeps the error to its message and first line of the stack trace.
require_relative "session"
PRODUCTION = "RUBYOPT=--backtrace-limit=1 RAILS_ENV=production SECRET_KEY_BASE_DUMMY=1 bin/rails runner 'puts \"booted\"'"
Session.run(<<~SH, fails: [PRODUCTION, "bin/rails zeitwerk:check"])
cd ../kaksha
cp -R ../broken/app ../broken/config .
bin/rails runner 'puts "booted"'
#{PRODUCTION}
bin/rails zeitwerk:check
SH Output
$ cd ../kaksha
$ cp -R ../broken/app ../broken/config .
$ bin/rails runner 'puts "booted"'
booted
$ RUBYOPT=--backtrace-limit=1 RAILS_ENV=production SECRET_KEY_BASE_DUMMY=1 bin/rails runner 'puts "booted"'
…/lib/ruby/gems/4.0.0/gems/zeitwerk-2.8.3/lib/zeitwerk/loader/callbacks.rb:31:in 'Zeitwerk::Loader::Callbacks#on_file_autoloaded': expected file kaksha/app/models/fee_plan.rb to define constant FeePlan, but didn't (Zeitwerk::NameError)
raise Zeitwerk::NameError.new(msg, cref.cname)
^^^^^
from …/lib/ruby/gems/4.0.0/gems/zeitwerk-2.8.3/lib/zeitwerk/core_ext/kernel.rb:27:in 'Kernel#require'
... 53 levels...
[exit status 1]
$ bin/rails zeitwerk:check
expected file app/models/fee_plan.rb to define constant FeePlan, but didn't
Hold on, I am eager loading the application.
[exit status 1]
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby broken_session.rb
Development printed booted: nothing used FeePlan, so the wrong file was never opened. Production stopped at boot
with Zeitwerk::NameError: expected file kaksha/app/models/fee_plan.rb to define constant FeePlan, but didn't. A mistake
that development hides until someone opens the right page, production refuses outright.
Trusting development to catch naming errors
A misnamed file that no page in development touched still boots there. Run bin/rails zeitwerk:check, which eager
loads like production, before every deploy and in CI.
Checkpoint
Check: Loading on use and eager loading
zeitwerk:check finds files that define the wrong constant
bin/rails zeitwerk:check loads the whole app the way production would and reports the first file that does not
define its constant; the session above shows it stopping at fee_plan.rb. Its error sits above its first line, “Hold
on, I am eager loading the application.”, because the error goes to standard error, which Ruby writes at once, while
that line waited in the output buffer until the task ended. To see every broken file at once, this program loads each
file of ../broken on its own with Zeitwerk:
# The three files of ../broken, each loaded on its own by Zeitwerk (zeitwerk:check stops at the first problem, this
# reports every one). Each error names the constant the file's path promises.
require "zeitwerk"
loader = Zeitwerk::Loader.new
%w[app/models app/services].each { |dir| loader.push_dir(File.join(__dir__, "broken", dir)) }
loader.setup
Dir.glob("broken/app/**/*.rb", base: __dir__).sort.each do |file|
loader.load_file(File.join(__dir__, file))
puts "#{file}: ok"
rescue Zeitwerk::NameError => e
puts "#{file}:"
puts " #{e.message.sub("#{__dir__}/", '')}"
end Output
broken/app/models/fee_plan.rb: expected file broken/app/models/fee_plan.rb to define constant FeePlan, but didn't broken/app/models/pdf_receipt.rb: expected file broken/app/models/pdf_receipt.rb to define constant PdfReceipt, but didn't broken/app/services/fees/receipt_builder.rb: expected file broken/app/services/fees/receipt_builder.rb to define constant Fees::ReceiptBuilder, but didn't
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby broken_names.rb
Each error names the constant the path promises. fee_plan.rb defined Feeplan instead of FeePlan: a spelling
fix. receipt_builder.rb sits in the folder fees, so it must define Fees::ReceiptBuilder: a missing namespace.
pdf_receipt.rb defined PDFReceipt while the path promises PdfReceipt: an acronym, which the next section fixes
without renaming anything. Here are the three fixes and the check that follows them:
# The same three files fixed (../fixed), what changed, and Zeitwerk's check and a production boot afterwards.
require_relative "session"
Session.run(<<~SH, fails: ["diff -r ../broken ../fixed"])
diff -r ../broken ../fixed
cd ../kaksha
cp -R ../fixed/app ../fixed/config .
bin/rails zeitwerk:check
RAILS_ENV=production SECRET_KEY_BASE_DUMMY=1 bin/rails runner 'puts Fees::ReceiptBuilder.new.call'
SH Output
$ diff -r ../broken ../fixed
diff -r ../broken/app/models/fee_plan.rb ../fixed/app/models/fee_plan.rb
1c1
< class Feeplan
---
> class FeePlan
diff -r ../broken/app/services/fees/receipt_builder.rb ../fixed/app/services/fees/receipt_builder.rb
1,2c1,4
< class ReceiptBuilder
< def call = "Receipt for #{FeePlan.default_paise} paise"
---
> module Fees
> class ReceiptBuilder
> def call = "Receipt for #{FeePlan.default_paise} paise"
> end
diff -r ../broken/config/initializers/inflections.rb ../fixed/config/initializers/inflections.rb
16a17,20
>
> ActiveSupport::Inflector.inflections(:en) do |inflect|
> inflect.acronym "PDF"
> end
[exit status 1]
$ cd ../kaksha
$ cp -R ../fixed/app ../fixed/config .
$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!
$ RAILS_ENV=production SECRET_KEY_BASE_DUMMY=1 bin/rails runner 'puts Fees::ReceiptBuilder.new.call'
Using vips to process variants requires the libvips library. Please install libvips using the instructions on the libvips website.
Receipt for 1500000 paise
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby fixed_session.rb
After the fixes the check printed “All is good!” and production booted far enough to run the receipt builder. The
libvips line has nothing to do with autoloading. Active Storage needs the libvips image library for image variants,
and Rails does not install it, so at the end of a boot Active Storage logs that warning when it cannot load the
library, as on this machine; the generated production.rb sends the log to standard output, so the production boot
shows it. Put the check in CI beside the tests: it ends with status 1 on a broken name, as the first session showed,
so the build stops before the deploy does.
Checkpoint
Check: Finding naming errors with zeitwerk:check
Acronyms need an inflection
Zeitwerk in Rails uses the inflector to camelize file names, and the inflector does not know your acronyms:
pdf_receipt becomes PdfReceipt. You can write the class as PdfReceipt, or tell the inflector that PDF is an
acronym, in config/initializers/inflections.rb, as the Autoloading guide does for HTML and SSL:
# How Rails turns a file name into a class name (camelize) and back (underscore), before and after an acronym
# inflection like the one config/initializers/inflections.rb can hold.
require "active_support/core_ext/string"
puts "Before: pdf_receipt -> #{'pdf_receipt'.camelize}, PDFReceipt -> #{'PDFReceipt'.underscore}"
ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym "PDF" }
puts "After: pdf_receipt -> #{'pdf_receipt'.camelize}, PDFReceipt -> #{'PDFReceipt'.underscore}"
puts " pdf_receipts_controller -> #{'pdf_receipts_controller'.camelize}" Output
Before: pdf_receipt -> PdfReceipt, PDFReceipt -> pdf_receipt
After: pdf_receipt -> PDFReceipt, PDFReceipt -> pdf_receipt
pdf_receipts_controller -> PDFReceiptsController
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby inflection_demo.rb
After inflect.acronym "PDF", pdf_receipt camelizes to PDFReceipt, and so does every name camelized from the word:
the controller that resources :pdf_receipts looks for becomes PDFReceiptsController. underscore still gives
pdf_receipt, so file names, the table pdf_receipts and route helpers such as pdf_receipts_path stay as they
were. This is the third fix of the session: one line in the initializer, no file renamed. The generated initializer
shows the same form in a comment, with RESTful as its example, and switches nothing on, so each acronym your names
use is yours to add.
Adding an acronym after code uses the other spelling
An acronym changes every name camelized from the word, in the whole app. Once PDF is an acronym, a file
pdf_invoice.rb that still defines PdfInvoice breaks, because its path now promises PDFInvoice. Run
bin/rails zeitwerk:check right after you add an acronym, and fix what it reports until it prints “All is good!”.
Checkpoint
Check: Acronym inflections
Code that runs at boot must wait for the app’s classes
Files in config/initializers/ run while the app boots, before Zeitwerk manages app/. The Autoloading guide is
explicit: initializers cannot refer to reloadable constants. Kaksha prints its receipts on A4 paper, so an initializer
sets that on PDFReceipt, a class of app/models. The session tries it, then moves the line into to_prepare:
# An initializer that configures an app class while the app boots, then the same line inside to_prepare
# (../initializers holds both versions; ../fixed holds the corrected app files). Ruby's --backtrace-limit=1 keeps the
# error to its message and first line of the stack trace. The last command prints the paper size, sets another one by
# hand, reloads the app's code the way reload! does in a console, and prints the size again.
require_relative "session"
EARLY = "RUBYOPT=--backtrace-limit=1 bin/rails runner 'puts PDFReceipt.paper_size'"
RELOAD = %q(bin/rails runner 'puts PDFReceipt.paper_size; PDFReceipt.paper_size = "A5"; Rails.application.reloader.reload!; puts PDFReceipt.paper_size')
Session.run(<<~SH, fails: [EARLY])
cd ../kaksha
cp -R ../fixed/app ../fixed/config .
cp ../initializers/early.rb config/initializers/receipts.rb
#{EARLY}
cp ../initializers/prepared.rb config/initializers/receipts.rb
#{RELOAD}
SH Output
$ cd ../kaksha $ cp -R ../fixed/app ../fixed/config . $ cp ../initializers/early.rb config/initializers/receipts.rb $ RUBYOPT=--backtrace-limit=1 bin/rails runner 'puts PDFReceipt.paper_size' kaksha/config/initializers/receipts.rb:2:in '<main>': uninitialized constant PDFReceipt (NameError) PDFReceipt.paper_size = "A4" ^^^^^^^^^^ from …/lib/ruby/gems/4.0.0/gems/railties-8.1.4/lib/rails/engine.rb:692:in 'Kernel#load' ... 50 levels... [exit status 1] $ cp ../initializers/prepared.rb config/initializers/receipts.rb $ bin/rails runner 'puts PDFReceipt.paper_size; PDFReceipt.paper_size = "A5"; Rails.application.reloader.reload!; puts PDFReceipt.paper_size' A4 A4
Recorded with ruby 4.0.7 (revision 229531a6cf) +PRISM [arm64-darwin23] on macOS 26 arm64. To run it yourself: ruby initializer_session.rb
config/initializers/receipts.rb named PDFReceipt and the boot failed with uninitialized constant PDFReceipt (NameError). Wrapped in Rails.application.config.to_prepare do … end, the same line runs later in the boot, once
Zeitwerk manages app/, so the next run printed A4. That run then set A5 by hand and reloaded the code the way
reload! does in a console, and the size was A4 again. A reload replaces the app’s classes with new ones loaded from their files, and
an initializer never runs again after boot, so nothing it set on the old PDFReceipt would reach the new one.
to_prepare runs at boot and again after every reload, so each new class gets its paper size. The guide warns that
the block may run twice, so it should give the same result each time, as setting a value does.
Boot code comes in three kinds, and each has its own place:
| Code that must run at boot | Where it belongs |
|---|---|
Settings that need no app class (config.x.*, a gem’s options) |
An initializer, as it is |
Code that configures a reloadable class from app/ |
Rails.application.config.to_prepare |
| Code that extends a Rails framework class | An ActiveSupport.on_load hook, such as on_load(:action_controller) |
Checkpoint
Check: App classes in code that runs at boot
Practise: name it, load it, fix it
Most questions use this lesson’s runs: the inflector’s names, Zeitwerk’s map, the two boots, the three broken files and the receipt initializer; a few ask you to carry an idea to a new case. Each choice explains itself:
Practice that adapts to you
Practise this lesson
12 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up: why does a Rails app need no require lines for its own models and controllers?
Because Zeitwerk loads them. At boot Rails gives Zeitwerk the autoload paths, every subdirectory of app/ such as
app/models and app/controllers, and Zeitwerk expects each file to define the constant its path names below its root:
app/models/fee_plan.rb defines FeePlan, app/services/fees/receipt_builder.rb defines Fees::ReceiptBuilder. In
development it loads a file the first time its constant is used and reloads the code after a file changes; in
production it loads every file at boot. A file that defines a different constant raises Zeitwerk::NameError, which
bin/rails zeitwerk:check reports before a deploy.
Two questions of this module’s bank build on this lesson: a deploy that fails with a Zeitwerk::NameError that nobody
saw in development, and an initializer that reads a value from a class of app/.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A deploy fails at boot with Zeitwerk::NameError for app/models/sms_sender.rb, yet every page worked in development and the tests passed on laptops. Explain why, fix it, and stop it from happening again.
-
Mid to senior
An initializer stores FeePlan.default_paise in a setting so that every request can read it. It fails at boot in Rails 8.1. Why does Rails refuse it, what is the right place for that code, and what would go wrong if Rails allowed it?
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
From the model name Batch, which other names does Rails work out?
The table batches, the foreign key batch_id, the controller BatchesController and the views folder batches, all through the inflector and with no configuration.
-
Which constant must the file app/services/fees/receipt_builder.rb define?
Fees::ReceiptBuilder. Below the autoload root, each folder becomes a module and the file name becomes the class.
-
Why can a misnamed file work in development and still stop a production boot?
Development loads a constant only when code first uses it, so an unused file is never read. Production eager-loads every file at boot, where the wrong constant raises Zeitwerk::NameError.
-
What does bin/rails zeitwerk:check report?
It loads the app the way production does and names the first file that defines a different constant from the one its path names, before a deploy finds it.
-
A class is named PDFReceipt. What does Zeitwerk need before it can load it?
An inflect.acronym entry for PDF, or the class renamed to the spelling the inflector already gives, PdfReceipt.
-
Where does boot code go that configures your app's own classes?
Inside a to_prepare block. Initializers run once, before those classes can autoload; to_prepare runs at boot and again after each reload.
Key takeaways
- One model name gives every related name through the inflector: table, foreign key, controller, views folder and
file, so
Batchmeansbatches,batch_idandBatchesControllerwithout configuration. - Zeitwerk expects each file under an autoload path to define the constant its relative path names, folders as modules, and loads it the first time the constant is used.
- Development loads on first use and reloads changes, while production eager-loads everything at boot, so a misnamed file can boot in development and stop production.
bin/rails zeitwerk:checkloads the app like production and names the first broken file; a class name with an acronym needsinflect.acronym, or the spelling the inflector already gives, such asPdfReceipt.- Initializers run once, before the app’s classes can autoload; code that configures those classes goes in a
to_prepareblock, which runs at boot and again after every reload.
Terms in this lesson
- autoload path
- A root directory whose files Zeitwerk loads by name, such as app/models; a new app has every subdirectory of app/ except assets, javascript and views.
- eager loading
- Loading every file of the autoload paths when the app boots instead of on first use, as production does with config.eager_load = true.
- inflector
- The part of Active Support that turns one word into another by rules: plural and singular, CamelCase and snake_case, so that Batch gives batches and batch_id.
- Zeitwerk
- The code loader Rails uses: it expects each file to define the constant its path names, loads the file on first use, and in development reloads the code after a file changes.
References
- Autoloading and Reloading Constants (Ruby on Rails)
- Configuring Rails Applications (Ruby on Rails)
- Zeitwerk (Xavier Noria (Zeitwerk project))
- The Rails Doctrine (Ruby on Rails)
- Active Support Core Extensions (Ruby on Rails)
- Active Storage Overview (Ruby on Rails)
Report a problem with this lesson
Kept only in this browser. Your Learn progress