Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ: Rails 7.2でメンテナンスポリシー更新、書籍『Ruby on Railsパフォーマンスアポクリファ』ほか(20240819)

こんにちは、hachi8833です。Railsガイドも先週7.2に更新完了しました↓。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やX.comでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙏

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Rails: 先週の改修(Rails公式ニュースより)

🔗 Railsのメンテナンスポリシーが変更された

主な変更:

  • リリースのメンテナンスは事前定義済みの固定期間に基づいて行われる。バグ修正は1年間、セキュリティ修正は2年間。

  • 「重大なセキュリティ問題」と「通常のセキュリティ問題」の区別を廃止した。

  • npmバージョニングを更新し、プレリリースの-区切りを使わないようにした。

サポート対象となる全リリース、およびサポート終了日については今後https://rubyonrails.org/maintenanceに追加される。https://rubyonrails.org/securityも自分が更新する予定。
同PRより

参考: Ruby on Rails のメンテナンスポリシー - Railsガイド


つっつきボイス:「お、メンテナンスポリシーの変更は把握しておかないといけないヤツですね」「重大なセキュリティ問題と通常のセキュリティ問題という区分がなくなったのね」

「ポリシーの変更は一足先にRailsガイドにも反映して、その後で以下の文言も追加してもらいました↓」

訳注:2024年8月に更新された本メンテナンスポリシーは、それ以降のリリースにのみ適用されます。したがってRails 6.1EOL旧メンテナンスポリシーに沿って2024年10月1日、Rails 7.0は2025年4月1日、Rails 7.1は2025年10月1日のままとなります。(» 原文を見る)
Ruby on Rails のメンテナンスポリシー - Railsガイドより

「従来はガイドに書かれていたサポート期間の情報が、今後はhttps://rubyonrails.org/maintenanceに表示されるようになったようです」「サイトが更新中なのか、まだセキュリティサポート期間が空欄ですね(編注: その後更新が終わって表示されました↓)」


Ruby on Rails — Maintenance policyより

「ちょうど今Rails 6.1のアプリをアップデートしているんですけど、6.1のサポートは今年の10月でおしまいか〜😅」「お疲れさまです」「Rails 3.x系あたりのアップデート依頼が来るのは今でも珍しくありませんね」「Rails 7.0のEOLは来年10月って、もうそんなに先の話じゃないですね」

「ところで、以下のendoflifeみたいに有志がやっているサポート終了情報は、公式情報に追従しきれなかったりとかで、たまに古かったり誤っていたりすることもありますよね↓」「そうなんですよね...」「だからこそ公式がEOL情報をわかりやすく出してくれるととてもありがたい」

参考: Ruby on Rails | endoflife.date

「現実には古いバージョンのまま動き続けているアプリはたくさんあるわけですけど、フレームワークがすべてのバージョンをサポートするのは負担が大きすぎるし進化も遅れるしで無理なので、こうやってサポート終了までの期間を誤解されにくい形で明確に示すのが重要になってきますね」

「アプリを普段から最新に保っていればアップデートはそれほど大変ではないはずなんですけど、数年放置していたりすると一気につらくなってきますよね」「たいていサードパーティgemでつらくなる」「その間にアプリを知っている開発者がいなくなっていたりするともう大変ですよね」「そういうアプリはアップグレードの調査だけで1か月かかって、テストコードもたくさん更新しなければならなくなって...と高く付きがち」

Railsのアップグレードを成功させるための知見リスト(翻訳)

🔗 authenticationジェネレータにパスワードリセットを追加

パスワードリセットの基本的なフロー(署名済みIDをメーラーで知らせるため)。
これはオプトアウト可能にすべき。

残りの作業:

  • [x] 手動ルーティングのないPOSTで署名済みIDのトークンパラメータが認識されない問題の修正
    同PRより

つっつきボイス:「DHHによる改修です」「先週追加されたsessionジェネレータ(ウォッチ20240807)の改修のようですね」「ちなみにその後以下でauthenticationジェネレータにリネームされたそうです↓」「この名前の方が適切でしょうね」「今はシンプルだけど、こうやってサポートを追加していくとだんだんDeviseみたいになっていったりして😆」

🔗 has_secure_passwordにデフォルトのパスワードリセット用トークンジェネレータを追加

  • has_secure_passwordを使っている場合に、パスワードリセットトークン用のデフォルトのトークンジェネレータを追加。
