Skip to content

Compile the APIs mounted in a Rails application's routes at boot - #3022

Open
ericproulx wants to merge 1 commit into
masterfrom
compile_mounted_apis_at_boot
Open

ericproulx wants to merge 1 commit into
masterfrom
compile_mounted_apis_at_boot

Conversation

@ericproulx

@ericproulx ericproulx commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • When config.eager_load is on, Grape's Railtie now compiles the APIs mounted in the application's routes as soon as the routes are loaded (config.after_routes_loaded), rather than on the first request each process answers. This mirrors Rails, whose RoutesReloader eager loads its own route sets under the same condition.
  • It walks the route set the way bin/rails routes does (route.app.rack_app, recursing into mounted engines). Only the APIs Rails actually serves get compiled, not the ones mounted inside another API or never mounted.
  • Without eager loading, as in development, nothing changes. The hook also runs after each reload, and the APIs stay lazy.
  • README: drops the advice to call compile! from application.rb, which raises NameError on Rails 7+ (the main autoloader is set up after the app's initializers). It now shows config.after_initialize for an API Rails doesn't see in its routes, such as one wrapped in a Rack::Builder.

What you get: no first-request compile or LOCK wait per worker, and under a preloading server (preload_app!) the compiled router is shared copy-on-write across workers.

Behaviour change

Documented in UPGRADING: compiling builds each endpoint's middleware stack, so a middleware's initialize now runs at boot. Under a preloading server, that is before the workers fork. Also, an API that fails to compile now fails the boot. A compile! call the application already makes is harmless, because compile! is memoized.

Test plan

  • spec/integration/rails/railtie_spec.rb: compiled at boot with eager loading, through an engine, and not without eager loading. Each case was mutation-checked (no hook / no eager_load gate / no engine recursion each fail a spec).
  • Rails integration specs green on 7.2, 8.0 and 8.1 across 10 seeds. The spec preloads ActionView::Base and points Rails.application at its own app, to keep a multi-app test process from leaking into the boot. It also swaps in a fresh I18n backend, so the I18n.eager_load! an eager boot performs doesn't leak into later specs (explained in the spec's comments).
  • Booted a single real app (no stubs) on 7.2 and 8.1: one router built at boot and reused by the first request with eager loading, none until the first request without.
  • bundle exec rake and the CI spec set under the Rails gemfiles pass locally.
  • CI green.

🤖 Generated with Claude Code

@ericproulx
ericproulx force-pushed the compile_mounted_apis_at_boot branch from cdccfb0 to 09e0196 Compare October 1, 2026 07:22
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@ericproulx
ericproulx force-pushed the compile_mounted_apis_at_boot branch from 09e0196 to d7daf10 Compare October 1, 2026 07:31
Comment thread lib/grape/railtie.rb
class << self
private

def compile_mounted_apis(route_set)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this live in route_set, route_set.compile!?

@ericproulx
ericproulx force-pushed the compile_mounted_apis_at_boot branch 4 times, most recently from 1dc440b to 3d4e555 Compare October 17, 2026 19:28
An API compiles on the first request each process answers unless the
application calls compile! itself, and the README's advice for Rails, to
call it from application.rb, raises NameError on Rails 7+: the main
autoloader is set up only after the application's initializers have run.

Rails builds its own routes' router at boot when config.eager_load is on.
The Railtie now does the same for the Grape APIs those routes mount,
including through a mounted engine, from config.after_routes_loaded, the
first point at which the routes are drawn. It walks the route set the way
bin/rails routes does, so only the APIs Rails serves are compiled, not the
ones mounted inside another API or never mounted at all. Without eager
loading, as in development, the hook also runs after each reload, and the
APIs stay lazy.

The README drops the application.rb advice and shows how to compile an API
Rails does not see in its routes, from config.after_initialize.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the compile_mounted_apis_at_boot branch from 3d4e555 to c90052f Compare October 17, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants