Ruby on Rails 7の主要な新機能・機能追加・変更点

## はじめに

Ruby on Rails（以下Rails）の最新バージョンである7.0（以下Rails 7）が2021年12月15日に[リリース](https://rubyonrails.org/2021/12/15/Rails-7-fulfilling-a-vision)されました。

Rails 7では、JavaScriptフロントエンドの大幅な刷新、CSS BundlingによるTailwind CSSやPostCSSのサポートを含めた、メジャーバージョンアップグレードにふさわしい機能強化が行われています。

本記事では、[Railsの公式ブログ](https://rubyonrails.org/blog/)や[Railsガイド](https://edgeguides.rubyonrails.org/active_record_encryption.html)、GitHubのRailsプロジェクトの[Issues](https://github.com/rails/rails/issues)や[Pull Requests](https://github.com/rails/rails/pulls)の内容をもとに、Rails 7の主要な新機能・機能追加・変更点の紹介を行います。

※ 以前のバージョンのRailsの主要な新機能・機能追加・変更点については以下を参照してください。

* [Ruby on Rails 6の主要な新機能・機能追加・変更点](https://qiita.com/ryohashimoto/items/622c3bcfb3336cb9317e)
* [Ruby on Rails 6.1の主要な新機能・変更点](https://qiita.com/ryohashimoto/items/7caf905389ccb5d7d8fb)

## 注意点

### Rubyのバージョン

Rails 7を動かすには、Ruby 2.7.0以上が必要になります。可能であれば、Ruby 3.0系列にアップグレードを行ってください。Ruby 3.1については、Rails 7.0.0では非対応ですが、今後サポートされる予定です。

### セキュリティアップデート

Rails 7のリリース後は、Railsのバージョン7.0系列と、6.1系列がセキュリティアップデートの対象となります。それより前の6.0系列、5.2系列についても深刻なセキュリティ問題に対する修正は行われますが、サポート期間が短くなっていますので、注意してください。

**参考**

* [Maintenance Policy for Ruby on Rails](https://guides.rubyonrails.org/maintenance_policy.html)

## 新機能

### JavaScriptフロントエンドの刷新（ESM importmap / Hotwire / Stimulus / Turbo）

ES6（ECMAScript 6）/ ESM（ECMAScript modules） 、HTTP/2、[import maps](https://wicg.github.io/import-maps/)を使用することで、Node.jsをインストールせずにモダンなJavaScriptをRailsアプリケーションで利用することが可能になりました。

以前のRailsのバージョンでは、Webpacker経由でWebpackを使用してJavaScriptファイルを依存するライブラリと共に結合・処理してその結果のファイルを使用していました。

最近では、ES6 / ESMのブラウザサポートによってJavaScriptのコンパイル・結合が不要になり、HTTP/2で配信することで小さな複数のファイルのままでもネットワーク経由での読み込み速度が落ちなくなりました。また、import mapsを使用することでライブラリの名前解決を行なって直接CDNなどから取得できるようになりました。

Rails 7では上記の技術的進歩を踏まえた上で、標準ではWebpacker / Node.jsを使用しなくても良いようになっています。（以前のように使用することも可能です。）

デフォルトでは、[Hotwire](https://github.com/hotwired/hotwire-rails)、[Stimulus](https://stimulus.hotwired.dev/)、[Turbo](https://turbo.hotwired.dev/)といったライブラリがインストールされ、リッチなUI/UXを実現するSPA（シングルページアプリケーション）を実装する際に利用することができます。（Webpackerはデフォルトではインストールされなくなりました。）

* [Modern web apps without JavaScript bundling or transpiling](https://world.hey.com/dhh/modern-web-apps-without-javascript-bundling-or-transpiling-a20f2755)

### CSS BundlingによるTailwind CSSやPostCSSなどのサポート

[cssbundling-rails](https://github.com/rails/cssbundling-rails)というGemをインストールすることで、 [Tailwind CSS](https://tailwindcss.com/)、 [Bootstrap](https://getbootstrap.com/)、 [Bulma](https://bulma.io/)、[PostCSS](https://postcss.org/)、 [Dart Sass](https://sass-lang.com/) を使用して、CSSファイルを結合・処理し、Railsのアセットパイプラインに載せることが可能になりました。

使用するCSSライブラリは、アプリケーションの新規作成時に以下のようにして、`--css`オプションで指定することができます。例えば、Tailwind CSSを指定するには以下のようにします。

```
rails new myapp --css tailwind
```

また、既存のアプリケーションでは、Gemfileに以下の行を追加して、

```ruby:Gemfile
gem 'cssbundling-rails'
```

以下のコマンドを実行することで、インストールと初期設定を行うことが可能です。

```
$ bin/bundle install
$ bin/rails css:install:tailwind
```

これにより、`tailwind.config.js`などの設定ファイルが生成されます。

開発時には`yarn build:css --watch`コマンドを実行することで、リアルタイムでCSSファイルの処理を行いながらブラウザで結果を確認することができます。デプロイ時には、`assets:precompile`実行時にCSSファイルの処理が行われます。

**参考**

* [GitHub - rails/cssbundling-rails: Bundle and process CSS in Rails with Tailwind, PostCSS, and Sass via Node.js.](https://github.com/rails/cssbundling-rails)

### Active Recordの属性の暗号化

アプリケーション内でActive Recordの属性を暗号化した状態でDBに保存したり、読み出しを行うことができるようになりました。これによって、意図せずにユーザーの機密情報をアプリケーションのログや外部に公開してしまわないように、属性レベルできめ細かい制御が行うことができます。

設定を行うには`bin/rails db:encryption:init`のコマンドを実行して、ランダムなキーを生成し、その結果を`bin/rails credentials:edit`で開いたエディタの画面に貼り付けて保存します。

```
$ bin/rails db:encryption:init
Add this entry to the credentials of the target environment: 

active_record_encryption:
  primary_key: 5jQ2IWs63zd7Memm3dQ4O36Sefo32WTF
  deterministic_key: H7THwnF0ZU1LSYBZsENx7gRywujlb01j
  key_derivation_salt: 2Rbbe0FOLtbQWvvUanvETAIcwRFHgIs9
```

暗号化された属性を定義するには、以下のようにモデルで、`encrypts`の後に属性名を記述します。

```ruby
class User < ApplicationRecord
  encrypts :name
end
```

暗号化を指定した属性は、他の属性と同様に保存と読み出しが可能ですが、DB内には暗号化された状態で保存されます。

```
> user = User.new(name: 'John')
> user.save
INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "{\"p\":\"Fz3+bYdP\",\"h\":{\"iv\":\"2kWLG6TXjNmBy1Is\",\"at\":\"eUn3MOTa4qDR93ABt3rGSA==\"}}"], ["created_at", "2021-12-25 02:55:14.599457"], ["updated_at", "2021-12-25 02:55:14.599457"]]
> user = User.find_by(name: 'John')
SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "{\"p\":\"NzM54tpR\",\"h\":{\"iv\":\"947dl7Uq1O8EuXhE\",\"at\":\"FKlBF3hEO6QKJF3KSZMl4A==\"}}"], ["LIMIT", 1]]
```

**参考**

* [Active Record Encryption — Ruby on Rails Guides](https://edgeguides.rubyonrails.org/active_record_encryption.html)

### SQLクエリにコメントを自動で付与できるように

SQLのクエリに自動的にアプリケーション側の情報（Active Recordのメソッドが呼び出された、コントローラやジョブなどを含む）をコメントとして付与する機能が追加されました。

元々、[Marginalia](https://github.com/basecamp/marginalia)というBasecampで使用されていたGemの機能が、[ActiveRecord::QueryLogs](https://api.rubyonrails.org/classes/ActiveRecord/QueryLogs.html)に移植された形です。

以下のようにRailsの設定で出力する情報をタグとして設定できます。

```ruby
config.active_record.query_log_tags = [ :application, :controller, :action, :job ]
```

これによって例えば以下のコントローラを使用していて、`GET /booksに`アクセスした際に

```ruby
class BooksController < ApplicationController
  def index
    @books = Book.all
  end
end
```

以下のようなSQLのログがコメントとともに出力されるようになります。

```sql
SELECT * FROM books /*application:MyApp;controller:books;action:index*/
```

この機能を使うことで、例えばスロークエリが発生した際に、SQLのログを見ただけでアプリケーション側のどの部分の処理でそのクエリが発生したかを特定することができるようになります。
出力できるタグの詳細や他の設定に関しては、以下を参考にしてください。

**参考**

* [ActiveRecord::QueryLogs](https://api.rubyonrails.org/classes/ActiveRecord/QueryLogs.html)
* [Add Marginalia to Rails, via QueryLogs by keeran · Pull Request #42240 · rails/rails · GitHub](https://github.com/rails/rails/pull/42240)

### 非同期のSQLクエリによる結果の取得

`ActiveRecord::Relation#load_async`を使用することによって、非同期でSQLクエリを実行し、結果を取得することができるようになりました。

これによって、例えば以下のコードのようにコントローラの同じアクション内で、複数の相互に依存しないクエリを並列に実行することができるようになり、レスポンスを返す時間を短縮することができるようになります。

```ruby
def index
  @articles = Article.per(params[:per]).page(params[:page]).load_async
  @categories = Category.active.load_async
end
```

**参考**

* [Implement Relation#load_async to schedule the query on the background thread pool by casperisfine · Pull Request #41372 · rails/rails · GitHub](https://github.com/rails/rails/pull/41372)


## 機能追加

### バルクインサートで自動的にタイムスタンプを記録できるように

Active Recordの`insert_all`や`upsert_all`メソッドなどでバルクインサート・バルクアップサートでレコードを作成、更新する際に、`created_at`や`updated_at`などのタイムスタンプを自動でセットできるようになりました。

この機能を使用するには、以下の例のようにメソッドの`record_timestamps`オプションに`true`を渡す必要があります。

```ruby
Article.insert_all([{ title: "What's new in Rails 7" }], record_timestamps: true)
```

**参考**

* [Set timestamps on `insert_all`/`upsert_all` record creation by sambostock · Pull Request #43003 · rails/rails · GitHub](https://github.com/rails/rails/pull/43003)


### Active Recordに`in_order_of`メソッドが追加

レコードを指定した順番で取得できる`in_order_of`メソッドがActive Recordに追加されました。
以下の例では、ユーザーをIDが1、5、3の順番になるように取得しています。

```ruby
User.in_order_of(:id, [1, 5, 3])
# SELECT "users".* FROM "users" ORDER BY FIELD("users"."id", 1, 5, 3)
```

**参考**

* [Add `ActiveRecord::QueryMethods#in_order_of`. by kddnewton · Pull Request #42061 · rails/rails · GitHub](https://github.com/rails/rails/pull/42061)
* [ActiveRecord::QueryMethods#in_order_of](
https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-in_order_of)

### Active Recordに`structurally_compatible?`メソッドが追加

Active Recordのリレーションが他のリレーションとデータの構造が同じかどうかチェックする`structurally_compatible?`メソッドが追加されました。このメソッドを使用することで、`and`メソッドや`or`メソッドを使用する前にエラーが起きないかどうかを事前にチェックすることができます。

```ruby
Post.where("id = 1").structurally_compatible?(Post.where("author_id = 3"))
# => true
Post.joins(:comments).structurally_compatible?(Post.where("id = 1"))
# => false
```

**参考**

* [Add `ActiveRecord::Relation#structurally_compatible?`. by kddnewton · Pull Request #41841 · rails/rails · GitHub](https://github.com/rails/rails/pull/41841)
* [ActiveRecord::QueryMethods](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-structurally_compatible-3F)

### ActiveModel::APIの追加

`ActiveModel::Model`の実装を新しくできた`ActiveModel::API`に移し、Active Recordのモデルのように振る舞うもの以外でも使いやすいようにしました。内部的な変更のため、`ActiveModel::Model`はこれまで通り使用することができます。

**参考**

* [Introduce ActiveModel::API by p8 · Pull Request #43223 · rails/rails · GitHub](https://github.com/rails/rails/pull/43223)

### `weekday_options_for_select`ヘルパメソッドの追加

曜日選択のoptionタグを生成する`weekday_options_for_select`タグが追加されました。

```ruby
weekday_options_for_select
# => "<option value=\"Sunday\">Sunday</option>\n<option value=\"Monday\">Monday</option>\n
# <option value=\"Tuesday\">Tuesday</option>\n<option value=\"Wednesday\">Wednesday</option>\n
# <option value=\"Thursday\">Thursday</option>\n<option value=\"Friday\">Friday</option>\n
# <option value=\"Saturday\">Saturday</option>"
weekday_options_for_select(nil, index_as_value: true)
# => "<option value=\"0\">Sunday</option>\n<option value=\"1\">Monday</option>\n
# <option value=\"2\">Tuesday</option>\n<option value=\"3\">Wednesday</option>\n
# <option value=\"4\">Thursday</option>\n<option value=\"5\">Friday</option>\n
# <option value=\"6\">Saturday</option>"
```

国際化にも対応しており、[rails-i18nのja.ymlファイル](https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml)を使用すれば日本語の曜日選択のoptionタグを生成できるようです。

**参考**

* [Add `weekday_options_for_select` method by DRBragg · Pull Request #42979 · rails/rails · GitHub]( https://github.com/rails/rails/pull/42979)

### データベースの設定オプションでタスクをオフにできるように

データベース設定ファイルのオプションで`database_tasks: false`を使用することで、そのデータベースに対するタスクをオフにできるようになりました。

複数データベースを使用していて、例えば読み取り専用のDBに対してマイグレーションを実行したくない場合などで便利に使えると思います。

例:

```yaml
production:
  primary:
    database: my_database
    adapter: mysql2
  animals:
    database: my_animals_database
    adapter: mysql2
    database_tasks: false
```

**参考**

* [Add database config option `database_tasks: false` by westonganger · Pull Request #42794 · rails/rails · GitHub](https://github.com/rails/rails/pull/42794)

## 変更点

### `button_to`がHTTPメソッドとしてPATCHを使用するように

以前のバージョンでは、`button_to`タグはHTTPメソッドとして、POSTを使用するようになっていましたが、Rails 7からはPATCHを使用するようになりました。以前の挙動に合わせるには、`button_to`メソッドのオプションとして、`method: :post`を渡す必要があります。

**参考**

* [Upgrading Ruby on Rails — Ruby on Rails Guides](https://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#actionview-helpers-urlhelper-button-to-changed-behavior)

### SassとCSSジェネレータの非標準化

最近ではCSSフレームワークを使うようになっていて、Sassは使用されなくなってきているので`sass-rails` Gemが標準ではGemfileに含まれないようになりました。

自動生成されるCSSファイルも拡張子が.scssではなく.cssのファイルになっています。また、モデルごとにCSSファイルを生成しないようになりました。

**参考**

* [Remove default reliance on Sass and CSS generators by dhh · Pull Request #43110 · rails/rails · GitHub](https://github.com/rails/rails/pull/43110)

### Springの非標準化

`spring` Gemが標準ではGemfileに含まれないようになりました。

マシンが高速な場合はSpringによる高速化の恩恵を受けることが少なくなったためとのことです。

**参考**

* [Remove spring as a default installation option by dhh · Pull Request #42997 · rails/rails · GitHub](
  https://github.com/rails/rails/pull/42997)

### Zeitwerkモードでの起動が必須に

Rails 6.0と6.1では、ファイルの自動ロードのモードとして、`zeitwerk`モードと`classic`モードを選択することができましたが、Rails 7では`zeitwerk`モードのみ選択できるようにして、`classic`モードを廃止しました。

これに伴い、設定の`config.autoloader=`が削除されており、また、`classic`モードで使用していた`ActiveSupport::Dependencies`が削除されました。

**参考**

* [Autoloading in Rails 7, get ready! | Riding Rails](https://weblog.rubyonrails.org/2021/9/3/autoloading-in-rails-7-get-ready/)

### Byebugの代わりにruby/debugを使用するように

以前のバージョンでは、デバッグ用のGemとして`byebug`を使用していましたが、Rails 7ではRuby標準の`debug`が標準でGemfileに含まれるようになりました。

**参考**

* [Depend on ruby/debug, replacing Byebug by fxn · Pull Request #43187 · rails/rails · GitHub](https://github.com/rails/rails/pull/43187)

### ActiveSupport::DigestがSHA-256を使用するように

ダイジェスト値を生成する際にこれまでアルゴリズムとしてSHA-1を使用していたのが、SHA-256に変更になりました。キャッシュのキーなど様々な箇所で影響があるので、以前の挙動のままにしたい場合は、以下のように設定を行います。

```ruby
config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA1
```

**参考**

* [Change the default digest for new apps to SHA256 by dbussink · Pull Request #41043 · rails/rails · GitHub](https://github.com/rails/rails/pull/41043)

* [Allow configuration of the digest class used in the key generator by dbussink · Pull Request #40770 · rails/rails · GitHub](https://github.com/rails/rails/pull/40770)

### Active Storageの画像処理でlibvipsを使用するように

Active Storageで画像処理に使われるGemで、以前のバージョンでは`mini_magick`が使用されていましたが、Rails 7からは`vips`が標準になりました。

**参考**

* [Make vips the default variant processor for new apps by brenogazzola · Pull Request #42744 · rails/rails · GitHub](https://github.com/rails/rails/pull/42744)