Rails 7 Features: Hotwire by Default & Enhanced ActiveRecord Capabilities

Written by

The release of Rails 7 took place on April 12th, 2021. Rafael França, Rails Core Team member, had confirmed the release was being targeted for the previous Railsconf.

By far the major feature included in Rails 7 is making Hotwire the default approach for building modern and dynamic web applications without having to write much JavaScript.

Hotwire is a collection of components and techniques that use HTML instead of JSON over the wire, as it is usually done in traditional single-page applications.

There are also important additions to ActiveRecord, continuing the valuable work that Eileen M. Uchitelle and the Github team have extracted into the Rails core. Those include, for example, the ability to configure the thread pool for async queries and improvements to the way ActiveRecord connects to multiple database applications that use a custom primary abstract class.

Top 6 noteworthy Rails 7 features for our day-to-day work

1. Add benchmark method that can be called from anywhere

One convenient method I usually use for debugging purposes is the `benchmark` method that is available in views and controllers through ActiveSupport. It simply measures and logs the speed of the code provided in a block.

This small PR allows for using the benchmark method anywhere in your code, such as on your models or services. Anywhere in our code, we can now call:

Rails.benchmark("Importing CSV file") do
  lengthy_import_process  
end

Which will print the time taken to run lengthy_import_process.

Importing CSV file (12300.3ms)

2.  Type cast enum values by the original attribute type.

Before Rails 7 and on MySQL, if you had an enum on your model, like this:

class Post < ActiveRecord::Base
  enum :status, { pending: 0, published: 1, deleted: 2 }
end

If you were to query by status but used an unknown label or had a typo in the value, the find method would wrongly match 0:

Post.find_by(status: :publishd)
# => #<Post id: 1, status: 'pending', ...>

Moreover, the behavior was not consistent between adapters: with MySQL, for instance, it would match 0, while for PostgreSQL, it would raise an exception, and for Sqlite3, it would return nil.

After Rails 7, all adapters are returning nil in all such cases.

3. Additions to `strict_loading`

Rails 6.1 added the strict_loading mode to prevent lazy loading of associations and requires associated records to be eager loaded. It’s a powerful tool to find places where additional queries could be avoided by preloading an association. It’s also a great complement or even replacement for tools such as the Bullet gem, which has been my go-to gem to assist in identifying N+1 queries.
Rails 7 introduces some improvements and convenient settings to configure the strict loading mode.
First, it introduces a mode argument that can be used to enable strict loading for a single record. Until now, enabling strict loading for a single record would raise an error on any lazily loaded association. We can now use n_plus_one_only mode to allow lazily loading associations that are fetched through a single query:

User = User.first
user.strict_loading!(mode: :n_plus_one_only)

user.posts # does not raise an error since posts are fetched through a single query
user.posts.last.body # raises StrictLoadingViolationError since posts has not been eager loaded

Second, Rails 7 allows us to opt-out of strict-loading mode on a per-record basis when strict loading has been enabled application-wide or on a model level:

Class User < ApplicationRecord
  has_many :posts
end

User = User.first
user.posts # => ActiveRecord::StrictLoadingViolationError if strict loading has been set application wide

user.strict_loading!(false) # opting out of strict loading mode
User.posts # => #<ActiveRecord::Associations::CollectionProxy>

4. Support for file and content streaming

Rails 7 adds methods to support file streaming from controllers, and also adds convenience methods to facilitate streaming from ActiveStorage.
Before, you could stream files and on-the-fly generate data through different methods such as manipulating the response headers or through the render method with the :text option, but this commit extracts the existing functionality into a convenient method to be used on any controller:

send_stream(filename: "posts.csv") do |stream|
  stream.writeln "title, author, published_at"

  @posts.find_each do |post|
    stream.writeln [ post.tile, post.author.name, post.published_at ].join(",")
  end
end

At the same time ActiveStorage::Streaming has been extracted and leverages the commit above to allow for streaming a blob from cloud storage:

class PostsAttachmentsController < ApplicationController
  include ActiveStorage::SetBlob, ActiveStorage::Streaming
  
  def show
    send_blob_stream @post.header_image, disposition: params[:disposition]
  end
end

5. Encryption of ActiveRecord attributes

One of the most exciting pieces of news for Rails 7 is the support for attribute encryption. They are regular ActiveRecord attributes that Rails transparently encrypts upon saving and decrypts upon retrieving their values.
You first need to generate a key set to use for encryption and update your Rails credentials by running bin/rails db:encryption:init.
You can then declare any ActiveRecord attributes backed by a column with the same name to be encrypted, as long as they are serializable as strings.

Class User < ApplicationRecord
  encrypts :email_address, deterministic: true, downcase: true
end

There are many options and features supported by the new encrypted attributes, such as deterministic vs. non-deterministic encryption, advanced key management strategies such as custom key providers or key rotation, and an easy-to-use API to gain more control over this feature.

6. New ActiveRecord methods

Finally, ActiveRecord has been extended with several convenience methods that add new functionality and improve the existing API.
The new ActiveRecord::Relation#load_async method, for example, allows for scheduling a query to be performed asynchronously from a thread pool. You can, say, run independent queries in a controller asynchronously:

Class DashboardsController < ApplicationController
  def show
    @users = User.active.load_async
    @recent_posts = Post.recent.load_async
    @pending_posts = Post.pending.load_async
  end
end

ActiveRecord::Relation#excluding has been extracted as a convenience method for excluding the specified record or collection of records from the resulting relation. What you would have done before as Post.recent.where.not(id: current_post,id) can be now expressed as Post.recent.excluding(current_post).

Two new methods have been added to FinderMethods : FinderMethods#sole and FinderMethods#find_sole_by. These serve two purposes, as they are to be used when you expect a single row, but also want to assert that only that row matches the condition. If the condition is not met, or it is met by multiple rows, it will throw an error:

Post.promoted.sole
Post.find_sole_by(promoted: true)

# => ActiveRecord::RecordNotFound      (if no Post with promoted == true)
# => #<Post ...>                       (if only one Post with promoted == true)
# => ActiveRecord::SoleRecordExceeded  (if more than one Post with promoted == true)

A few methods have also been added to conveniently work with associations: build_association and create_association have been added as new constructors on has_one :through associations.

class Employee < ApplicationRecord
 has_one :contract
 has_one :project, through: :contract
end

@employee.build_project

You can also check for the presence or lack of an association with the new where.associated and where.missing methods:

Employee.where.associated(:user)  # Employees with a user entry

Employee.where.missing(:user) # Employees without a user entry

Wrapping it up

The greatest feature to be released as part of Rails 7 is without a doubt Hotwire since its release as a standalone package has drawn attention not only in the Rails community but also on the ecosystems of other languages.
In this blog post, I highlighted a few interesting features that developers can use frequently in their workflow. Some of these might seem like small additions to the framework, but they illustrate how Rails and the Rails community are always focused on providing the most convenient APIs and build upon the joy that is Ruby as a language.
I suggest you check out the Rails 7 Release notes and Changelogs as these are only a few of the many new features to be released. Also, check this article about Ruby on Rails developers’ salaries. If you’re a developer interested in new challenges, I invite you to visit our careers page.

Frequently Asked Questions