ruby

What's New in Rails 7.1

Emmanuel Hayford

Emmanuel Hayford on

What's New in Rails 7.1

Rails 7 was a welcome release that brought a lot of significant features and changes. On the backend, Rails 7 introduced asynchronous query loading and Zeitwerk for autoloading. The frontend saw Hotwire becoming the default solution for new Rails apps.

Rails 7.1 will add to these notable features. In this post, we'll discuss some noteworthy additions that are likely to be shipped.

A New API for Async Queries in Rails

Building on an earlier feature from Rails 7, Rails 7.1 will make it possible to run some queries asynchronously. Rails 7 introduced ActiveRecord::Relation#load_async, which schedules a query in a background thread pool, allowing us to do stuff like Post.where(published: true).load_async.

In Rails 7.1, we'll be able to run a lot more queries in background threads. Aggregate methods will run concurrently. Assuming you run two or more independent queries on a job or controller, query results may return quicker if your application is set up accordingly.

For this to work as intended, there are two configuration options worth paying attention to:

Ruby
config.active_record.async_query_executor config.active_record.global_executor_concurrency

Among the methods you can run asynchrously in Rails 7.1 are async_sum, async_pluck, and async_count_by_sql.

Resetting Singular Associations

Rails 7.1 allows resetting the cache on singular associations. Currently, you can only clear the cache of has_many associations with a class like:

Ruby
class Teacher < ActiveRecord::Base has_many :students has_one :classroom end teacher = Teacher.first

We can only do teacher.students.reset to clear the caches of the results returned by teacher.students. Subsequent requests need to hit the database again for a fresh results set in case some data goes stale.

With Rails 7.1, we'll get the reset method on a has_one association. Using our example class above, Rails 7.1 will allow us to do teacher.classroom.reset_teacher to clear the cache for the associations between teacher and classroom.

Disabling Methods Generated By ActiveRecord#enum

ActiveRecord#enum generates a bunch of methods if you create an enum Rails. Rails 7.1 will provide an option to opt out of these generated methods. Here's a simple example:

Ruby
class Payment < ActiveRecord::Base enum :status, %i[succeeded failed], instance_methods: false end

Rails won't generate auxiliary methods with instance_methods: false. Currently, we expect to have methods like:

Ruby
payment = Payment.first payment.succeeded? payment.failed? payment.succeeded! payment.failed!

Support for Common Table Expressions

Rails 7.1 will have in-built support for Common Table Expressions (CTEs). This ensures that code will be more succinct but, more importantly, that we won't have to use Arel::Nodes for complex queries.

With Rails 7.1, we'll have a new .with method to write queries similar to the one below:

Ruby
Post.with( posts_with_comments: Post.where("comments_count > ?", 0), posts_with_tags: Post.where("tags_count > ?", 0) )

Support for Async Bulk Record Destruction

As mentioned, Rails 7.1 will introduce several ways to run code asynchronously. One such new addition to async code executions is the destroy_association_async_batch_size configuration.

With this new configuration, Rails applications can set a maximum number of records to be destroyed in a single background job by the dependent: :destroy_async association.

The default behavior, where all dependent records are destroyed in a single background job when the parent record is destroyed, will remain unchanged. However, if the number of dependent records exceeds the new configuration, they will be destroyed in multiple background jobs.

Other Rails 7.1 Updates

ActiveRecord::Relation#explain Accepts Options

Rails 7.1 will allow you to pass database systems that support EXPLAIN options to ActiveRecord::Relation#explain. An example query might look like this:

Ruby
Customer.where(id: 1).joins(:orders).explain(:analyze, :verbose)

Active Record regroup

Active Record will allow for "regrouping" queries with a new regroup method that can be used like so: Post.group(:title).regroup(:author). This generates SQL equivalent to SELECT posts.* FROM posts GROUP BY posts.author.

The same can be achieved in current versions of Rails with more verbose code:

Ruby
Post.group(:title)..unscope(:group).group(:author)

New stub_const method for Testing

A new stub_const method for ActiveSupport::TestCase will be added that stubs a constant for a yield's duration.

For example:

Ruby
# World::List::Import::LARGE_IMPORT_THRESHOLD = 5000 stub_const(World::List::Import, :LARGE_IMPORT_THRESHOLD, 1) do assert_equal 1, World::List::Import::LARGE_IMPORT_THRESHOLD end assert_equal 5000, World::List::Import::LARGE_IMPORT_THRESHOLD = 5000

Take note, however, that stubbing a constant will affect its value across all threads in a multi-threaded setup. This means that if multiple concurrent threads rely on the same constant, simultaneous and conflicting stubbing may occur.

Password Challenge via has_secure_password

Rails 7.1 has improved the functionality of has_secure_password by adding a password_challenge accessor and a corresponding validation. The validation will verify that a password_challenge matches the stored password_digest.

With this, implementing a password challenge becomes as straightforward as a password confirmation. This also enables reusing the same error-handling logic in both the view and the controller.

For instance, instead of writing separate code in the controller, you will simply use the existing logic for password confirmation.

Ruby
password_params = params.require(:password).permit( :password_challenge, :password, :password_confirmation, ).with_defaults(password_challenge: "") if current_user.update(password_params) # perform some work end

Saving Attachments Returning the Blob

