新たなシリアライザActiveSupport::MessagePackのサポート
Rails 7.1では、新たなシリアライザとしてActiveSupport::MessagePackが利用できるようになりました。MessagePackは、msgpackに替わるシリアライザで、標準のRubyの型に加えて新たにTime、ActiveSupport::TimeWithZone、ActiveSupport::HashWithIndifferentAccessなどの型もシリアライズできます。また、JSONなどに比べてペイロードサイズが抑えられているので性能の向上を図れます。
MessagePackは、Active Supportのメッセージシリアライザ、Action Dispatchのクッキーシリアライザ、他にはキャッシュシリアライザとして指定できます。以下はActive Supportのメッセージシリアライザを既定のJSONとMessagePackで比較した例です。
% rails c
# BigDecimal、Time、Hash、HashWithIndifferentAccessからなるタプルを生成
> data = [5.to_d, Time.now, { sym: 1 }, { str: 2 }.with_indifferent_access]
# JSONをシリアライザとするMessageVerifierを生成
> verifier = ActiveSupport::MessageVerifier.new("secret", digest: "SHA512", serializer: :json)
# メッセージを生成しベリファイ。BigDecimalやTime、Hashのキーは文字列になる
> message = verifier.generate(data)
> verifier.verified(message)
=> ["5.0", "2023-12-09T07:28:33.663+09:00", {"sym"=>1}, {"str"=>2}]
# MessagePackをシリアライザとするMessageVerifierを生成
> verifier = ActiveSupport::MessageVerifier.new("secret", digest: "SHA512", serializer: :message_pack)
# BigDecimalやTime、Hashのキーがシンボルである場合も適切にシリアライズされる
> message = verifier.generate(data)
> verifier.verified(message)
=> [0.5e1, 2023-12-09 07:28:33.663841 +0900, {:sym=>1}, {"str"=>2}]
# 型も見てみる
> verifier.verified(message).map(&:class)
=> [BigDecimal, Time, Hash, ActiveSupport::HashWithIndifferentAccess]
既定のシリアライザは、application.rbファイルで以下のように設定できます。
config.active_support.message_serializer = :message_pack
config.action_dispatch.cookies_serializer = :message_pack
config.cache_store = :file_store, "tmp/cache", { serializer: :message_pack }
Active SupportのMessageEncryptorなどのクラスでも、個別にシリアライザの指定が可能です。
ActiveSupport::MessageEncryptor.new(secret, serializer: :message_pack)
明示的なテンプレート変数の指定
Rails 7.1では、テンプレートが受け取る変数を厳密に指定できるようになりました。既定では、localsオプションで指定される全ての変数を受け取りますが、テンプレートが受け入れる変数を制限したり、既定値を与えることができます。これにより、テンプレートが想定しない変数を渡されたり、変数が省略された場合の対応が可能になります。
変数は、テンプレートのマジックコメント構文にlocals:を記述して指定します。この場合、変数titleとview_countを受入れ、既定値はそれぞれ'No title'と0になります。
<%# locals: (title: 'No title', view_count: 0) -%> <h1>タイトル:<%= title %></h1> <p>ビュー数:<%= ciew_count %> 回</p> # => ActionView::Template::Error (undefined local variable or method `ciew_count'
コメントにない変数が渡されたり、既定値のない変数が省略されたりすると、Template::Errorが発生します。このように、テンプレートへの変数の引き渡しを厳密化できるので、より安全なテンプレートの利用が可能になります。
その他の便利な機能追加
最後に、Active ModelやActive Recordで追加された便利な機能を紹介します。
Validatorオプションの機能拡張
Active ModelのValidatorにおいて、幾つかのオプションの指定方法が拡張されました。
まず、文字列長のバリデータLengthValidatorで指定できる:withinオプション、:inオプションで範囲(Range)を指定する場合の書式が変更になっています。従来は開始値と終了値の双方が必要でしたが、Rails 7.1ではそのいずれかを省略できるようになりました(双方の省略は不可)。これにより、例えばvalidates_length_ofメソッドで「何文字以下であるか」「何文字以上であるか」といったバリデーションの指定が容易になります。
class User < ApplicationRecord
# nameは20文字まで(空文字列もOK)
validates_length_of :name, in: ..20
end
また、包含のバリデータInclusionValidatorのvalidates_inclusion_ofメソッド、除外のバリデータExclusionValidatorのvalidates_exclusion_ofメソッドにおいては、開始値のみが省略できるようになりました。それぞれ、繰り返しオブジェクトの中に値が含まれるか(いないか)を検証するメソッドです。
# birthが今日まではOK
validates_inclusion_of :birth, in: -> { (..Date.today) }
# birthが1900/1/1以降ならOK
validates_exclusion_of :birth, in: -> { (.."1899/12/31".to_date) }) }
この他、比較のバリデータComparisonValidatorのvalidates_comparison_ofメソッドなどにカスタムバリデータを指定する場合、ラムダ式を与えるときはレコードそのものを表す引数を省略できるようになりました。従来は、例えラムダ式中で参照されなくても、引数としては必要でした。これは、Procオブジェクトを与えるときとの互換性のためです。
# Rails 7.0まで
validates_comparison_of :birth, less_than_or_equal_to: ->(x) { Date.today }
↓
# Rails 7.1
validates_comparison_of :birth, less_than_or_equal_to: -> { Date.today }
whereメソッド引数のタプル受入れ
第1回で、Active Recordにおける複合主キーのサポートを紹介しました。そこでは、複合主キーのためにfindメソッドが主キーのタプルを受け入れることを紹介しましたが、それと同時にRails 7.1ではwhereメソッドにもタプル構文が導入されました。これにより、クエリに含まれるキーと値の対応が分かりやすくなります。
% rails c
> Book.create!(title: 'Rails Primer', author: 'Nao Yamauchi')
> Book.create!(title: 'Good Ruby', author: 'Shino Onodera')
> Book.where([:title, :author] => [['Rails Primer', 'Nao Yamauchi'], ['Good Ruby', 'Shino Onodera']])
SELECT "books".* FROM "books" WHERE ("books"."title" = ? AND "books"."author" = ? OR "books"."title" = ? AND "books"."author" = ?) LIMIT ? [["title", "Rails Primer"], ["author", "Nao Yamauchi"], ["title", "Good Ruby"], ["author", "Shino Onodera"], ["LIMIT", 11]]
キーのタプルに値のタプルを指定することで、値の組み合わせに一致するレコードのAND検索が容易です。値のタプルを複数指定すれば、それはOR検索となります。もともとは、複合主キーによる検索をwhereメソッドでも可能にする改変で、複合主キーをクエリに指定するときは以下のようにprimary_keyフィールドを使って記述できます。
% rails c > Order.create!(customer_id: 1, product_id: 2, quantity: 10) > Order.create!(customer_id: 2, product_id: 4, quantity: 20) > Order.where(Order.primary_key => [[1, 2], [2, 4]]).count => 2
まとめ
今回は、Ruby on Rails 7.1の新機能のうち、非同期キューや新しいJavaScriptランタイム環境などの機能強化について紹介しました。
Railsは、絶え間ない進化を成し遂げています。次の進化でも、改めてその概要をお伝えできればと思います。
