What's Cooking in Rails 7?

Jason Dinsmore - April 20, 2021

A new version of Rails always brings new and exciting features. This writeup takes a look at some of the things Rails 7 has in store for us.

To find these examples, I dug through the Changelogs and selected some of the features that have me most excited so far, primarily based on how I currently use Rails.

I encourage you to do the same - you may find something awesome that impacts a feature you use regulary or provides functionality you've been lying awake at night fantasizing about.

For each feature, I have included the PR that introduced it and the GitHub user who submitted the PR.

Note that several of these features have followup PRs from people making essential contributions by updating documentation, adding changelog entries, tweaking implementations, etc. If you are one of those folks, please know that while you're not given explicit credit here, we are very grateful for all you do! 🙏


The main Branch

PR 40254 by @prateekkish

When generating a new Rails project or plugin without the --skip-git flag, the default branch of the created repository will now be main instead of master. If you've got a different branch set in your git config's init.defaultBranch, that default will be used instead.

Rails began initializing the Git repository in 5.1 via PR 27632 by @dixpac.

benchmark Anywhere

PR 40734 by @semaperepelitsa

This PR adds the ActiveSupport::Benchmarkable.benchmark method to the Rails namespace, which allows you to easily benchmark a block of code anywhere without having to declare a logger or extend/include the ActiveSupport::Benchmarkable module.

To use the new functionality, you'd do something like this:

  Rails.benchmark('Print bar') do
    # code to benchmark
    100.times { puts 'bar' }

After your code executes, the message you provided and the time it took the code to execute in milliseconds will be passed along to the Rails logger, ie: Print bar (0.6ms). The benchmark call will return the return value of the block, 100 in this case.

Support Stats For Stylesheets and ERB Views

PR 40597 by @joelhawksley

If you've run rails stats, you know that it provides various statistics about your app's codebase. This new functionality adds Views and Stylesheets to the output, providing stats for files in app/views and app/assets/stylesheets.

Note that stylesheets need to have a .css or .scss extension, and view files need to have the .erb extension in order to be processed.


Redirect Back or To

PR 40671 by @dhh

Adds redirect_back_or_to(fallback, **) as a nice shorthand for redirect_back(fallback_location:, **).

This avoids having to specify the fallback_location kwarg (keyword argument) to redirect_back. redirect_back_or_to will redirect the user back to whence they came if it can, and will redirect to the fallback location if it can't.

The old syntax still works and hasn't been explicitly deprecated, but now redirect_back calls redirect_back_or_to under the hood.

Unpermitted Parameter Context

PR 41809 by @bbuchalter

This PR provides controller and request context when the parameters object is initialized so that when unpermitted attributes are logged, the controller, action, request, and filtered parameters will be logged as well.


Lazy Load Images

PR 38452 by @jonathanhefner

Currently, apps can take advantage of the HTML standard's lazy loading of images by manually specifying the loading attribute as lazy in calls to image_tag. This new configuration allows you to set the default to "lazy" app-wide, so that the attribute will only need to be specified for places you want an image to be eager loaded.

An eager beaver

Tag Attributes From Hash

PR 40657 by @seanpdoyle

This cool addition provides a slick way to tranform a hash of attributes into attributes on an ERB tag.

For example:

<div <%= tag.attributes({ id: 'percent-loaded', role: 'progressbar', aria: { valuenow: '75' }}) %>>

Would render:

<div id="percent-loaded" role="progressbar" aria-valuenow="75">

Perhaps not the greatest example, but if the hash were generated programmatically and required logic, this could be pretty useful.


Add Numericality In Range Validation

PR 41022 by @mpapis

When validating numericality of a model attribute, this new syntax lets you specify a range for the numericality validator versus having to sandwich between the greater_than_or_equal_to and less_than_or_equal_to options.

Example usage:

validates :latitude, numericality: { in: -90..90 }


Invert a where Clause

PR 40249 by @kddeisz

This addition provides a handy way to get at the inverse of a where clause by via invert_where.

For example:

good_students = Student.where(grade: 80..100)
# SELECT \"students\".* FROM \"students\" WHERE \"students\".\"grade\" BETWEEN 80 AND 100

bad_students = good_students.invert_where
# SELECT \"students\".* FROM \"students\" WHERE NOT (\"students\".\"grade\" BETWEEN 80 AND 100)

Exclude a Record From Results

PR 41439 by @GlenCrawford

Ever need a way to get all records matching some condition except a record you already have? excluding may be your jam.

Instead of:

other_users = User.where(rating: 80..).where.not(id: primary_user)

You can:

other_users = User.where(rating: 80..).excluding(primary_user)

Build or Create Association on Has One Through

PR 40007 by @perezperret

Enables the build_association and create_association functionality for has_one through: relations. Previously these were not available on through associations.

For example, if you had:

class Dog
  has_many :toys
  has_one :toy_box

class Toy
  belongs_to :dog
  has_one :toy_box, through: :dog

class ToyBox
  belongs_to :dog

You'll now be able to:

# <ToyBox:0x00007f572007e170 id: nil, dog_id: 3>


# <ToyBox:0x00005601f2ac09a0 id: 5, dog_id: 3>

Respect Column Type When Calculating Average

PR 40351 by @schmijos

In prior versions of Rails, calling ActiveRecord::Caculations#calculate with :average would result in a BigDecimal, even if a column was of a Float or Integer type.

For example:

# Float
Coordinate.calculate(:average, :longitude).class
# BigDecimal
Coordinate.calculate(:average, :longitude)
# 0.13002356e3

With the new behavior, it honors the type of the database column:

Coordinate.calculate(:average, :longitude).class
# Float
Coordinate.calculate(:average, :longitude)
# 130.02356

One of the motivators for this change was that JSON conversions of BigDecimal result in a string value, whereas Float results in a numeric value.

JSON.parse({ avg_longitude: 130.02356.to_d }.to_json)
# { "avg_longitude" => "130.02356" }
JSON.parse({ avg_longitude: 130.02356 }.to_json)
# { "avg_longitude" => 130.02356 }

Automatically Encrypt/Decrypt a Model Attribute

PR 41659 by @jorgemanrubia

Finally, we get to encrypts.

This is deserving of a post all its own. The TLDR; version is that this feature adds a mechanism to auto-encrypt/decrypt an ActiveRecord attribute by declaring encrypts attr_name in the model. When a record is written to the database, it will automatically be encrypted, and when it is loaded from the database, the attribute will automatically be decrypted.

encrypts also provides a workflow for changing the encryption scheme being used, without needing to re-encrypt all previously encrypted records under the new scheme.

Data can be encrypted in a non-deterministic manner (encrypting the same text twice will result in different ciphertexts), or can be encrypted deterministically - which allows querying the encrypted attribute.

Other niceties are the capability to encrypt Action Text and to filter encrypted attributes from the application log files.

It really does quite a lot and provides several ways to customize behavior, depending on your needs.

What Else?

I touched on the things I am most excited about so far, but there is other great work underway. ActionPack, ActionView, ActiveRecord, and ActiveSupport in particular, all have rapidly growing lists of changes.

Thanks again to all the contributors who are making this next release possible. It's shaping up nicely!

Jason Dinsmore

Jason is a software engineer at Hint. He loves crafting great software, improving his fitness, and chillin' with any of his 5 dogs.


Ready to Get Started?