はじめに
Rubyを使用したOSSのWebアプリケーションフレームワーク、Ruby on Rails(以下Rails)の最新バージョンである7.1(以下Rails 7.1)が2023年10月5日にリリースされました。
Rails 7.1はデプロイ用のDockerfileの自動生成、複合プライマリキーのサポート、Active Recordの非同期クエリの機能追加、Trilogy MySQLアダプタのサポートなど、多くの機能追加が行われました。
本記事では、Railsの公式ブログやRailsガイド、GitHubのRailsプロジェクトのIssuesやPull Requestsの内容をもとに、Rails 7.1の主要な機能追加・変更点の紹介を行います。
※ 以前のバージョンのRailsの主要な新機能・機能追加・変更点については以下を参照してください。
注意点
Rubyのバージョン
Rails 7.1を動かすには、Ruby 2.7.0以上が必要になります。可能であれば、現時点で最新のRuby 3.2系列にアップグレードを行ってください。
Railsのサポート対象のバージョン
Rails 7.1のリリース後は、バグ修正の対象がバージョン7.1、セキュリティアップデートの対象が7.1、7.0、6.1系列となります。それより前のバージョンに関してはサポートは行われません。
参考
新機能・機能追加
Dockerfile(デプロイ用)の自動生成
アプリケーションをrails new
で新規作成した際に、本番環境デプロイ用のDockerfileをはじめとした、Docker関連のファイルを自動生成するようになりました。
(--skip-docker
オプションを渡すことで、Docker関連のファイルの生成をスキップすることができます。)
以下のコマンドでDockerイメージのビルドとボリューム作成、実行を行うことができます。
$ docker build -t app .
$ docker volume create app-storage
$ docker run --rm -it -v app-storage:/rails/storage -p 3000:3000 --env RAILS_MASTER_KEY=<config/master.keyの値> app
※ 元々この機能は本番環境へのデプロイを想定していて、開発環境で使用することを意図していないことに注意してください。(開発環境ではHTTPSの設定や開発用のRubyGemsパッケージのインストールなどカスタマイズが必要です。)
参考
複合プライマリキー
複合プライマリキーがサポートされ、マイグレーションや、Active RecordのクエリメソッドなどRailsのアプリケーション全般で使用できるようになりました。
DBに複合プライマリキーを使用したテーブルを作成するには、以下のようにマイグレーションのchange_table
のprimary_key:
オプションにカラム名の配列を渡します。
class CreateProducts < ActiveRecord::Migration[7.1]
def change
create_table :products, primary_key: [:store_id, :sku] do |t|
t.integer :store_id
t.string :sku
t.text :description
end
end
end
find
やwhere
のActive Recordのクエリメソッドでも、複合プライマリキーに対応する引数を指定することができるようになっています。
Product.find([3, "XYZ12345"]) # store_id: 3, sku: "XYZ12345"を指定
Product.where(Product.primary_key => [[1, "ABC98765"], [7, "ZZZ11111"]]) # store_id: 3, sku: "XYZ12345"とstore_id: 7, sku: "ZZZ11111"
first
メソッドでは、2つのプライマリキーを組み合わせた順番で並び替えを行い、その最初のレコードを取得するようになっています。
Product.first # SQLで実行: SELECT * FROM products ORDER BY products.store_id ASC, products.sku ASC LIMIT 1
モデルの関連で、従来のid
ではなく、複合プライマリキーを使用するには、has_many
やbelongs_to
などのオプションでquery_constraints
を使用します。
class Author < ApplicationRecord
self.primary_key = [:first_name, :last_name]
has_many :books, query_constraints: [:first_name, :last_name]
end
class Book < ApplicationRecord
belongs_to :author, query_constraints: [:author_first_name, :author_last_name]
end
他にも、フォームへルパやルーティングなどでもサポートされます。詳しい内容に関しては、以下のRails guidesの記事を参照してください。
参考
ActiveRecord::Base.normalizes
によるモデルの属性の正規化
メールアドレスの小文字化、電話番号のハイフン削除などの正規化を、モデル内に以下のようにnormalizes
で指定することで、属性に値がアサインされたり、更新された際に行うことができるようになりました。
class User < ApplicationRecord
normalizes :email, with: -> email { email.downcase }
normalizes :tel, with: -> tel { tel.delete("-") }
end
上記のモデル定義を使用した場合、以下のようにemail
とtel
の正規化が行われます。
user = User.new(email: 'JOHN@example.com', tel: '01-2345-6789')
=> #<User:0x0000000108142a68 id: nil, email: "john@example.com", tel: "0123456789", created_at: nil, updated_at: nil>
normalizes
を指定している場合、Active Recordのwhere
、exists?
に正規化前の値を渡すと、正規化されてSQLが実行されます。
> User.where(email: 'JOHN@example.com')
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? /* loading for pp */ LIMIT ? [["email", "john@example.com"], ["LIMIT", 11]]
> 9> User.exists?(email: 'JOHN@example.com')
User Exists? (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "john@example.com"], ["LIMIT", 1]]
参考
Active Recordに非同期クエリのメソッドが追加
Active Recordに、以下のasync_
で始まる各種の非同期クエリのメソッドが追加されました。
async_count
async_sum
async_minimum
async_maximum
async_average
async_pluck
async_pick
async_ids
async_find_by_sql
async_count_by_sql
これらのメソッドは呼出後に、クエリの実行状態を持つActiveRecord::Promise
のオブジェクトを返し、実行が完了している場合は、#value
メソッドで値を取得することができます。
promise = Post.where(published: true).async_count
promise.value # => 10
参考
- Active Record API for general async queries by casperisfine · Pull Request #44446 · rails/rails
- ActiveRecord::Promise
Trilogy MySQLアダプタのサポート
GitHubによってOSS化された、MySQL互換のDBに対するアダプタであるTrilogyがサポートされるようになりました。
trilogyは従来のmysql2アダプタに比べて高速で軽量に動作し、GitHub以外にもShopifyなどで利用されています。(詳しくは参考にあげたページを参照してください。)
Railsアプリケーションではconfig/database.yml
で以下のようにadapter: trilogy
を指定することで、従来のmysql2アダプタから移行することができます。
development:
adapter: trilogy
database: blog_development
pool: 5
参考
- Introduce adapter for Trilogy, a MySQL-compatible DB client by adrianna-chang-shopify · Pull Request #47880 · rails/rails
- Introducing Trilogy: a new database adapter for Ruby on Rails - The GitHub Blog
- Benchmarking Trilogy : Is it really the future of Rails ?
ジョブの一括エンキュー(perform_all_later
)
複数のジョブを一括でエンキューする、perform_all_later
がActiveJob
のクラスメソッドとして追加されました。
このメソッドを使用することで、Sidekiqなど、Active Jobのアダプタでenqueue_all
メソッドを使用できるジョブ実行システムの場合では、個別にジョブをエンキューすることなく効率よくジョブを実行できるようになっています。(Sidekiqの場合はpush_bulkで処理が行われます。)
この場合Active Jobのエンキュー用のコールバックが実行されないことに注意してください。
また、enqueue_all
を使用できない場合は、ループで個別のジョブをエンキューします。
参考
- Add
perform_all_later
to enqueue multiple jobs at once by Mangara · Pull Request #46603 · rails/rails - ActiveJob.perform_all_later
ActiveSupport::MessagePack
Rubyオブジェクトをシリアライズする際に従来はJSONやMarshalのシリアライザが使用されていましたが、より高速で、データが軽量になるmsgpack gemを使用したActiveSupport::MessagePackが利用できるようにようになりました。
Railsアプリケーションの各種設定でも、以下の例のように:message_pack
が指定できるようになりました。
config.active_support.message_serializer = :message_pack
config.action_dispatch.cookies_serializer = :message_pack
config.cache_store = :file_store, "tmp/cache", { serializer: :message_pack }
参考
config.autoload_lib
/ config.autoload_lib_once
Railsアプリケーションのlib/
ディレクトリ以下にあるクラスやモジュールを自動読み込みする設定が追加されました。
以下のように指定することで、lib/assets
、lib/tasks
、lib/generators
以外のlib
ディレクトリ内のファイルにある、モジュールやクラスが起動時に自動読み込みされ、起動中にファイルの変更を行った場合は(モデルやコントローラと同様に)リロードされます。
config.autoload_lib(ignore: %w[assets tasks generators])
autoload_lib
の代わりにconfig.autoload_lib_once
を使用した場合は、起動時に1度だけ読み込まれ、変更後はリロードされません。
参考
JSONとHTMLのresponse.parsed_body
でパターンマッチングが利用できるように
ActionDispatch::IntegrationTest
を使用した際に、response.parsed_body
に対して以下のようにパターンマッチングを使用したテストを書くことができるようになりました。
以下はコンテンツがJSONの場合で、Rails 6からresponse.parsed_body
がActiveSupport::HashWithIndifferentAccess
またはArray
を返すようになっており、それに対してパターンマッチングを行なっています。
(以下の例ではMinitestに追加されたassert_pattern
でテストを行なっています。)
get "/posts/42.json" # JSONで{ "title": "Title" }を返す
assert_pattern { response.parsed_body => [{ title: /title/i }] } # マッチする
また、コンテンツがHTMLの場合は、Rails 7.1でresponse.parsed_body
がNokogiri::HTML5::Document
を返すようになり、それに対してパターンマッチングを行えるようになっています。
get "/posts" # <main><h1>Some main content</h1></main>を含むHTMLを返す
assert_pattern { html.at("main") => { content: "Some main content" } } # マッチする
参考
変更点
自動読み込みのパスがデフォルトで$LOAD_PATH
に追加されなくなった
Rails 7.1から自動読み込みパスを$LOAD_PATH
に追加しないようになりました。これは、Rails 7.0からZeitwerkモードでの起動が必須になり、Zeitwerkは内部的に絶対パスを使用していて、require
やrequire_relative
のように$LOAD_PATH
に登録された情報は使用しないからのようです。
(lib
ディレクトリはZeitwerk外からも使用されるため、$LOAD_PATH
に追加されます。)
参考