Faster blank? checks, safer SQLite rollbacks, and more!
Hi, it's Vipul. Let's explore this week's changes in the Rails codebase.
Agents on Rails: Can a model build on its own work?
Stage 3 adds multi-step tasks where each ticket starts a fresh session, while files and notes carry forward. The benchmark now combines all three stages into one leaderboard, with tasks spanning legacy applications and new projects.
Stage 3 adds multi-step tasks where each ticket starts a fresh session, while files and notes carry forward. The benchmark now combines all three stages into one leaderboard, with tasks spanning legacy applications and new projects.
Here are some highlights from the changes merged to Rails main this week.
Speed up String#blank? for ASCII strings
String#blank? now checks ASCII-only strings by scanning their bytes instead of running a regular expression. Strings containing non-ASCII characters keep the existing regexp path and its encoding-specific whitespace handling. A follow-up removes repeated bytesize calls from the scan.
String#blank? now checks ASCII-only strings by scanning their bytes instead of running a regular expression. Strings containing non-ASCII characters keep the existing regexp path and its encoding-specific whitespace handling. A follow-up removes repeated bytesize calls from the scan.
Share read-only default attributes in Active Record
Active Record now shares a read-only set of default attributes when it can, with a per-Ractor fallback for defaults that cannot be shared. In the PR author's ruby-bench Active Record run, average time excluding warm-up iterations fell from 199 ms to 170 ms; the same PR reports 134 ms for Rails 8.1.4.
Active Record now shares a read-only set of default attributes when it can, with a per-Ractor fallback for defaults that cannot be shared. In the PR author's ruby-bench Active Record run, average time excluding warm-up iterations fell from 199 ms to 170 ms; the same PR reports 134 ms for Rails 8.1.4.
Render collections with fewer intermediate objects
Action View now renders collection partials into one shared output buffer when neither collection caching nor a per-item layout needs the items separately. This avoids allocating buffers and rendered-template wrappers just to concatenate them afterward. Collections using caching or per-item layouts keep their existing rendering path.
Action View now renders collection partials into one shared output buffer when neither collection caching nor a per-item layout needs the items separately. This avoids allocating buffers and rendered-template wrappers just to concatenate them afterward. Collections using caching or per-item layouts keep their existing rendering path.
Only consult connection pools the execution context has used
Active Record now tracks which pools an execution context has used. Checking for open transactions, releasing active connections, and clearing query caches only consult that subset. This reduces bookkeeping for applications with many configured pools; the PR notes that most applications won't see a difference.
Active Record now tracks which pools an execution context has used. Checking for open transactions, releasing active connections, and clearing query caches only consult that subset. This reduces bookkeeping for applications with many configured pools; the PR notes that most applications won't see a difference.
Preserve the original error when SQLite rolls back a transaction
SQLite can roll back a transaction itself after errors such as a full disk. Rails used to issue another rollback, masking the original failure with "cannot rollback - no transaction is active" and skipping record cleanup. It now avoids the redundant rollback, preserves the original failure as the cause of ActiveRecord::StatementInvalid, restores record state, and runs after_rollback callbacks.
SQLite can roll back a transaction itself after errors such as a full disk. Rails used to issue another rollback, masking the original failure with "cannot rollback - no transaction is active" and skipping record cleanup. It now avoids the redundant rollback, preserves the original failure as the cause of ActiveRecord::StatementInvalid, restores record state, and runs after_rollback callbacks.
Include PostgreSQL extension schemas in schema dumps
Following the earlier extension-dumping fixes, this covers relocatable extensions such as pg_trgm in schemas outside dump_schemas. Rails now emits create_schema with if_not_exists: true for schemas referenced by dumped extensions, so loading the dump works whether the schema already exists or needs to be created.
Following the earlier extension-dumping fixes, this covers relocatable extensions such as pg_trgm in schemas outside dump_schemas. Rails now emits create_schema with if_not_exists: true for schemas referenced by dumped extensions, so loading the dump works whether the schema already exists or needs to be created.
create_schema "extensions", if_not_exists: true enable_extension "extensions.pg_trgm"
Fix join order when eager loading with merged joins
Combining eager_load with joins merged from another model could generate SQL that referenced a table before joining it. Rails now puts the eager-load joins first. A related fix handles the same ordering problem with left_joins.
Combining eager_load with joins merged from another model could generate SQL that referenced a table before joining it. Rails now puts the eager-load joins first. A related fix handles the same ordering problem with left_joins.
Fix eager loading with a limit and a CTE join
Combining a CTE join, eager_load, and limit could raise ActiveRecord::ConfigurationError because Rails treated the CTE name as an association name. The limit check now recognizes CTE joins and accounts for their ability to match multiple rows.
Combining a CTE join, eager_load, and limit could raise ActiveRecord::ConfigurationError because Rails treated the CTE name as an association name. The limit check now recognizes CTE joins and accounts for their ability to match multiple rows.
Keep Active Job retry counts out of the original payload
Deserialized jobs now get their own copy of exception_executions. Retrying a job no longer changes the counters in the original job data or earlier entries in the test adapter's performed_jobs, and frozen job payloads no longer raise FrozenError on this path.
Deserialized jobs now get their own copy of exception_executions. Retrying a job no longer changes the counters in the original job data or earlier entries in the test adapter's performed_jobs, and frozen job payloads no longer raise FrozenError on this path.
Fix composite-key association queries
Empty tuple conditions and empty composite-key association conditions now return no records without issuing a query, instead of generating WHERE (). Polymorphic association queries also accept relations targeting composite-key models, and querying a composite-key association with a bare ID raises an explanatory ArgumentError.
Empty tuple conditions and empty composite-key association conditions now return no records without issuing a query, instead of generating WHERE (). Polymorphic association queries also accept relations targeting composite-key models, and querying a composite-key association with a bare ID raises an explanatory ArgumentError.
Keep connected_to state isolated between streaming and request threads
ActionController::Live shared the request's connected_to stack with its streaming thread, so leaving a block in one thread could change the shard and role seen by the other. Rails now replaces the stack with a modified copy when pushing or popping, preserving each thread's connection context.
ActionController::Live shared the request's connected_to stack with its streaming thread, so leaving a block in one thread could change the shard and role seen by the other. Rails now replaces the stack with a modified copy when pushing or popping, preserving each thread's connection context.
Detect Aube as a JavaScript package manager
Rails generators now recognize aube-lock.yaml when detecting an application's JavaScript package manager.
Rails generators now recognize aube-lock.yaml when detecting an application's JavaScript package manager.
You can view the whole list of changes here.
We had 30 contributors to the Rails codebase this past week!
We had 30 contributors to the Rails codebase this past week!
You can also read this issue on the Rails blog.
Until next time!
You’re subscribed to This Week in Rails on HEY World
23 links in this issue
- Vipulsaeloun.com
- Agents on Rails: Can a model build on its own work?rubyonrails.org
- Speed up String#blank? for ASCII stringsgithub.com
- follow-upgithub.com
- Share read-only default attributes in Active Recordgithub.com
- Render collections with fewer intermediate objectsgithub.com
- Only consult connection pools the execution context has usedgithub.com
- Preserve the original error when SQLite rolls back a transactiongithub.com
- Include PostgreSQL extension schemas in schema dumpsgithub.com
- earlier extension-dumping fixesrubyonrails.org
- Fix join order when eager loading with merged joinsgithub.com
- related fixgithub.com
- Fix eager loading with a limit and a CTE joingithub.com
- Keep Active Job retry counts out of the original payloadgithub.com
- Fix composite-key association queriesgithub.com
- Keep connected_to state isolated between streaming and request threadsgithub.com
- Detect Aube as a JavaScript package managergithub.com
- heregithub.com
- 30 contributorscontributors.rubyonrails.org
- Rails blogrubyonrails.org
- View this post onlineworld.hey.com
- This Week in Railsworld.hey.com
- HEY Worldhey.com
Sent by This Week in Rails. Links that track readers or name the subscriber were removed from this copy; its images still load from the sender's own servers. If you publish this newsletter and want an issue corrected or removed, write to us.