What's Cooking in Rails 7?
Tags: rails, ruby
Published: 2021-04-21 16:16:09

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](https://edgeguides.rubyonrails.org/7_0_release_notes.html) 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! 🙏

# Railties

## The _main_ Branch
### [PR 40254](https://github.com/rails/rails/pull/40254) by [@prateekkish](https://github.com/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](https://guides.rubyonrails.org/v5.1.0/5_1_release_notes.html#railties-notable-changes) via [PR 27632](https://github.com/rails/rails/pull/27632) by [@dixpac](https://github.com/dixpac).

## _benchmark_ Anywhere
### [PR 40734](https://github.com/rails/rails/pull/40734) by [@semaperepelitsa](https://github.com/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:

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

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](https://github.com/rails/rails/pull/40597) by [@joelhawksley](https://github.com/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.

# ActionPack

## Redirect Back or To
### [PR 40671](https://github.com/rails/rails/pull/40671) by [@dhh](https://github.com/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](https://github.com/rails/rails/blob/07233426e693ea0b8292050fb926de4d52fbfeeb/actionpack/lib/action_controller/metal/redirecting.rb#L69-L71).

##  Unpermitted Parameter Context
### [PR 41809](https://github.com/rails/rails/pull/41809) by [@bbuchalter](https://github.com/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.

# ActionView

## Lazy Load Images
### [PR 38452](https://github.com/rails/rails/pull/38452) by [@jonathanhefner](https://github.com/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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ikt8kqkge2g0l66kdb1b.png)

## Tag Attributes From Hash
### [PR 40657](https://github.com/rails/rails/pull/40657) by [@seanpdoyle](https://github.com/seanpdoyle)

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

For example:

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

Would render:

```html
<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.

# ActiveModel

## Add Numericality In Range Validation
### [PR 41022](https://github.com/rails/rails/pull/41022) by [@mpapis](https://github.com/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:

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

# ActiveRecord

## Invert a _where_ Clause
### [PR 40249](https://github.com/rails/rails/pull/40249) by [@kddeisz](https://github.com/kddeisz)

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

For example:
```ruby
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](https://github.com/rails/rails/pull/41439) by [@GlenCrawford](https://github.com/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:

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

You can:
```ruby
other_users = User.where(rating: 80..).excluding(primary_user)
```

## Build or Create Association on Has One Through
### [PR 40007](https://github.com/rails/rails/pull/40007) by [@perezperret](https://github.com/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:

```ruby
class Dog
  has_many :toys
  has_one :toy_box
end

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

class ToyBox
  belongs_to :dog
end
```

You'll now be able to:

```ruby
toy.build_toy_box
# <ToyBox:0x00007f572007e170 id: nil, dog_id: 3>
```
Or:

```ruby
toy.create_toy_box
# <ToyBox:0x00005601f2ac09a0 id: 5, dog_id: 3>
```

## Respect Column Type When Calculating Average
### [PR 40351](https://github.com/rails/rails/pull/40351) by [@schmijos](https://github.com/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:

```ruby
Coordinate.last.longitude.class
# 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:

```ruby
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.

```ruby
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](https://github.com/rails/rails/pull/41659) by [@jorgemanrubia](https://github.com/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](https://github.com/rails/rails/blob/main/actionpack/CHANGELOG.md), [ActionView](https://github.com/rails/rails/blob/main/actionview/CHANGELOG.md), [ActiveRecord](https://github.com/rails/rails/blob/main/activerecord/CHANGELOG.md), and [ActiveSupport](https://github.com/rails/rails/blob/main/activesupport/CHANGELOG.md) 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!