For any Rails developer, the reload! command is a lifeline. Whether you’re testing a new method or debugging a complex query, the ability to refresh the application code without restarting the console session is a massive productivity boost.
However, as the framework has evolved to become more robust, a subtle discrepancy appeared: while your code was reloading, the execution state (such as the Query Cache) sometimes stayed stuck in the past. A recent update to Rails, implemented in Pull Request #56639, fixes this by making the console experience more consistent with how the framework handles actual web requests.
The Invisible Shield: The Rails Executor
In modern Rails versions, code execution is wrapped in what is known as the Rails Executor. This component manages the “lifecycle” of a task, handling essential housekeeping such as:
- Clearing and resetting the Query Cache.
- Returning database connections to the pool.
- Managing Zeitwerk’s code loading state.
Recently, the Rails console began using the Executor for every command to mirror a real web request environment. But this introduced a catch: the Executor’s state could persist longer than intended during a long-running console session.
The Problem: Stale Data After Reloading
Before this update, calling reload! refreshed your classes and constants, but it didn’t necessarily “reset” the current Executor.
This led to a frustrating edge case. Since the Executor enables the Query Cache by default, you could run a query, call reload!, and run the same query again, only to see the old results because the cache hadn’t been cleared. The code was new, but the data was stale.
The Solution: A Deeper Reset
The implementation ensures that reload! now triggers a full reset of the Rails Executor.
Now, when you run the command:
- Constants are cleared: Zeitwerk reloads your modified models, controllers, and lib files.
- The Executor is reset: This forces the Query Cache to be purged and ensures all internal “per-request” states are wiped clean.
What it looks like in practice:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Start your console
user = User.first
# (SQL: SELECT * FROM users LIMIT 1)
# Imagine a background job or another process updates that user in the DB
# ...
reload!
# Now triggers a full Executor reset via PR #56639
User.first
# (SQL: SELECT * FROM users LIMIT 1)
# Guaranteed to fetch fresh data from the database
This change is all about reducing surprises. By making reload! reset the Executor, the console now perfectly mirrors the “clean slate” that a user gets on a fresh browser request. It eliminates those “ghost bugs” where you think your code isn’t working, only to realize later that you were looking at cached database results from earlier in the session.