class User < ApplicationRecord
  has_secure_password
end

user = User.create!(name: "david", password: "123", password_confirmation: "123")
token = user.password_reset_token
User.find_by_password_reset_token(token) # userを返す

# 16分後...
User.find_by_password_reset_token(token) # nilを返す

# トークン失効のためActiveSupport::MessageVerifier::InvalidSignatureが発生する
User.find_by_password_reset_token!(token)

DHH
同Changelogより


つっつきボイス:「これもDHHによる改修です」「パスワードをリセットしてfind_by_password_reset_tokenを取れるようになった: has_secure_passwordなどのRails組み込みの認証系機能が昔より強力になってきていて、authenticationジェネレータのサポートが進む流れでパスワードリセットトークン処理も追加されたということですね」「こういう作り込みに手間がかかったので以前はDeviseをインストールすることが多かったんですが、こうなってくると無理にDeviseをインストールしなくてもいいのではという気持ちになってきそう」「そういえばEvil Martiansもhas_secure_passwordでかなりやれると書いていましたね」

参考: 1.12 SecurePasswordモジュール -- Active Model の基礎 - Railsガイド

「かつてDeviseのような認証系gemが重宝された理由のひとつは、よくあるログインや今回のようなパスワードリセットなどの機能もビューごとまとめて生成してくれたからなんですが、昨今はRailsをAPIサーバーにして直接Railsのビューをユーザーに出さないケースも増えてきたので、とりあえずDeviseをインストールするという必然性はかつてほどでもなくなりつつある感じ」「なるほど」「Deviseはこれまでデファクトスタンダードでしたけど、こうやってRails組み込みの認証機能が充実してくると、新規プロジェクトではDeviseを使わないことも視野に入れるようになってくるかもしれませんね」「Deviseで苦しむ人多かったですもんね」

🔗 アプリを起動してすぐ終了するbin/rails bootコマンドを追加

自分は、ベンチマークの実行や、ブートロジックの理解のためにアプリケーションを頻繁に起動していることに気づいた。
こうした作業用に、以下のようなrunnerコマンドをよく使っている。

bin/rails r 1

これにより、アプリケーションが起動し、既存のrunnerフックが呼び出され、整数リテラル1で構成したRubyプログラムが実行される。

自分は長年このトリックを便利に使ってきたが、これは間接的である。「アプリケーションを起動して他に何もしない」という厳密な意味を持つ、もっとシンプルな方法が今までなかった。

そこでこの新しいbootコマンドを作った。
同PRより


つっつきボイス:「Rails 7.2 RC1が出ているので今回の改修の多くは次のRails 8向けの改修になるはずですが、このbootコマンドは7.2に入りました」「ここではローカルでの実験用にRailsアプリを起動して終了するコマンドが欲しいということだけど、CIやコンテナイメージのビルドなんかで正常に起動と終了することだけを確認するようなシチュエーションにも使えそう: 特にデプロイ可能なコンテナをビルドするときなんかは、さんざん時間をかけてビルドしたのに実は動かなかったみたいな状況は避けたいので、ビルド前のチェックなんかに使えるかも👍」「bootという名前、もっといいのがありそうな気がしなくもないですね🤔」

🔗 check_boxcheckboxにリネーム

  • check_box*メソッドをcheckbox*にリネーム

従来のメソッド名もエイリアスとして引き続き利用可能。

Jean Boussier
同Changelogより


つっつきボイス:「以下も同趣旨のプルリクだそうです↓」「命名を従来のアンダースコア_区切りからアンダースコアなしのメソッド名に統一するということですね」

「HTMLでは<textarea>という要素名があるので、そちらに寄せるのはわかるしそうした方がよさそう」「checkboxはちょっと違和感あるかも?」「こっちは<input type="checkbox">というふうに属性なので要素名ほど切実ではなさそうですけどね」「エイリアスで新旧どちらも使えるならOK」「こだわりないのでlintが自動修正してもOK」

🔗 Dockerのビルド実行時に警告が出たらエラーにするようになった

07/29に、DockerにDockerビルドチェックという機能が導入された

デフォルトでは、Dockerビルドで警告が発生してもビルドは失敗しない(ゼロ以外の終了コードを返す)。警告時にエラーを発生させるには、Dockerfileに# check=error=true宣言を追加する必要がある。

