I'm using custom middle ware for the first time to normalize error handling. I have my middleware defined in app/middleware/error_handler.rb
and using in my main application.rb
...
require_relative "../app/middleware/error_handler"
module AppName
class Application < Rails::Application
...
config.middleware.use Middleware::ErrorHandler
...
end
end
This all works fine in tests and in development when routing to localhost. But when I push to github and the action takes over I get this after it performs the migrations
An error occurred while loading spec_helper.
Failure/Error: require_relative "../config/environment"
NameError:
uninitialized constant ErrorHandler
# ./config/environment.rb:5:in `<top (required)>'
# ./spec/rails_helper.rb:6:in `require_relative'
# ./spec/rails_helper.rb:6:in `<top (required)>'
# ./spec/spec_helper.rb:3:in `<top (required)>'
No examples found.
Here is my action/workflow
name: Run RSpec
on:
push:
branches:
- "*" # Trigger on push to any branch
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16.1
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${{ secrets.ROUTE_RATER_DATABASE_PASSWORD }}
POSTGRES_DB: postgres
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
redis:
image: redis
ports:
- 6379:6379
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3.0"
- name: Install dependencies
run: |
gem install bundler
bundle install --jobs 4 --retry 3
- name: Setup DB, Run tests
env:
PGHOST: localhost
PGUSER: postgres
PGPORT: ${{ job.services.postgres.ports[5432] }}
PGPASSWORD: ${{ secrets.DATABASE_PASSWORD }}
REDIS_URL: redis://localhost:6379/1
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
RAILS_ENV: test
run: |
bin/rails db:create db:migrate db:schema:load
bundle exec rspec
app/middleware/error_handler.rb
is expected to define ErrorHandler
, but you're defining a namespaced Middleware::ErrorHandler
.
Normally, if you were to ask for ErrorHandler
it would be loaded from the first error_handler.rb
file found in any of the root directories, which is app/middleware/
in this case. File structure have to correspond to module/class names relative to a root directory:
# app/middleware/error_handler.rb
# ^^^^^^^^^^^^^
# |
class ErrorHandler # >-'
end
Rails automatically configures directories directly under app/
to be root directories. With this you can just load ErrorHandler
without requiring it:
>> ErrorHandler
=> ErrorHandler
If you were to namespace it:
# app/middleware/error_handler.rb
module Middleware
class ErrorHandler
end
end
You get an error:
>> ErrorHandler
(irb):1:in `<main>': uninitialized constant ErrorHandler (NameError)
ErrorHandler
^^^^^^^^^^^^
Which is exactly what zeitwerk
is doing by eager loading your app in production or in ci test environment:
# config/environments/test.rb
config.eager_load = ENV["CI"].present?
In development, you can double check that your app can be eager loaded:
$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
expected file app/middleware/error_handler.rb to define constant ErrorHandler, but didn't
Since you can't autoload reloadable code during boot anyway you have to require
and use Middleware::ErrorHandler
, just don't put it in autoload path.
You can also use autoload_once_paths
or autoload_lib_once
, which autoloads non-reloadable code, but you have to move config.middleware.use
into an initializer:
# config/application.rb
config.autoload_lib_once(ignore: %w(assets tasks))
# config/initializers/middleware.rb
Rails.application.config.middleware.use Middleware::ErrorHandler
# lib/middleware/error_handler.rb
module Middleware
class ErrorHandler
# ...
end
end