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.record
. ErrorReporter
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 includes
, select
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: required
, multiple
, size
.
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.