Here are links to the other parts of the series:

This post brings us to the last in the “An Overview Of Ruby on Rails 7.1 Features” series. Rails has improved a lot over the years, no question about that, but this minor version, in my books, is the most exciting. Rails now comes inbuilt with Dockerfiles. Who’d have thought?

You can take all of these features for a spin in Rails 7.1.0.alpha now.

gem "rails", github: "rails/rails", branch: "main"

Here are 17 more reasons to celebrate the release of Rails 7.1.

01. ErrorReporter to handle several error classes.

This fully backwards-compatible pull request adds the ability to pass a list of error classes to Rails.error.handle and Rails.error.recordErrorReporter now allows you to handle several error classes in one go. You can now handle multiple error classes like so:

Rails.error.handle(ArgumentError, TypeError) do

# Do something with the errors

end

02. Support checked as a kwarg in check_box_tag.

This involves a fix and some improvements to the API documentation. In the past, if you did something like check_box_tag "admin", "1", checked: false you’d expect that some checkbox would be unchecked right? Wrong! That piece of code wasn’t doing what one would expect it to do. This pull request fixes that.

It doesn’t stop there, it goes ahead and updates check_box_tag and radio_button_tag to support checked as a positional or keyword argument, and improves the API documentation for both check_box_tag and radio_button_tag.

03. Expose request.parameter_filter.

This pull request exposes the ActiveSupport::ParameterFilter object used in requests to filter values in a hash so you can filter your hashes based on the same parameter filter as the request. With this, you can now do magic tricks like:

#
request.parameter_filter.filter ("secret" => "skrt", "name" => "Manny")
#=> { "secret" => "[FILTERED]", "name" => "Manny" }

04. validate: false added to foreign keys and check constraints in schema.rb.

Previously, the schema.rb file did not record whether validate: false was used when adding a foreign key or check constraint. As a result, when restoring a database from the schema, foreign keys or check constraints that should not have been validated were validated. This pull request updates the schema.rb file to include validate: false in add_foreign_key and t.check_constraint when the foreign key or check constraint should not be validated. This will ensure that the database is properly restored and foreign keys and check constraints are not validated incorrectly.

05. Puma worker count to match the number of processors.

Rails updated the Puma template to include a new default along the lines of:

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

For newly-generated Rails applications, Puma workers will now max out to the total number of physical processors on the host by default. Of course, this can always be modified in your puma.rb.

06. Allow unscoping of preload and eager_load associations.

This pull request introduced the capability to unscope preloaded and eager loaded associations, in a manner similar to how includesselect and joins methods work in Active Record. This feature enables the ability to apply aggregate functions on has_many associations that have been previously explicitly loaded through eager_load or preload in existing queries.

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

07. Add a build persistence method.

This pull request enhances the ActiveRecord::Persistence module with a new build method. The method creates one or more objects and returns them. The input for the attributes parameter must be a hash or an array of hashes, each containing the attributes of the object(s) to be constructed.

Here are a few examples of usage.

#Build a single new object
User.build(first_name: 'Jamie')

#Build an array of new objects
User.build([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }])

#Build a single object and pass it into a block to set other attributes.
User.build(first_name: 'Jamie') do |u|
u.is_admin = false
end

08. YAML serialization options.

Rails allows you to set an application-wide config.active_record.yaml_column_permitted_classes that defaults to [Symbol]Rails now added the functionality to allow setting YAML serialization options on a per-attribute basis.

09. Allow resetting singular associations.

For collection ActiveRecord::Associations::CollectionProxy associations, there’s a reset method that essentially resets a cached query. Let’s go through an example:

class Person < ActiveRecord::Base
has_many :pets
has_one :car
end

person = Person.first

# Fetches pets from the database

person.pets

# => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]

# Uses the pets cache

person.pets

# => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]

# Clears the pets cache

person.pets.reset

# Fetches pets from the database

person.pets

# => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]

This pull request does the same for has_one and belongs_to associations, you can now call a reset_association method on the owner model (where “association” is the name of the association). This method clears any cached associate records and subsequently retrieves them from the database upon the next access. Here’s an example for this one, using our Person class above we can now do something like person.car.reset_person. Pretty cool, right?

10. Avoid validating belongs_to association if it has not changed.

This feature is not as palpable as the others but a necessary one nonetheless.

Active Record previously performed an unnecessary query to check for the presence of belongs_to associations when updating a record, even if the associated attribute hadn’t changed. This has been improved so that only belongs_to-related columns are checked for presence. However, this approach can lead to orphaned records. To prevent this issue, it is recommended to use a foreign key constraint.

Currently, queries look like this:

post.update!(title: "Rails is the best")
TRANSACTION (0.1ms) BEGIN
User Load (0.5ms) SELECT "users".\* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Post Update (0.3ms) UPDATE "posts" SET "title" = $1 WHERE "posts"."id" = $2 [["title", "Rails is the best"], ["id", 3]]
TRANSACTION (39.5ms) COMMIT

This pull request improves the query to look like this:

post.update!(title: "Rails is the best")
TRANSACTION (0.1ms) BEGIN
UPDATE "posts" SET "title" = $1 WHERE "posts"."id" = $2 [["title", "Rails is the best"], ["id", 4]]
TRANSACTION (0.4ms) COMMIT

Essentially, Rails avoids validating belongs_to association if it has not changed.

This behaviour can be configured via config.active_record.belongs_to_required_validates_foreign_key = false and will be disabled by default with load_defaults 7.1.

11. Allow f.select to be called with a single options hash.

select can now be called with a single hash containing options and some HTML options.

Previously, this would not work as expected:

<%= select :post, :author, authors, required: true %>

Instead, you needed to do this:

<%= select :post, :author, authors, {}, required: true %>

Now, either form is accepted, for the following HTML attributes: requiredmultiplesize.

12. assets:precompile to run in a production build step without passing in RAILS_MASTER_KEY.

This pull request was a fix to an old issue reported in Rails that apparently bothered a lot of Rails developers. This fix was five years late, but now, when you’re compiling assets in production as part of an image build step, you don’t have to pass in a real RAILS_MASTER_KEY. Just like in the development and test environments, we can now pass in a dummy secret_key_base via ENV["SECRET_KEY_BASE_DUMMY"] = 1. This will not give access to any of the real credentials or message verifiers, but allow the build step to complete, since it typically does not need it anyway.

13. Docked Rails CLI.

For those new to Rails, the initial barriers of installation and setup can be overwhelming. Without knowing where to begin, many may give up on the idea of trying Rails altogether.

This new tool in the Rails ecosystem aims to eliminate that hassle. It simplifies the process by installing Rails and all necessary dependencies in a Docker container, allowing curious minds to quickly start building a basic Rails application with ease.

14. Rails applications now come with default Dockerfiles.

This pull request introduces the addition of Docker files as a default option for new Rails applications. Included in the files are:

  • Dockerfile
  • .dockerignore
  • bin/docker-entrypoint

These files are intended as a starting point for deploying the application in a production environment and should not be used during development. However, if desired, these files can be skipped using the --skip-docker option.

Example:

docker build -t app .
docker volume create app-storage
docker run --rm -it -v app-storage:/rails/storage -p 3000:3000 --env RAILS_MASTER_KEY=<see config/master.key> app

You can also start a console or a runner from this image:

#
docker run --rm -it -v app-storage:/rails/storage --env RAILS_MASTER_KEY=<see config/master.key> app console

15. Rails.env.local? is the latest method for ENV checks.

Rails 7.1 will combine checks for the development and test environments, so you can do stuff like:

if Rails.env.local?

# Do something

end

# In place of

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

# Do something

end

16. An option to disable methods that ActiveRecord#enum generates.

Do you remember the enum method from ActiveRecord::Enum that was introduced to Rails 4.1? This method declares an enum attribute where the values map to integers in the database. It generates a bunch of methods meant to make our work with enum attributes painless.

So if you had something like:

class Conversation < ActiveRecord::Base
enum :status, [ :active, :archived ]
end

The above would generate all of these methods for you, for free:

# conversation.update! status: 0

conversation.active!
conversation.active? # => true
conversation.status # => "active"

# conversation.update! status: 1

conversation.archived!
conversation.archived? # => true
conversation.status # => "archived"

For some Rails developers, these method names are conflicting with application code method names. This pull request adds the instance_methods option to the enum definition. When it’s set to false, Active Record will not generate the instance methods it does by default if you did:

class Conversation < ActiveRecord::Base
enum :status, [ :active, :archived ], instance_methods: false
end

17. Add a default health controller.

This pull request introduces a new endpoint for load balancers and uptime monitors by adding a Rails::HealthController#show method and mapping it to the “/up” path in newly generated Rails applications. With this new endpoint, load balancers and uptime monitors can easily track whether or not an app is up.

However, if you need to monitor the database, Redis or internal network connections to microservices that your application relies on, you will need to take care of monitoring yourself.

In summary, this series on Rails 7.1 features highlights some of the most noteworthy updates coming in the new version of the framework. While it is not an exhaustive list, the features discussed were chosen for their significance and impact.

This post made its way onto Hacker News and generated some interesting perspectives and insights.

That’s it! Thanks for taking the time to read the series. And to all the 522 contributors that made all these features possible, the Rails Community is truly grateful!

If you want to stay updated on Ruby and Rails, you can subscribe to my newsletter below to receive occasional updates on related topics that may interest you.

Last Update: January 10, 2024