また、Dockerの公式ガイドの宣言サンプルにはスペースがないので、こちらもスペースなしにした。
同PRより


つっつきボイス:「Dockerfileに# check=error=trueを記述するとビルド時の警告をエラー扱いするのか」

# railties/lib/rails/generators/rails/app/templates/Dockerfile.tt
-# syntax = docker/dockerfile:1
+# syntax=docker/dockerfile:1
+# check=error=true

「ちょうど最近Dockerのこのあたりの新機能が社内でも話題になってましたね: たとえばdocker build . --checkするとビルドしなくなるのでチェックがめちゃくちゃ速くなるとか、docker --debug buildでデバッグビルドできるようになったとか↓」「なるほど」

参考: Docker ビルドチェックの紹介: ベストプラクティスによるDockerfileの最適化 | Docker

🔗 モデルでhuman_attribute_nameの訳文が見つからない場合にエラーを発生するよう改修

  • ActiveModel::Translation用の読み込みフックactive_model_translationを追加

Shouichi Kamiya

  • ActiveModel::Translationraise_on_missing_translationsオプションを追加

このオプションを設定すると、指定の属性の訳文が見つからない場合にhuman_attribute_nameがエラーを発生するようになる。

# ActiveModel::Translation.raise_on_missing_translations = false
Post.human_attribute_name("title")
=> "Title"

# ActiveModel::Translation.raise_on_missing_translations = true
Post.human_attribute_name("title")
=> Translation missing. Options considered were: (I18n::MissingTranslationData)
    - en.activerecord.attributes.post.title
    - en.attributes.title

            raise exception.respond_to?(:to_exception) ? exception.to_exception : exception
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Shouichi Kamiya

同Changelogより

config.i18n.raise_on_missing_translations = trueになっていると、訳文が見つからないというエラーがコントローラとビューで発生するが、モデルではエラーが発生しない。
このプルリクは、raise_on_missing_translationsがtrueの場合にモデルでエラーになるよう変更する。
同PRより


つっつきボイス:「Railsのi18nは、今まではコントローラやビューでは訳文が見つからないときにエラーをraiseしてたけど、モデルではしてなかったのを修正したのね」「それは修正すべき」「見つからない場合は基本的にraiseでいいと思います👍」

「ところでhuman_attribute_nameっていうメソッド名、ちょっと長いですよね」「エイリアスがありそうでないのか〜」

参考: Rails API human_attribute_name -- ActiveModel::Translation

🔗 ルーティングの非推奨化2件

  • ルーティング高速化のため、複数パスを含むルーティング生成を非推奨化する

with_optionsまたはループを使えば、複数パスは楽に生成できる。

# 変更前
get "/users", "/other_path", to: "users#index"

# 変更後
get "/users", to: "users#index"
get "/other_path", to: "users#index"

Gannon McGibbon
同Changelogより

動機/背景

このプルリクを作成した理由は、ルーティングのマッピングにおける複数パスの利用を非推奨化したいため。

Rails.application.routes.draw do
  get "/users", "/other_path/users", "/another_path/users", to: "users#index"
end

上の代わりに以下のようにシンプルに書ける。

Rails.application.routes.draw do
  get "/users", to: "users#index"
  get "/other_path/users",  to: "users#index"
  get "/another_path/users", to: "users#index"
end

私見では、この方が少し読みやすいと思う(その分冗長にはなるが)し、自分が提案したいと思っているマッパー実装の今後の変更もシンプルになる。自分はルーティングマッパー内のハッシュをキーワードに置き換えたい。ベンチマークは以下のとおり。

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  gem "rails", path: "."
  gem "benchmark-ips"
  gem "stringio"
end

require "benchmark/ips"
require "action_controller/railtie"

class TestApp < Rails::Application
  config.root = __dir__
  config.hosts << "example.org"
  config.secret_key_base = "secret_key_base"

  config.logger = Logger.new($stdout)
  Rails.logger  = config.logger
end

class TestController < ActionController::Base
  include Rails.application.routes.url_helpers

  def index
    render plain: "Home"
  end
end