With Rails 7.1, when you save attachments to a record, the attach method will return the attached blob or blobs. This enables the direct use of blob methods on the attachment. However, if the record fails to save, attach will return false.

Here's an example demonstrating its use:

Ruby
@user = User.create!(name: "Josh") avatar = @user.avatar.attach(params[:avatar]) # You can now directly call blob methods as follows: avatar.download avatar.url avatar.variant(:thumb)

Storage of CSRF Tokens Outside of Sessions

Rails has introduced a new configuration option to address the excessive creation and eviction of millions of sessions for just storing a CSRF token when sessions are not stored in cookies.

This option allows the use of a lambda function to store the CSRF token in a custom location, thus enabling the storage of CSRF tokens outside of sessions.

You can also create custom strategy classes for storing CSRF tokens.

Ruby
class CustomStore def fetch(request) # Return the token from a custom location end def store(request, csrf_token) # Store the token in a custom location end def reset(request) # Delete the stored session token end end class ApplicationController < ActionController:x:Base protect_from_forgery store: CustomStore.new end

Validity Checking for PostgreSQL Indexes

Creating indexes as shown below may lead to an invalid index:

Ruby
add_index :account, :active, algorithm: :concurrently

With Rails 7.1, you can verify an index's validity as shown here:

Ruby
connection.index_exists?(:users, :email, valid: true) connection.indexes(:users).select(&:valid?)

ActiveRecord::QueryMethods#select Accepts a Hash

ActiveRecord::QueryMethods#select in Rails 7.1 now accepts a hash of options. This is best demonstrated with an example:

Ruby
# You can now write selects like this: Post.joins(:comments).select( posts: { id: :post_id, title: :post_title }, comments: { id: :comment_id, body: :comment_body} ) # In place of this: Post.joins(:comments).select( "posts.id as post_id, posts.title as post_title, comments.id as comment_id, comments.body as comment_body" )

Number of Processors Match the Puma Worker Count

By default, newly generated Rails applications will have Puma workers that are capped at the total number of physical processors on the host machine. This default setting can always be changed in the puma.rb file.

The puma.rb file for newly-generated Rails applications will now look like the following:

Ruby
if ENV["RAILS_ENV"] == "production" worker_count = ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count } workers worker_count if worker_count > 1 end

preload and eager_load Associations to Be Unscoped

Rails 7.1 will add the ability to unscope preloaded and eager loaded associations in a manner similar to how Active Record's includes, select, and joins methods work.

This feature allows for the use of aggregate functions on has_many associations previously loaded through eager_load or preload in existing queries.

An example usage could look like:

Ruby
query.unscope(:eager_load, :preload).group(:id).select(:id)

Default Dockerfiles for New Rails Applications

Docker files are to be added as a default option for new Rails applications. The files include:

  • Dockerfile
  • .dockerignore
  • bin/docker-entrypoint

These files serve as a starting point for deploying an application in a production environment and are not intended for use during development. However, if desired, you can skip these files by using the --skip-docker option.

Default Health Controller

Rails 7.1 introduces a new endpoint for load balancers and uptime monitors. The endpoint, named Rails::HealthController#show, is mapped to the "/up" path in newly generated Rails applications. This allows load balancers and uptime monitors to easily track an app's availability.

Note that monitoring the database, Redis, and internal network connections to microservices that an application relies on must be managed separately.

New Rails.env.local? for Environment Checks

In Rails 7.1, a new local? method can be used to simplify environment checks.

You'll be able to replace code like:

Ruby
if Rails.env.development? || Rails.env.test? end

With:

Ruby
if Rails.env.local? end

New ActiveRecord::Persistence#update_attribute! Method

Rails has added a new method, ActiveRecord::Persistence#update_attribute!, which functions similarly to update_attribute but uses save! instead of save.

Here's how you could use this new method:

Ruby
class Apm < ActiveRecord::Base before_save :check_name def check_name = throw(:abort) if name == "abort" end monitor = Apm.create(name: "App Signal") # => #<Apm name: "App Signal"> monitor.update_attribute!(:name, "AppSignal") # => #<Apm name: "AppSignal"> monitor.update_attribute!(:name, "abort") # raises ActiveRecord::RecordNotSaved

Templates Capable of Defining Accepted Locals

Templates will be enhanced with the option of required arguments that have default values.

Currently, templates accept any locals as keyword arguments. With 7.1, Rails templates will define specific accepted locals through a magic comment.

This improvement provides greater control and customization options for template behavior and functionality.

A partial in Rails could now look like:

erb
<%# locals: (title: "Default title", comment_count: 0) %> <h2><%= title %></h2> <span class="comment-count"><%= comment_count %></span>

Instead of:

erb
<% title = local_assigns[:title] || "Default title" %> <% comment_count = local_assigns[:comment_count] || 0 %> <h2><%= title %></h2> <span class="comment-count"><%= comment_count %></span>

Wrapping Up

As you can see, Rails 7.1 promises a lot of further improvements on Rails 7.

For more information on features, updates, and bug fixes, check out the Rails 7.1 release notes.

Happy coding!

P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!

Emmanuel Hayford

Emmanuel Hayford

Guest author Emmanuel is a Ruby on Rails developer currently working on his first book, Rad Ruby. He's also the host of The Rails Changelog, a Rails and Ruby podcast.

All articles by Emmanuel Hayford

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps