rails
7 Lesser-Known Features and Changes in Rails 7.1
Rails 7.1 lacks big show-stopping additions, but there are still many improvements to the developer experience that are worth checking out.
Rails 7.1 is finally here, nearly 2 years after 7.0 was released. My initial reaction was that – with a couple notable exceptions (strict locals in ERB views and an auto-loaded lib directory, hooray!) – most of the major features in the release notes were a bit underwhelming.
However, as I dug deeper into the 2,000+ pull requests merged since Rails 7.0, I realized that there are several interesting features and changes, many of which haven’t been widely promoted. Here’s what caught my attention:
- Finally, we can write proper integration tests for errors
- SSL is now on by default in production
-
A new
picture_tag
helper supports responsive images - Development and test log files no longer grow to infinity
-
The
webdrivers
gem has been removed - Active Record enums can now validate like other attributes
- Deployments to Linux work out of the box
1. Finally, we can write proper integration tests for errors
Before Rails 7.1, if you wanted to write an integration test for an error scenario that returned a 404 status, this would not work:
test "attempting to load non-existent user ID results in 404 error" do
get "/users/does-not-exist"
assert_equal(404, response.status)
end
assert_equal
line is never reached.
Surprisingly, the get
method raises ActiveRecord::RecordNotFound
. You may be tempted to set config.action_dispatch.show_exceptions = true
to get this test to pass, but this disables stack traces for legitimate errors.
In Rails 7.1, there is now a clean solution:
config.action_dispatch.show_exceptions = :rescuable
This is a new default for Rails 7.1 apps. For apps being upgraded to Rails 7.1, you may need to add this setting to
test.rb
.
Now integration tests will correctly render a 404 response instead of halting with an exception. Other errors that can’t be rescued (i.e. legitimate bugs) will still raise with a stack trace.
For more details, check out this PR: Make the test environment show rescuable exceptions in responses #45867.
2. SSL is now on by default in production
It probably goes without saying that we should all be using secure cookies and HTTPS in production. Prior to Rails 7.1, you needed to remember to explicitly set config.force_ssl = true
for this to take effect. Without it, your requests might technically still be traveling over HTTPS (depending on your hosting or CDN setup), but without the full security of HSTS and https-only cookies.
In Rails 7.1, force_ssl
is now on by default in production. If you are deploying to a private staging or hobby environment where a full HTTPS stack is not possible, you can opt-out by setting config.force_ssl = false
.
PR: Enable force_ssl=true in production by default #47852
3. A new picture_tag
helper supports responsive images
When developing responsive web apps, sometimes you’ll need to deliver different images to different browsers. Here are a few scenarios:
- For efficiency, you want to deliver a WebP version of an image, but you also need to provide a PNG version as a fallback for older browsers.
- You want to swap between different images based on screen size or orientation.
- You have low-res and hi-res versions of images that are appropriate for different devices based on their screen density.
The HTML <picture>
tag is designed to solve for these use cases. Rails 7.1 adds nice syntactic sugar for generating them with the new picture_tag
helper:
<%= picture_tag(
"picture.webp",
"picture.png",
class: "mt-2",
image: { alt: "Image", class: "responsive-img" }
) %>
picture_tag
helper knows where the alt
and class
attributes should be placed in the generated markup.
The resulting HTML:
<picture class="mt-2">
<source srcset="/images/picture.webp">
<source srcset="/images/picture.png">
<img alt="Image" class="responsive-img" src="/images/picture.png">
</picture>
<img>
tag is automatically constructed based on the last image passed to the helper.
PR: Add a picture_tag helper #48100
4. Development and test log files no longer grow to infinity
Rails helpfully writes very detailed logs to log/development.log
and log/test.log
. But over the course of months and months of fast paced test-driven development, these files could grow very large. I’ve seen some apps with log files over 1 gigabyte. Before Rails 7.1, there was technically no upper limit.
Rails 7.1 now caps development and test log files at 100 MB. Whew!
config.log_file_size = 100 * 1_024 * 1_024 # 100 MB
PR: Rotate Default Logs on Each 100MB #44888
5. The webdrivers
gem has been removed
The webdrivers
gem has long been included in the default Rails Gemfile
to facilitate browser testing. If your tests use chromedriver, webdrivers would automatically detect the necessary chromedriver version and download it, so that you didn’t need to install it manually (e.g. via homebrew).
Fast-forward to 2023, and the selenium-webdriver
gem has taken over this responsibility. The webdrivers gem is now deprecated.
In short, as long as you are using Ruby 3.0+, the latest version of selenium-webdriver
has you covered. You can remove webdrivers
from your Gemfile. In Rails 7.1, rails new
no longer includes it.
PR: Omit webdrivers gem from Gemfile template #48847.
6. Active Record enums can now validate like other attributes
Prior to Rails 7.1, enum
attributes always raised an exception when assigned invalid values. For programmatically-maintained fields, like an internal state machine, this made sense. However it was not a good fit for user-provided data.
class Conversation < ApplicationRecord
enum :status, [:active, :archived]
end
conversation.status = "inactive"
# => ArgumentError, "'inactive' is not a valid status"
ArgumentError
by default.
In Rails 7.1, this behavior is now configurable to provide user-friendly validation errors.
class Conversation < ApplicationRecord
enum :status, [:active, :archived], validate: true
end
conversation.status = "inactive" # no longer raises
conversation.valid? # => false
conversation.errors.full_messages
# => ["Status is not included in the list"]
validate: true
causes it to behave similarly to a regular attribute validated by validates_inclusion_of
.
PR: Make enums validatable without raising error #49100
7. Deployments to Linux work out of the box
To make dependency resolution more predictable, recent versions of Bundler include platform information in the Gemfile.lock
. This means that, for example, a Gemfile.lock
created on macOS will not work on Linux until bundle install
is run in a Linux environment to update the lock file and resolve any Linux-specific dependencies.
That may sound reasonable, but in Rails, it can make for a frustrating experience.
- Many Rails developers are using macOS.
- As a result, the initial
Gemfile.lock
generated when they runbundle install
will include macOS platform information, but not Linux. - Nearly all cloud deployment and CI platforms use Linux.
- Inevitably, the first time a deploy is attempted to Linux, it fails with a Bundler error.
Specifically, Bundler’s error message will instruct you to run this command:
bundle lock --add-platform=x86_64-linux
This is such a common obstacle that Rails 7.1 now includes its own workaround. When you generate an app using rails new
, Rails 7.1 will automatically run the lock
command to make sure the resulting Gemfile.lock
includes Linux support. This ensures the first deploy to a Linux environment will “just work”, even if you develop on Mac.
PR: rails new: add x86_64-linux and aarch64-linux platforms by default #47492