Benchmark.ips do |x|
  x.report("draw") do
    Rails.application.routes.draw do
      resources :posts
      resource :user
      root to: "home#index"
      post "login", to: "sessions#login"
      delete "logout", to: "sessions#logout"
    end
    Rails.application.instance_variable_set(:@routes, nil)
  end
end

ルーティング生成が1.25〜1.5倍高速になった。

mainブランチの場合:

ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
Warming up --------------------------------------
                draw   114.000 i/100ms
Calculating -------------------------------------
                draw      1.150k (± 1.1%) i/s -      5.814k in   5.055991s

この機能を削除したフィーチャーブランチの場合:

ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23]
Warming up --------------------------------------
                draw   141.000 i/100ms
Calculating -------------------------------------
                draw      1.496k (± 1.4%) i/s -      7.614k in   5.091321s

詳細

ハッシュキーでルーティングを生成するとルーティング生成が複雑になるため、非推奨化すべき。ルーティングマッパーでキーワードを使う形に移行すれば1.25〜1.5倍高速化するし、この機能を削除すれば大幅に簡単になる。ほとんどの開発者はルーティングでパスを1つしか使わないので、この機能はめったに使われていない可能性がある。

追加情報

この後のプルリクで、ハッシュキーによるルーティングパスの非推奨化(例: get "/users" => "users#index")を構想中。
同PRより


つっつきボイス:「え、get "/users", "/other_path", to: "users#index"みたいな書き方できるの?」「これは見たことなかった」「このサポートを削ると速くなるんだそうです」「使ったことない書き方だから非推奨でも全然OK😆」

ルーティング高速化のため、ハッシュキーのパスを渡してルーティングすることを非推奨化する。

# 変更前
get "/users" => "users#index"
post "/logout" => :sessions
mount MyApp => "/my_app"

# 変更後
get "/users", to: "users#index"
post "/logout", to: "sessions#logout"
mount MyApp, at: "/my_app"

同Changelogより

#52409の続き。

動機/背景

このプルリクを作成した理由は、ルーティングのマッピングでハッシュキーのパスを非推奨化したいため。

# 変更前
get "/users" => "users#index"
post "/logout" => :sessions

# 変更後
get "/users", to: "users#index"
post "/logout", to: "sessions#logout"

これによって、ルーティングマッパーでキーワードを使う方式に近づき、1.25〜1.5倍高速化する(ベンチマークについては上のプルリクのリンクを参照)。

詳細

ハッシュキーでルーティングを生成するとルーティング生成が複雑になるため、非推奨化すべき。ルーティングマッパーでキーワードを使う形に移行すれば1.25〜1.5倍高速化するし、この機能を削除すれば大幅に簡単になる。
同PRより

「こちらもルーティングの似たような非推奨化で、to:の方が速いということだそうです」「to:を使わずにget "/users" => "users#index"のようにハッシュの=>で書くのは昔からあった気がしますね(たしかRails 2ぐらいの頃)」「この非推奨に引っかかる人はそれなりにいそう: いずれにしろ今この書き方にする必要はないでしょうね」

「ところでこれがあくまで起動時のルーティングマップの生成を高速化するものだとしたら、起動時に1回しか実行されない部分が高速化されてもあまり嬉しくない気はしませんか?」「たとえばECSでコンテナをオートスケーリングしたときなんかは高速にサービスインできる方がありがたいんじゃないかな」「それもそうか」「ルーティング生成はRailsの処理の中では重い方ではありますね」

🔗 attribute_writer_missingが追加

  • ActiveModel::AttributeAssignment#attribute_writer_missingを導入

見つからない属性の代入を適切に行える機会をインスタンスに提供する。

class Rectangle
  include ActiveModel::AttributeAssignment

  attr_accessor :length, :width

  def attribute_writer_missing(name, value)
    Rails.logger.warn "Tried to assign to unknown attribute #{name}"
  end
end

rectangle = Rectangle.new
rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'"

同Changelogより

動機/背景

外部データソース(API呼び出しなど)を元にビルドされたActive Modelインスタンスは、時間とともに元のデータの古いバージョンと同期が取れなくなる可能性がある。たとえば、APIがJSONレスポンスからフィールドを追加・削除し、解析されたJSONをアプリケーションがmass assignmentのためにモデルのコンストラクタに直接渡す可能性が考えられる。

最初にデータで何らかの操作(サニタイズや正規化など)を行わないと、モデルインスタンスで元のデータへの予想外の変更を適切に処理する機会を得られない。最良のケースでは修正や強制の措置を講じられる可能性もあるが、最悪の場合、不整合をトラッキングするかログ出力することになる。

詳細

未知の属性の代入を適切に行える機会をインスタンスに提供するため、ActiveModel::AttributeAssignment#attribute_writer_missingを導入する。

class Rectangle
  include ActiveModel::AttributeAssignment

  attr_accessor :length, :width

  def attribute_writer_missing(name, value)
    Rails.logger.warn "Tried to assign to unknown attribute #{name}"
  end
end

rectangle = Rectangle.new
rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'"

デフォルトでは、#attribute_writer_missingをオーバーライドしないクラスはActiveModel::UnknownAttributeErrorをraiseする。

追加情報

attribute_writer_missingの命名は、BasicObject#method_missingの模倣を意図している。
ActiveModel::AttributeMethods#attribute_missingというメソッドもあるが、こちらは属性のアクセスに関係する。
同PRより


つっつきボイス:「今までは属性が見つからない場合にUnknownAttributeErrorを直接raiseしていたけど、#attribute_writer_missingメソッドを追加してオーバーライド可能にすることで、属性が見つからないときの処理をカスタマイズするときの入口として使えるということですね: こういうのがあると助かる場合がありそう👍」

# activemodel/lib/active_model/attribute_assignment.rb#L56
+   def attribute_writer_missing(name, value)
+     raise UnknownAttributeError.new(self, name)
+   end
+
    private
      def _assign_attributes(attributes)
        attributes.each do |k, v|
          _assign_attribute(k, v)
        end
      end
      def _assign_attribute(k, v)
        setter = :"#{k}="
        public_send(setter, v)
      rescue NoMethodError
        if respond_to?(setter)
          raise
        else
-         raise UnknownAttributeError.new(self, k.to_s)
+         attribute_writer_missing(k.to_s, v)
        end
      end
  end

🔗 ログ出力から除外するデフォルト変数名のリストにcvvとcvcも追加

クレジットカードの詳細情報は一般にサーバーに送信すべきではなく、StripeやBraintreeなどで処理すべき。しかし、フォームでユーザーが誤ってクレジットカード番号を送信してしまうと、サーバーで利用されなくても詳細情報がログに出力されてしまうことになる。これは潜在的に「カードデータを保存」していることになるが、これをセキュアに行うための法的な要件は他にもたくさんある。

このプルリクは、新規アプリのActiveSupport::ParameterFilterにデフォルトでcvvcvcを追加する。つまり、これらの名前を持つパラメータはデフォルトでログ出力されなくなる。これは新規アプリのテンプレートを変更するだけであり、既存アプリは変更されない。

同PRより


つっつきボイス:「あ〜なるほど、フィルタで除外するワードリストにクレジットカード番号で使われるcvvcvcを追加したんですね↓」「クレジットカード系のパラメータを直接Railsサーバーで処理する実装にするのは現代日本では通常あり得ない(PCI DSS準拠を自前で頑張るということは非現実的)ので、実際にこのフィルタに助けられることは少ないとは思いますが、転ばぬ先の杖にはなりますね👍」

# railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt#L04
# Use this to limit dissemination of sensitive information.
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
Rails.application.config.filter_parameters += [
- :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
+ :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc
]

参考: クレジットカード・セキュリティガイドライン【4.0版】が改訂されました (METI/経済産業省)

「ちなみにこのフィルタ除外機能は、ログ以外にたとえばinspect結果からの除外でも使われていますね↓」

参考: 3.2.33 config.filter_parameters -- Rails アプリケーションの設定項目 - Railsガイド

🔗 in_batchesで主キー以外のカラムも指定できるようになった

  • カスタムカラムでのバッチングをサポート
Product.in_batches(cursor: [:shop_id, :id]) do |relation|
  # relationで何かする
end

fatkodima
同Changelogより

利用例:

Product.in_batches(cursor: [:shop_id, :id]) do |relation|
  # relationで何かする
end

現在のバッチ処理では主キーカラムのみを利用可能だが、この制約は厳しい。クラスタ化インデックスをより有効に活用するために、何らかの親(テナント)カラムを含める(あるいは一方の親からすべてのレコードを処理して他方の親からの処理を開始する)か、親内で何らかの「位置」カラムを用いてイテレーションするなどが必要になることがある。

カスタムカラムを用いてイテレーションする機能がないので、自分は既に独自gemでカスタムのバッチイテレータを何度か実装したことがある。また、現在のプロジェクトでもカスタムカラムをバッチ処理可能にする必要が生じている。

幸い、複数カラムによる順序付け(:orderによる)が既にサポートされているし、複合主キー(すなわち複数カラム)もサポートされているので、この機能のサポートは非常に簡単である。

うっかりnon-uniqueなカラムを使って結果が誤ってしまう(行の重複や欠落)ことはよくあるので、この実装では、指定のカラムのリストにuniqueカラムの組み合わせ(主キーや、uniqueインデックスに含まれる一部のカラムなど)を含めることを強制している。
同PRより


つっつきボイス:「in_batchesに主キー以外のもの(デフォルトは主キー)も渡せるようになるのは便利だと思うけど、これって安全にやれるのかな?」「バッチ処理中に検索結果が変わった場合とかですよね」「それそれ、そもそもin_batchesに主キーしか指定できないという制限があったのは、主キーでロックしないとおかしなことが起きる可能性があるので、それを防ぐという意図があったと思うんですよ」「使うカラムがイミュータブルで、ソート順位に影響しないことが保証されれば大丈夫なんでしょうけどね」

「プルリクメッセージに"指定のカラムのリストにuniqueカラムの組み合わせを強制する"とあるので、uniqueにならないカラムの組み合わせは渡せないようにすることで防いでいるっぽいですね」「in_batchesに主キー以外のものを渡したいことはたしかにあるので、uniqueな複合主キーを前提にして実装したということでしょうね」

参考: Rails API in_batches -- ActiveRecord::Batches

🔗 ローカル環境でsecret_key_basenilを再び設定可能にした

動機/背景

参照: #52062

従来は、バリデーションは利用時にのみ行われ、セッターでは行われなかったため、ローカル環境ではsecret_key_basenilに設定できた(またはSECRET_KEY_BASE_DUMMY環境変数を利用)。
この部分が#52351で変更され、secret_key_baseに無効な値が設定される場所を正確に特定しやすくなった。

しかしこれによって、development環境やtest環境でsecret_key_baseに無条件に外部値を設定する一部のアプリケーションが動かなくなった。この変更前は、設定値がnilになる可能性があり、利用時に生成されたローカルのsecretにフォールバックしなければならなかった。

詳細

このコミットは以前の振る舞いを復元し、このnil値が最終的に生成されたローカルsecretによって置き換えられるようになったので、アプリケーションが引き続きsecret_key_baseを無条件に設定可能になった。

追加情報

振る舞いを復元するため、今は条件を空白のままにしているが、これを非推奨にして代わりにアプリケーション側でcredentials.secret_key_baseを設定するようにすべきだろうか?
同PRより


つっつきボイス:「そういえばこれは7.2のマイルストーンにしばらく残っていました」「ローカル環境でsecret_key_basenilにできないようにしたら不都合が生じたので修正したんですね: production環境では当然secret_key_baseを設定すべきだけど、ローカル環境でsecret_key_baseをいちいち生成しなければならなくなると面倒なのもたしか」「SECRET_KEY_BASE_DUMMYはRails 7.1でDHHが追加した環境変数でしたね(ウォッチ20230125)」

secret_key_baseが漏れると何が起きるのか実際に試してみた

🔗 SQLIteアダプタでIMMEDIATEトランザクションを有効にした

  • SQLiteで可能な場合はIMMEDIATEトランザクションを利用するようになった

SQLite3アダプタに対して実行されるトランザクションは、コンカレンシーサポートを改善してビジー例外を回避するため、デフォルトでIMMEDIATEになる。

Stephen Margheim
同Changelogより

動機/背景

SQLiteがRailsアプリケーションのproduction向けデータベースエンジンとして人気が高まるにつれて、堅牢かつ回復力を備えたデフォルト設定の必要性も高まってきている。RailsアプリケーションでSQLiteを使うときに直面しがちな問題の1つは、ActiveRecord::StatementInvalid (SQLite3::BusyException: database is locked)例外がときたま発生すること。

