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
:
$ ./bin/importmap pin date-fns
This will add a line in config/importmap.rb
like:
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:
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:
$ ./bin/rails javascript:install:[esbuild|rollup|webpack]
Turbolinks and UJS Replaced by Turbo and Stimulus
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:
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:
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.
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.
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:
<input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>
will produce
<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.
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.
# 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.
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:
- Change the Rails version number in the Gemfile (
7.0.0.rc1
as of the publication date) and runbundle update
. - Run
bundle exec rails app:update
. Follow the interactive CLI and add/replace/modify the files as required. - 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!
Top comments (0)