Skip to content

Commit c5ca13c

Browse files
committed
Fix for wrong name of file that broke zeitwerk
1 parent 58ed783 commit c5ca13c

File tree

5 files changed

+164
-3
lines changed

5 files changed

+164
-3
lines changed

app/jobs/pwb/zoho/base_job.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
require_relative '../../../services/pwb/zoho/errors'
3+
require_relative '../../../services/pwb/zoho/error'
44

55
module Pwb
66
module Zoho

app/services/pwb/zoho/client.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

3-
# Load error classes explicitly since they don't follow zeitwerk naming convention
4-
require_relative 'errors'
3+
# Load error classes explicitly
4+
require_relative 'error'
55

66
module Pwb
77
module Zoho

docs/ZEITWERK_FIX.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Zeitwerk Naming Convention Fix
2+
3+
**Date**: 2026-01-09
4+
**Issue**: Staging environment failed to boot with `uninitialized constant Pwb::Zoho::Errors`
5+
**Status**: ✅ Fixed
6+
7+
## Problem
8+
9+
The app crashed when starting in staging environment (where eager_load is enabled):
10+
11+
```
12+
uninitialized constant Pwb::Zoho::Errors (NameError)
13+
```
14+
15+
But it worked fine in development and all tests passed! ❌
16+
17+
## Root Cause
18+
19+
**Zeitwerk file naming mismatch**:
20+
21+
| File Name | Expected Constant | Actual Constant | Result |
22+
|-----------|-------------------|-----------------|--------|
23+
| `errors.rb` | `Errors` (module) | `Error` (class) | ❌ Mismatch |
24+
| `error.rb` | `Error` (class) | `Error` (class) | ✅ Match |
25+
26+
Zeitwerk's autoloader expects:
27+
- `errors.rb` → defines `Errors` module/class
28+
- `error.rb` → defines `Error` class
29+
30+
Our file was named `errors.rb` but defined `Error` class (and subclasses).
31+
32+
## Why Tests Didn't Catch This
33+
34+
1. **Test environment doesn't eager load by default**
35+
```ruby
36+
# config/environments/test.rb
37+
config.eager_load = false # Default setting
38+
```
39+
40+
2. **Development uses autoloading**
41+
- Files load on-demand (lazy loading)
42+
- If code never references `Pwb::Zoho::Errors`, file never loads
43+
- No error occurs
44+
45+
3. **Staging/Production use eager loading**
46+
- ALL files loaded at boot (for performance)
47+
- Zeitwerk validates all file/constant name mappings
48+
- Mismatch causes immediate error
49+
50+
4. **No eager load validation test**
51+
- Tests never validated that eager_load works
52+
53+
## The Fix
54+
55+
### 1. Renamed the File
56+
```bash
57+
mv app/services/pwb/zoho/errors.rb app/services/pwb/zoho/error.rb
58+
```
59+
60+
### 2. Updated require_relative References
61+
```ruby
62+
# app/jobs/pwb/zoho/base_job.rb
63+
# Before:
64+
require_relative '../../../services/pwb/zoho/errors'
65+
66+
# After:
67+
require_relative '../../../services/pwb/zoho/error'
68+
```
69+
70+
```ruby
71+
# app/services/pwb/zoho/client.rb
72+
# Before:
73+
require_relative 'errors'
74+
75+
# After:
76+
require_relative 'error'
77+
```
78+
79+
### 3. Added Eager Load Test
80+
Created `spec/zeitwerk_spec.rb`:
81+
```ruby
82+
RSpec.describe 'Zeitwerk eager loading' do
83+
it 'eager loads all constants without errors' do
84+
expect { Rails.application.eager_load! }.not_to raise_error
85+
end
86+
end
87+
```
88+
89+
This test will catch similar issues in the future!
90+
91+
## Files Changed
92+
93+
- ✅ Renamed: `app/services/pwb/zoho/errors.rb``error.rb`
94+
- ✅ Updated: `app/jobs/pwb/zoho/base_job.rb`
95+
- ✅ Updated: `app/services/pwb/zoho/client.rb`
96+
- ✅ Created: `spec/zeitwerk_spec.rb`
97+
98+
## Verification
99+
100+
```bash
101+
# Test passes
102+
bundle exec rspec spec/zeitwerk_spec.rb
103+
# 2 examples, 0 failures ✅
104+
105+
# Staging boots
106+
RAILS_ENV=staging rails runner "puts 'Success!'"
107+
# Staging boots successfully! ✅
108+
```
109+
110+
## Prevention
111+
112+
The new `spec/zeitwerk_spec.rb` test will now catch these issues:
113+
114+
- File naming mismatches (e.g., `errors.rb` with `Error` class)
115+
- Missing module definitions
116+
- Circular dependencies
117+
- Other autoloading issues that only appear in production
118+
119+
**Always run the full test suite before deploying!**
120+
121+
## Zeitwerk Naming Rules (Reference)
122+
123+
| File Path | Must Define |
124+
|-----------|-------------|
125+
| `app/models/user.rb` | `User` class |
126+
| `app/models/users.rb` | `Users` module |
127+
| `app/services/billing/error.rb` | `Billing::Error` |
128+
| `app/services/billing/errors.rb` | `Billing::Errors` |
129+
| `app/controllers/api/v1/users_controller.rb` | `Api::V1::UsersController` |
130+
131+
**Rule**: File name must match the constant name (singular/plural matters!)
132+
133+
---
134+
135+
**Status**: ✅ Fixed and Protected
136+
137+
This type of issue will now be caught in tests before it reaches staging/production!

spec/zeitwerk_spec.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'Zeitwerk eager loading' do
6+
# This test ensures all constants can be eager loaded without errors.
7+
# It catches file naming issues that only appear in production/staging
8+
# where eager_load is enabled.
9+
#
10+
# Common issues caught:
11+
# - File named `errors.rb` but defines `Error` class (should be `error.rb`)
12+
# - File named `user.rb` but defines `Users` module (should be `users.rb`)
13+
# - Missing module definitions
14+
# - Circular dependencies
15+
#
16+
it 'eager loads all constants without errors' do
17+
expect { Rails.application.eager_load! }.not_to raise_error
18+
end
19+
20+
# Verify Zeitwerk is actually configured
21+
it 'uses Zeitwerk for autoloading' do
22+
expect(Rails.autoloaders.zeitwerk_enabled?).to be true
23+
end
24+
end

0 commit comments

Comments
 (0)