これらの例外は、DEFERREDトランザクションが書き込みクエリに遭遇して、トランザクションの途中でSQLiteデータベースのロックを取得しようとしたときに発生する。これはトランザクションの途中で発生するので、SQliteはbusy_handlerコールバックやbusy_timeoutコールバックを呼び出してトランザクションを再試行せずに、ただちにビジー例外でエラーになる。

詳細

このプルリクでは、その存在期間中に2つの異なる手法を検討した。

  1. SQLiteアダプタのデフォルトのトランザクションモードをグローバルにDEFERREDからIMMEDIATEに変更する

  2. RailsがActive Recordの書き込み操作をラップするためのトランザクションに対してのみ、トランザクションモードをDEFERREDからIMMEDIATEに変更する

トランザクションを手動で作成するテストが多数失敗したことと、他のアダプタでは意味も目的もない汎用のmodeオプションを#transactionに公開しない方が自分にとって好みでるため、オプション2を選んだ。

オプション2では、ActiveRecord::Base#with_transaction_returning_statusAdapter#transactionを直接呼び出すのではなく、Adapter#transaction_returning_statusを呼び出すようになった。
このメソッドは、デフォルトでは単なるエイリアスだが、SQLiteアダプタではtransaction_returning_statusメソッドを実装してIMMEDIATEトランザクションモードが使われるようにした。
トランザクションモードの設定は、SQLite3アダプタに追加されたuse_*_transaction_mode!メソッドで行い、test_fixtures.rbモジュールでもこれを用いてfixtureトランザクションが常にDEFERREDトランザクションを使うようにする。

追加情報

このプルリクは、#50370とともに、断続的かつ頻繁にビジー例外をスローすることなく、SQLiteのコンカレンシー処理を安定させる。
同PRより


つっつきボイス:「これはSQLite3を使う人にのみ関係する改修ですね」「SQLite3のトランザクションにはIMMEDIATEDEFFEREDEXCLUSIVEという3つのモードがあるのね」

参考: SQLiteのtransactionにおけるimmediateとexclusive - Using Perl

🔗Rails

🔗 Rails WorldでDHHとMatzの対談企画(Rails公式ニュースより)


つっつきボイス:「肩の凝らない対談企画という感じかな」「"fireside chat"がx.comの機械翻訳で"炉辺談話"と訳されていたのがうまいなと思いました☺️」

🔗 書籍『Ruby on Railsパフォーマンスアポクリファ』


つっつきボイス:「TechRachoのパフォーマンス関連の翻訳記事でもたびたびお世話になっているNate Berkopecさんの書籍が翻訳されたそうです」「ニュースレターをまとめたもので、技術書よりもお気楽な技術エッセイという感じかな: 紹介記事にもあるハッカーと画家とかを思わせますね」「日本だと1,000円で買えるんですね」


「雑談ですけど、アポクリファ(Apocrypha)はキリスト教関連の用語で"外典"という意味で、その反対語がCanon(正典、聖典)で、DNSとかでお馴染みのカノニカル(Canonical)にも通じる言葉です」「へ〜」「Apocryphaはギリシャ語由来なんですが、crypt(=隠された)という言葉が含まれていることからわかるように、暗号のcryptgraphyにも通じる言葉ですね」「エヴァンゲリオンに出てきた死海文書みたいなヤツかな😆」

参考: 外典 - Wikipedia
参考: 聖書正典 - Wikipedia
参考: JPRS用語辞典|CNAMEリソースレコード(シーネームリソースレコード)
参考: 死海文書 - Wikipedia

「こんな感じでIT技術用語の中にはたまに宗教由来の用語もあって、たとえばデータベースなどの昇順を表すascendingに通じるAscensionは、宗教の文脈だとキリストの昇天を表したりしますね」

参考: ascensionの意味・使い方・読み方|英辞郎 on the WEB


つっつき後に日本語版PDFを購入しました。冒頭でアポクリファについても触れていますね↓。


『Ruby on Railsパフォーマンスアポクリファ』より

聖典はこちらです↓。

参考: The Complete Guide to Rails Performance


今週は以上です。

バックナンバー(2024年度第3四半期)

週刊Railsウォッチ: Rails 7.2 RC1がリリース、ストリーミングのレスポンス処理をRack 3で行うほか(20240807前編)

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Rails公式ニュース

Ruby Weekly


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。