What's New in Rails 7.1 | AppSignal Blog

Ask questions Research chat →

https://blog.appsignal.com/2023/02/15/whats-new-in-rails-7-1.html · scraped

rails

Attachments

Scraped Content

— 1691 words · 2026-02-14 17:42:14 UTC ·

Excerpt

![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/3f9c563a-bab3-4069-bb29-9a56799c4596/image?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466QHU3CN65%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174213Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJGMEQCIHHk6jfaVjGOSIAsgi1%2FL6IMnI5qmj%2BLHI%2BSOoMZMYwuAiAfhMqc0H4WroHyLx1zWR4zx1XlF2fkajItCrAU3R5VoSr%2FAwgKEAAaDDYzNzQyMzE4MzgwNSIMK7gH2sfAhiLt59haKtwDKRyx8qilR%2B1z%2BdAmULzhv7k3FsDd8P0i4mC%2BPi4XAas%2BdkVIKyUscHtdJY3AjO%2BtdjKNIhMDqpa4PQUF8I%2FHc33mrVsvtWz6cpnkZw7bMVe3X65wf3ZimGVjfujpCvyiNcdv5AZdEbz6aVkg%2Bf3RxFEmhJmAuxZv8OzTmwaMznSd3MNfKlvPCsFEO9gC%2BYXU%2F%2F4DYrC2jEEokFdOx2ar6tcnj6pnLWJx9C4AIbX4IF9a5oQAIslQ9nk6JaV0OCgqOKDFozh8UQ7BI1ZWGSreNEMgtSCyRt6MvRTXLmy6yuMHba0BaZfJ%2FDldy9S6U0Cyjq3Th%2FEwVLGbN9z0%2BgkToVbYq5QpgRpUs1vdd1%2F8PxsRFQKr2vVvKwt2uuFWgOjtlcSeH0AX3Kyk54ckfigebaMekFe3Rbo%2F5n5UgYFG7
![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/3f9c563a-bab3-4069-bb29-9a56799c4596/image?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466QHU3CN65%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174213Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJGMEQCIHHk6jfaVjGOSIAsgi1%2FL6IMnI5qmj%2BLHI%2BSOoMZMYwuAiAfhMqc0H4WroHyLx1zWR4zx1XlF2fkajItCrAU3R5VoSr%2FAwgKEAAaDDYzNzQyMzE4MzgwNSIMK7gH2sfAhiLt59haKtwDKRyx8qilR%2B1z%2BdAmULzhv7k3FsDd8P0i4mC%2BPi4XAas%2BdkVIKyUscHtdJY3AjO%2BtdjKNIhMDqpa4PQUF8I%2FHc33mrVsvtWz6cpnkZw7bMVe3X65wf3ZimGVjfujpCvyiNcdv5AZdEbz6aVkg%2Bf3RxFEmhJmAuxZv8OzTmwaMznSd3MNfKlvPCsFEO9gC%2BYXU%2F%2F4DYrC2jEEokFdOx2ar6tcnj6pnLWJx9C4AIbX4IF9a5oQAIslQ9nk6JaV0OCgqOKDFozh8UQ7BI1ZWGSreNEMgtSCyRt6MvRTXLmy6yuMHba0BaZfJ%2FDldy9S6U0Cyjq3Th%2FEwVLGbN9z0%2BgkToVbYq5QpgRpUs1vdd1%2F8PxsRFQKr2vVvKwt2uuFWgOjtlcSeH0AX3Kyk54ckfigebaMekFe3Rbo%2F5n5UgYFG7HnC49d8sQN8vSIon9DpezhHzMcMdg4nEeQLgXHOoOontxxjn9P2j4mfa1HAGLBN81i3koLUL793Ug6fMfzqmf%2FRMlE8nSEzhd3tpEKxC8Meyb3Eu7Sb1kelmvvc1gMC8CZkQcHpsSeRqhuLwThcewNec9whxdvUmwPIhSwbrZOJveYQ5Edu597Dq%2F2St4dHqtDiKttZ8xy8PjdGXaQwktLCzAY6pgGsa4S1AiaE%2FoIBBeWEMHocfeK7q5eHHxrk%2BkcppSTMwQwNjWYI3etqRHkoba6qHdDfB7s28U74dmRri4sdunMPJAopgA92gqOy3yVnhL%2FeVBwR2bwVHBCmDtFaB95AgXlKssidDRqWXIRX7TzKjnigEvDi47C8i8REi4HX461u4%2BATemCAepS3AKNlVlg9PtOGL0cC7WN6QPuDo0gpnWlLPrWqAGYq&X-Amz-Signature=c57a3d1796e9c3cdd288aafcc5ccf1154501a2ac1ea329f70419244208bed54b&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject) 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: - 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: ```plain text 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: ```plain text 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: ```plain text 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: ```plain text 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: ```plain text 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: ```plain text 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: ```plain text # 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. ```plain text 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: ```plain text @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. ```plain text 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: ```plain text add_index :account, :active, algorithm: :concurrently ``` With Rails 7.1, you can verify an index's validity as shown here: ```plain text 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: ```plain text # 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: ```plain text 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: ```plain text 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: ```plain text if Rails.env.development? || Rails.env.test? end ``` With: ```plain text 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: ```plain text 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: ```plain text <%# locals: (title: "Default title", comment_count: 0) %> <h2><%= title %></h2> <span class="comment-count"><%= comment_count %></span> ``` Instead of: ```plain text <% 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! ![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/7df29478-b0b0-4108-b861-88b0de7697f3/image?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466R2L74UF5%2F20260214%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260214T174214Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEEaCXVzLXdlc3QtMiJHMEUCIQCQYFsy91E6ffFJ5fZWUBpNPkAcj%2FNFFnEmqdW3tX1%2F%2BgIgHUbVWg%2FU1KJQBi4w1PPdsKldIbXVFBAjzhftTy43rLIq%2FwMIChAAGgw2Mzc0MjMxODM4MDUiDFwy%2BU9AzMD6GNgCmSrcA1U1yMx95o6TBRQw8YXL6wTk4Bp3gKU1F3rx15vOwf%2FEQgGnSXySnOJnZ8qBFpfFjTZwSF2Rxc2fcWrQ6OArl%2BS28JwzeN4Y%2F1vikHivU%2FBeJyVKi1jXj%2F6Aztd7eO4MQK1ILYBKowocBd1kDcgI8AdFTenlr9orWAVDcRpRv%2BPxDrepRL5zbK4hYEVZz70Yq8kj46m51Hrj11%2BYXrbrWHUxfo5IsXVieJp5Io0TjWgz6hO%2FXAnS0tvmTXnmoQ48KjOn6OCs%2Fy2bEVnk4D9gufwB8iOJiwoxsZLmQKbyYbjWyWm4%2BoEgCO5FP60t%2BEhnP1gefQ2S9mwv9%2BlaLncigx5ndylkq1Z2EpsIaFPcxxWFOD46pG27SpvPaiJFpov3f5c0J8MBTRX8I28f%2FdNNBJwuaqvTislK1zv3hVDQxnyavHfgfygbEFJhwi1PPl9wL8SuoxIkJl3ajGgLjAhfFLIiwcK638k9t%2BPdz7ln0l0%2Fxqi4tdrtnFoZeuwtWOIvcukzZWEKZped0nx3%2BB0rqWbTDRH%2BUsoPfoVMIcHNteKtVThmkJUqem3FCqkBcJ9BJ%2B4bY4uB%2B85er7WrGEGaGz%2FyYkobyJmS%2Be1yO894oX%2FHK0KbJ%2FHbizA2mn3qMJ7RwswGOqUBtomoy04vNuHo3gHzlWfMGdZtmUQYHPZ1fI1j%2F0av9q58kf5psXO0hRDGUlFmlYiKhFZ934JRtrnO02XBZ4Bdd5SqHwae3njgtTsSs2vMshkqamMiGpJsq9P05aghg1xSUBa56mvQEBq5HVqKOJ7zu52dhvDKsX9SgoVf9jFPwvFk2BVTFL6h8h5d3FCVbDl4FytbygebjezbVBFtG6GWiIhqpNHu&X-Amz-Signature=7c004fff2b6fcd063d6d1c9c1f260170ed4c0f43c85a20eb6653cacfa07f93e5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject)

Visibility

Visible to everyone

Reading Status

Related Bookmarks

My Note


Saved!

Annotations

Export as Markdown
+ Annotate selection

Add Annotation