|
| 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! |
0 commit comments