ruby

What's New in Rails 7

Pulkit Goyal

Pulkit Goyal on

What's New in Rails 7

Rails 7 is just around the corner. We don't have a confirmed release date, but it is expected to be available before Christmas, so not very long to go. The latest version as of this post's publication is 7.0.0.rc1, the first release candidate. Basecamp, HEY, Github, and Shopify have all been running the Rails 7 alpha in production, so we can expect even the release candidate to be pretty stable.

In this post, we will look at some of the new features and changes that Rails 7 will bring.

Node and Webpack Not Required

Yes, you read that right! JavaScript in Rails 7 will no longer require NodeJS or Webpack. And you can still use npm packages.

Transpiling ES6 with Babel and bundling with Webpack require a lot of setup. While Rails supported it pretty well with the Webpacker gem, this brought a lot of baggage, was hard to understand and make any changes to, especially while maintaining upgradability.

Now, the default for new apps created with rails new is to use import maps through the importmaps-rails gem. Instead of writing a package.json and installing dependencies with npm or yarn, you use ./bin/importmap CLI to pin (or unpin or update) dependencies.

For example, to install date-fns:

sh
$ ./bin/importmap pin date-fns

This will add a line in config/importmap.rb like:

ruby
pin "date-fns", to: "https://ga.jspm.io/npm:date-fns@2.27.0/esm/index.js"

In your JavaScript code, you can continue using everything as you used to:

javascript
import { formatDistance, subDays } from "date-fns"; formatDistance(subDays(new Date(), 3), new Date(), { addSuffix: true }); //=> "3 days ago"

One thing to keep in mind with this setup is that there is no transpiling between what you write and what the browser gets. For the most part, this is ok since all browsers that matter now support ES6 out of the box.

But this also means that you won't be able to use TypeScript or JSX as they require transpilation to JS before use.

So, if you want to use React with JSX, you still have to fall back to a different setup (using webpack/rollup/esbuild).

Rails 7 can do this for you. All you need is one command with your chosen strategy:

sh
$ ./bin/rails javascript:install:[esbuild|rollup|webpack]

Applications generated with Rails 7 will get Turbo and Stimulus (from Hotwire) by default, instead of Turbolinks and UJS. Hotwire is a new approach that delivers fast updates to the DOM by sending HTML over the wire.

Encryption at Database Layer

Rails 7 allows marking certain database fields as encrypted using the encrypts method on ActiveRecord::Base. This means that after an initial setup, you can write code like this:

rb
class Message < ApplicationRecord   encrypts :text end

You can continue using the encrypted attributes like you would any other attribute. Rails 7 will encrypt and decrypt it automatically between the database and your application.

But this comes with a slight quirk: you cannot query the database by that field unless you pass a deterministic: true option to the encrypts method. The deterministic mode is less secure than the default non-deterministic mode, so only use it for attributes you absolutely need to query.

Asynchronous Querying

There is now a load_async method that you can use when querying data to fetch results in the background. This is especially important when you need to load several un-related queries from a controller action. You can run:

rb
def PostsController   def index     @posts = Post.load_async     @categories = Category.load_async   end end

This will fire both queries in the background at the same time. So, if each query takes 200ms, the total time spent fetching the data is ~200ms instead of 400ms, if they are fetched serially.

Zeitwerk Mode for Rails 7

This is a breaking change for older applications that still run the classic loader. All Rails 7 applications must use Zeitwerk mode, but the switch is pretty easy. Check out the full Zeitwerk upgrade guide.

Other Rails 7 Updates

Retry Jobs Unlimited Times

ActiveJob now allows passing :unlimited as the attempts parameter on retry_on. Rails will continue to attempt the job without any maximum number of attempts.

rb
class MyJob < ActiveJob::Base   retry_on(AlwaysRetryException, attempts: :unlimited)   def perform     raise "KABOOM"   end end

Named Variants

You can now name variants on ActiveStorage instead of specifying size on every access.

rb
class User < ApplicationRecord   has_one_attached :avatar do |attachable|     attachable.variant :thumb, resize: "100x100"   end end #Call avatar.variant(:thumb) to get a thumb variant of an avatar: <%= image_tag user.avatar.variant(:thumb) %>

Hash to HTML Attributes

There is a new tag.attributes method for use in views that translates a hash into HTML attributes:

erb
<input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>

will produce

html
<input type="text" aria-label="Search" />

Ruby debug

The new default for debugging has changed from byebug to the debug gem.

Instead of calling byebug, you now need to call debugger in the code to enter a debugging session.

Assert a Single Record with sole

When querying records, you can now call sole or find_sole_by (instead of first or find_by) when you want to assert that the query should only match a single record.

rb
Product.where(["price = %?", price]).sole # => ActiveRecord::RecordNotFound      (if no Product with given price) # => #<Product ...>                    (if one Product with given price) # => ActiveRecord::SoleRecordExceeded  (if more than one Product with given price) user.api_keys.find_sole_by(key: key) # as above

Check Presence/Absence of an Association

We can now use where.associated(:association) to check if an association is present on a record instead of joining and checking for the existence of an id.

rb
# Before: account.users.joins(:contact).where.not(contact_id: nil) # After: account.users.where.associated(:contact)

Stream Generated Files from Controller Actions

You can now use send_stream inside a controller action to start streaming a file that is being generated on the fly.

rb
send_stream(filename: "subscribers.csv") do |stream|   stream.write "email_address,updated_at\n"   @subscribers.find_each do |subscriber|     stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"   end end

This provides an immediate (partial) response to the user so that they know something is happening and has an added benefit if you deploy on Heroku.

Since the file will start streaming immediately, Heroku will not terminate the connection. This means you don't need to resort to background jobs to generate one-off files that take longer than 30 seconds.

Upgrading to Rails 7

As with previous versions of Rails, upgrading is simple. While we don't have an official upgrade guide yet, the steps will remain the same:

  1. Change the Rails version number in the Gemfile (7.0.0.rc1 as of the publication date) and run bundle update.
  2. Run bundle exec rails app:update. Follow the interactive CLI and add/replace/modify the files as required.
  3. Run your tests and verify everything works as expected.

Wrap Up

You can see the full list of bug fixes, features, and changes in the Rails 7 release notes. These are not comprehensive at the moment, but we can expect them to be updated soon.

If you are still running Rails 6 or lower, please note that with the final release of Rails 7, Rails 6.1 will enter the "security issues only" mode and will no longer receive bug fixes. This will also mark the EOL for Rails 5.2, as it will no longer receive any fixes.

Have fun coding!

P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!

Pulkit Goyal

Pulkit Goyal

Our guest author Pulkit is a senior full-stack engineer and consultant. In his free time, he writes about his experiences on his blog.

All articles by Pulkit Goyal

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps