Zeitwerk File Naming and Acronyms
Rails 6+ uses Zeitwerk as the default autoloader. It scans the file system, camelizes file names, and expects each file to define the matching constant.
The rule is simple: user_service.rb must define UserService. But this breaks down with acronyms.
The Naming Rule
Zeitwerk calls String#camelize on the file's basename to determine the expected constant:
| File name | Camelize result | Expected constant |
|---|---|---|
user_service.rb | UserService | UserService ✓ |
api_client.rb | ApiClient | APIClient ✗ |
ai_service.rb | AiService | AIService ✗ |
When Rails can't match the constant to the file, you get:
expected file app/services/ai_service.rb to define constant AiService, but didn't
The file exists, the class exists, but the names don't match because "ai_service".camelize returns AiService, not AIService.
Option 1: Register in inflections.rb (Recommended)
The most common approach is to register the acronym globally via ActiveSupport:
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym "AI"
inflect.acronym "API"
inflect.acronym "CSV"
inflect.acronym "JSON"
inflect.acronym "PDF"
inflect.acronym "SSL"
inflect.acronym "HTML"
inflect.acronym "URL"
inflect.acronym "ID"
end
After this, "ai_service".camelize returns AIService, and Zeitwerk can find the constant. This also affects other ActiveSupport inflections globally (route helpers, humanize, etc.).
One edge case: if you register a plural acronym, you may also need to register the plural form explicitly:
inflect.acronym "API"
inflect.acronym "APIs" # if you have APIsController
Option 2: Override Only for Zeitwerk
If you don't want the acronym to affect ActiveSupport globally (e.g., it would break titleize or other helpers), override just the autoloader:
Rails.autoloaders.each do |autoloader|
autoloader.inflector.inflect(
"ai_service" => "AIService",
"api_client" => "APIClient",
"csv_importer" => "CSVImporter"
)
end
This maps the exact filename (without .rb) to the constant name. But you need a separate entry for every affected file.
Which to Use
For acronyms you use across multiple files (like AI, API, CSV), inflections.rb is cleaner. You register once and all filenames with that segment resolve correctly.
When the global inflection conflicts with something else in the app, use the Zeitwerk override instead.
Debugging Tip
If you're unsure what constant a file name resolves to, you can check directly in the Rails console:
"ai_service".camelize
# => "AiService" (before inflection)
# After registering AI:
"ai_service".camelize
# => "AIService"
Or use Zeitwerk's eager load check in the console to surface all mismatches at once:
bin/rails zeitwerk:check
This will tell you upfront if any file doesn't define the constant Zeitwerk expects.