What's Coming in Ruby on Rails 7.2: Database Features in Active Record

Andrew Atkinson - Aug 7 - - Dev Community

Ruby on Rails is currently in major version 7.1 and rolling towards Rails 8, the next comprehensive new release.

Before Rails 8, though, there’s a significant version that will help bridge the gap: Ruby on Rails 7.2.

In this post, we’ll dive into several noteworthy changes in Ruby on Rails 7.2, focusing on the support for database changes in Active Record.

You'll come away with hands-on opportunities to work with these features.

Let's get started!

Active Record Database-Related Features in Ruby on Rails 7.2

The Active Record object-relational mapper (ORM) supports three open-source relational database management systems: MySQL, PostgreSQL, and SQLite. Active Record is most commonly thought of for its ORM facilities, mapping between database operations and database types to Ruby methods and types. However, besides the ORM, Active Record has mechanisms to evolve your database schema definition, capturing incremental changes and a representation of the entire schema in Ruby files or SQL files.

Active Record can connect a single Rails app to multiple databases, and those databases can have distinct roles. The databases might have writer or reader roles or even be “shards” that leverage the Horizontal Sharding feature.

Recently, Beta releases of 7.2 have shipped, allowing developers to preview what’s changed.

As we roll towards Rails 8, we must first pass through 7.2. Let’s examine what's in this release.

What’s New with Composite Primary Keys (CPKs)

Rails 7.1 brought us the first release of composite primary keys (CPKs). What are CPKs? First, let’s talk about unique identifiers in databases more generally. Uniquely identifying rows can take the shape of “natural keys” or “surrogate keys”. Since most databases use surrogate keys, let’s focus on those. Surrogate keys are when “id” integer values are used to uniquely identify a row. This is a surrogate in that it’s not directly related to the data row.

In Rails, the surrogate primary key is usually a column called “id” with an integer type, populated by the database. In PostgreSQL, a sequence object usually provides the value.

What does this have to do with indexes? When we define our id column as the primary key column, this creates a primary key constraint on the table, and constraints have a supporting index. Primary key constraints enforce unique values, and the index helps the lookups performed to enforce that rule to run quickly.

CPKs, or multi-column primary keys, are primary keys where multiple columns uniquely identify a table row. This is a generic mechanism, so it’s up to you as to how you describe your CPK. You may choose to incorporate a surrogate value like the id integer, and combine that with a unique identifier for one of your customers, for example, a customer_id.

You may choose a CPK as a natural key composed of two foreign key columns that point to other tables. This structure is most common with join tables.

With that context in mind, let's arrive at what’s changing in Rails 7.2.

Currently, for the CPK feature, we use the query_constraints keyword on models that relate to other models, and supply a list of foreign key columns that refer to the foreign table primary key columns.

In Rails 7.2, this option changes from query_constraints to foreign_keys. Besides the parameter name change, some interesting discussions are underway about plans to repurpose the original query_constraints name by freeing it up.

If you’d like to play around with single-column surrogate keys, and compare them with composite or multi-column primary keys as a type of natural key, take a look at this Bookshop repo.

Here, we’ve got the models listed in the Rails API documentation for CPKs implemented as a single file Rails app. You can modify the code and database schema design and try out different alternatives.

To try out Rails 7.2 changes, edit the Gemfile portion within the bookshop.rb Ruby file. Use a 7.2 beta version by putting this change into the file, then running bundle install from your terminal:

gem 'rails', '~> 7.2.0.beta1'
Enter fullscreen mode Exit fullscreen mode

Development Containers and Databases in Rails 7.2

Ruby on Rails has always been a productivity-centric framework. One challenge that impacts the developer experience and productivity is creating and maintaining a development environment.

Dev containers are intended to fix that. They're used to create reproducible development environments that anyone on your team can run.

Besides reproducibility, another benefit for developers working on many Rails apps is the isolation of environment dependencies. Some operating system dependencies you use can be particularly challenging to install, or multiple versions might coexist. By isolating dependencies into a container, you can avoid that problem entirely.

Compared with Docker containers alone, dev containers add a “devcontainer.json” file to the mix. This can describe the text editor configuration and be shared on a team that uses the same editor with the same configuration.

Let’s generate a new dev container for bookshop:

gem install rails -v 7.2.0.beta1

rails new myapp --devcontainer
Enter fullscreen mode Exit fullscreen mode

Click “Reopen in devcontainer” in the bottom left of VS Code. You'll see “Starting Dev Container” while it starts up. Click “Show Log” to expand the terminal area, showing log progress. Here, you can tell whether containers are being downloaded and which stage they’re in.

The outer container can be viewed and administered using Docker Desktop.

The Rails app and its dependencies all run inside the container (you’ll find an instance of Redis, Selenium, and your Rails app). All the gems are installed too, including ones with native dependencies.

Try opening a terminal within VS Code, and, from there, run bin/rails server. Now, when you navigate to localhost:3000 in your browser, since there’s a local port mapping from 3000 into the container, you should see the generated Rails 7.2 app welcome page!

Database Transaction Enhancements, Solid Queue, and Active Job

Transactions are one of the fundamental concepts of relational databases. Databases are designed to support high concurrent access to shared resources, like table row data. For example, multiple clients might try to read and write to the same table row at once. The database consistently processes operations by using transactions with an isolated view of the data.

One of the problems in Rails apps comes when working with relational database transactions and then with another data store like Redis (via background processing with Sidekiq or other Active Job backends).

The issue is one of timing: ensuring data is committed to the relational database before any background work starts.

With Solid Queue — a database-backed queue management system — coming in Rails 8, ensuring transactionally consistent data and operational order is even more important. Transactional consistency errors will become more visible when there's a first-party database-backed queue system.

To prepare for that, what's changed in Rails 7.2? Active Job will now defer enqueuing background jobs until after a database transaction has been committed. This small change ensures no background job processing starts until the database transaction is committed.

Another change in 7.2 is that callbacks will be registered on database transactions.

For example, after_commit can be added within a transaction block to ensure that it runs after the transaction is committed.

Here's the example used in the release notes:

Article.transaction do |transaction|
  article.update(published: true)

  transaction.after_commit do
    # Do work after commit, like send a notification email
  end
end
Enter fullscreen mode Exit fullscreen mode

This approach ensures the code in the after_commit block runs after the article is updated.

New Active Support Instrumentation for Transactions

The Beta 3 version of Rails 7.2 was recently released, and it included new Active Support Instrumentation events we can configure for our applications.

One of these new events is called start_transaction.active_record, and it is triggered when database transactions or savepoints within transactions are started.

Check out the blog post You make a good point! — PostgreSQL Savepoints for more information on savepoints.

When database transactions finish, the event transaction.active_record event is emitted.

What can we do with these events? By creating an Active Support Subscriber that inspects these events and their payloads, we can gain a better understanding of database transaction and savepoint activity within Active Record.

Check out the commit code changes and documentation.

Wrapping Up

In this post, we looked at a few database-related features coming in Ruby on Rails 7.2. We covered the basics of CPKs and an important option name that's changing in 7.2. We also examined how we'll be able to run all of our application dependencies (including our databases) in a dev container.

Finally, we covered how database transactions and database-backed background job systems will tackle the challenge of write operations occurring in the expected order.

Besides those items, there's plenty more to read and learn about Rails 7.2. For example, Ruby 3.1 will become the new default minimum version, there will be support for jemalloc, RuboCop rules, a GitHub CI workflow, and support for progressive web apps (PWAs). To learn more, check out the Rails 7.2 release notes.

Thanks for reading!

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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player