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

週刊Railsウォッチ: Rails 8からPropshaftがアセットパイプラインのデフォルトにほか(20240619前編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

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

🔗 Rails 7.2新規アプリでdevcontainerをオプトイン扱いに変更

動機/背景

このプルリクを作成した理由は、Rails 7.2でdevcontainersを今後オプトインとして扱うことが決定されたため。これにより新規アプリは、rails new--devcontainerフラグを渡した場合にのみdevcontainerセットアップを取得するようになる。さらに、既存アプリでもbin/rails devcontainerを実行することでdevcontainerセットアップを生成可能になる。

詳細

これを機能させるためのコミットを複数含んでいる。
まず、アプリケーションのジェネレータをリファクタリングして、devcontainerをオプトインできるようにした。次に、devcontainerの全ロジックをアプリケーションのジェネレータから専用のdevcontainerジェネレータに抽出した。これにより、newコマンドとdevcontainerコマンドの両方でロジックを共有可能になる。

devcontainerコマンドを実行すると、アプリを起動して状態を確認し、app:updateコマンドと同じ要領でdevcontainerで必要なオプションを決定する。

追加情報

従来は、devcontainer固有のロジックがapplication_system_test_case.rb.ttに置かれていた。このロジックを削除して、devcontainerジェネレータがdevcontainerを生成するときにapplication_system_test_case.rbを更新する形にした。これにより、既存アプリでdevcontainerを生成するときにファイル全体を上書きせずに済む。

postgresql.yml.ttにも同様にdevcontainer固有のロジックが置かれていた。今後devcontainerは、database.ymlを最新に保つために既存アプリのdatabase.ymlを更新するようになる。db:system:changeジェネレータも修正した(修正前はdevcontainer固有の設定を含めるためにdatabase.ymlを更新していなかった)。

同PRより


つっつきボイス:「Rails 7.2で採用される例のdevcontainer(ウォッチ20240228)は、当初rails newしたときにデフォルトでセットアップされるようになっていたんですが、--devcontainerオプションをつけない限りセットアップされない方針に変わったそうです」「devcontainerのセットアップは環境によってもいろいろ変わるので、サポートはしつつデフォルトから外すことにしたんでしょうね: 不要なら.devcontainer/フォルダを削除すれば済む話なので大して影響しないかな」

参考: Developing inside a Container using Visual Studio Code Remote Development
参考: 開発コンテナーの概要 - GitHub Docs

「そういえばRails 8からはKamalがデフォルトになった(ウォッチ20240529)けど、Kamalもオプトインでいいのではという気がしますね」「言えてる😆」

「さほど使わなさそうなツールをrails newのデフォルトにあまり増やして欲しくない気持ちはありますね: プロジェクトで使う予定がないツールでも、デフォルトでインストールされていたら新メンバーが知らずに使ってしまうみたいなことが起きる可能性があるので、devcontainerやKamalあたりはオプトイン方式の方が好み」

🔗 Kamal用のDocker Volumeが不要な場合は設定しないようになった

動機/背景

修正: #51836

このプルリクを作成した理由は、KamalのDocker永続化ストレージボリュームの設定は、SQLiteやActive Storage以外では不要と思われるため。別のデータベースと--skip-active-storageオプションを組み合わせる場合は、この設定をスキップ可能。

詳細

このプルリクは、Active StorageをスキップしてSQLite以外のデータベースを使う場合は、永続化ストレージボリュームの設定をスキップするようconfig/deploy.yml.ttを変更する。

追加情報

自分が実装した条件は、Railsアプリジェネレータのここで使われているものと同じである。
同PRより


つっつきボイス:「Kamalのデプロイ時に利用するDocker Compose設定の生成で、Active Storageを使う場合やSQLite3を使う場合以外はDocker Volumeを利用しないようにした、なるほど」「deploy.yml.ttがKamal設定のテンプレートなんですね」「ややニッチだけどActive Storageを使わないならKamal用のDocker Volumeは不要でしょうね👍」

# railties/lib/rails/generators/app_base.rb#L369
+     def skip_storage? # :doc:
+       skip_active_storage? && !sqlite3?
+     end
...

      def dockerfile_chown_directories
        directories = %w(log tmp)

-       directories << "storage" unless skip_active_storage? && !sqlite3?
+       directories << "storage" unless skip_storage?
        directories << "db" unless skip_active_record?

        directories.sort
      end

Kamal README: 37signalsの多機能コンテナデプロイツール(翻訳)

🔗 RailsのプラグインジェネレータにRuboCopとGitHub Actionsを追加

RailsのプラグインジェネレータにRuboCopとGitHub Actionsを追加。

Chris Oliver
同Changelogより

動機/背景

RailsConf 2024の講演準備中に、Railsのプラグインジェネレーターが更新されておらず、アプリのジェネレーターに同梱されている新しいrubocopファイルとGitHub Actionsファイルがプラグインに含まれていないことに気付いた。これらは、プラグイン開発者が作業を手軽に開始するうえで特に有用。

これについて@rafaelfrancaと少し話したところ同意をもらえた。なおbrakemanの追加については「意味がない」とのこと。

動機/背景

このプルリクは、プラグインジェネレーターを更新して、GitHub Actionsとrubocopのテンプレートを含めるようにする。また、rubocopのlintにパスするようにいくつかのファイルを更新した。

これらのテンプレートは、時間の経過とともに若干変わってくる可能性があるため、アプリ テンプレートとは別にしている。
同PRより


つっつきボイス:「Rails 7.2からGitHub Actions(ウォッチ20240123)やRuboCop(ウォッチ20240117)のテンプレートを含めるようになったけど、rails plugin newで生成するプラグインファイルにも含めるようにしたんですね」「そういえば大昔のRailsのプラグインは独自仕様だった気がするけど、もうかなり前からgemで作るようになっていましたね↓」

参考: Rails プラグイン作成入門 - Railsガイド

Railsのプラグインはgem化されています。gem形式を採用したことで、必要に応じてプラグインをRubygemsとBundlerでさまざまなRailsアプリケーションと共有することも可能です。
Rails プラグイン作成入門 - Railsガイドより

「今さらですけど、RailsプラグインはRailsエンジン↓とは別のものなんですよね?」「ガイドによればプラグインは基本的にライブラリの拡張という位置づけで、Railsエンジンは少なくともルーティングの特定のポイントにマウントして使うものだと理解しています」「なるほど」

参考: Rails エンジン入門 - Railsガイド

🔗 Rails 8からPropshaftがデフォルトに

Sprocketsは長年貢献してくれたが、Rails 8でPropshaftにバトンを渡すときが来た。
同PRより


つっつきボイス:「ついにRails 8からSprocketsに代わってPropshaftがアセットパイプラインのデフォルトになるのか〜😳」「もうすぐリリースされるRails 7.2をはさむことになりますね」「現行のSprocketsも今見ると最後に更新されたのが9か月前か」

参考: アセットパイプライン - Railsガイド

Propshaft gem README(翻訳)

🔗 Instrumentationのsql.active_recordtransaction.active_recordに現在のトランザクションを追加

#51955の後、sql.active_recordイベントペイロードにも現在のトランザクションオブジェクトを含めるようにした。

最近追加されたActiveRecord::Transaction#uuidのおかげで、ユースケースとしてデータベースアクティビティのトレース(トランザクションごとにクエリをグループ化する機能など)が可能になる。
同PRより


つっつきボイス:「これはActiveSupport::Notificationsへの項目追加ですね: たしかに現在のトランザクションオブジェクトへの参照を取れればいろんなことに使えるのでありがたい👍」「これは欲しい情報ですね」「なお、ActiveSupport::Notificationsはマルチスレッドで動いていないので、取り出した後の処理を増やしすぎると他のものがいろいろ詰まってしまうので注意が必要ですが、こうやって取り出せるということが大事」

# guides/source/active_support_instrumentation.md#L358
| Key                  | Value                                    |
| -------------------- | ---------------------------------------- |
| `:sql`               | SQL statement                            |
| `:name`              | Name of the operation                    |
| `:connection`        | Connection object                        |
+| `:transaction`       | Current transaction                      |
| `:binds`             | Bind parameters                          |
| `:type_casted_binds` | Typecasted bind parameters               |
| `:statement_name`    | SQL Statement name                       |
| `:async`             | `true` if query is loaded asynchronously |
| `:cached`            | `true` is added when cached queries used |
| `:row_count`         | Number of rows returned by the query     |

参考: Active Support Instrumentation で計測 - Railsガイド
参考: Rails API ActiveSupport::Notifications

「あと、以下の#51955もActiveSupport::Notificationsへの項目追加ですね↓」

transaction.active_recordのサブスクライバがペイロード内のイベントに関するトランザクションを取得するのは自然だと思う。
同PRより

# guides/source/active_support_instrumentation.md#L413
| Key                  | Value                                                |
| -------------------- | ---------------------------------------------------- |
| `:connection`        | Connection object                                    |
+| `:transaction`       | Transaction object
| `:outcome`           | `:commit`, `:rollback`, `:restart`, or `:incomplete` |

🔗 nil UUIDを定義するDigest::UUID.nil_uuidが追加

RFC 4122は、いわゆるnil UUIDを定義している。これをDigest::UUIDに追加して、この特殊UUIDを使えるようにしよう。
同PRより


つっつきボイス:「RFCに定義があるならそれに従う方がいい👍」「なおプルリクではRFC 4122が参照されていますが、調べてみたら4122は廃止されていて以下のRFC 9562が最新だそうです↓」

参考: RFC 9562 - Universally Unique IDentifiers (UUIDs) 日本語訳

5.9. Nil UUID
nil uuidは、128ビットすべてがゼロに設定されるように指定されている特別な形式のuuidです。
RFC 9562 - Universally Unique IDentifiers (UUIDs) 日本語訳より

🔗 schema_cache_ignored_tables?のpublicメソッドを追加

  • テーブルがスキーマキャッシュで無視されるかどうかをチェックするpublicメソッドを追加

従来は、テーブルが無視される設定になっているかどうかをチェックするために、アプリケーションでスキーマキャッシュクラスのignored_table?を再実装しなければならなかった。
これをサポートするpublicメソッドが追加され、スキーマキャッシュはこれを直接利用するよう更新された。

ActiveRecord.schema_cache_ignored_tables = ["developers"]
ActiveRecord.schema_cache_ignored_tables?("developers")
=> true

Eileen M. Uchitelle
同PRより


つっつきボイス:「以前追加されたschema_cache_ignored_tablesコンフィグ(ウォッチ20210921)に加えてメソッドも追加したそうです」「あまりなさそうだけど、特定のテーブルでスキーマキャッシュを使いたくないみたいな状況が生じたときに欲しくなりそうな機能かも🤔」

参考: 週刊Railsウォッチ20210921: schema_cache_ignored_tables設定オプションが追加
参考: 3.8.51 config.active_record.schema_cache_ignored_tables -- Rails アプリケーションの設定項目 - Railsガイド

🔗 rails app:updateでpublic/ディレクトリも更新するよう修正

動機/背景

Railsバージョンをアップグレードするとき、app:updateコマンドが/publicディレクトリ内に新規ファイルを生成していない。

たとえば、Rails 7.1を7.2にアップグレードしても、406-unsupported-browser.htmlファイルが生成されない。

詳細

このプルリクは、app:updateでpublic/ディレクトリが追加されるようにする。

config_when_updatingなどで行う方がよいだろうか?
同PRより


つっつきボイス:「少し前にpublic/ディレクトリに追加された"406 Not Acceptable"エラーページ(ウォッチ20240425)などがrails app:updateで追加されていなかったのを修正した、なるほど」「よくぞ気づいたという感じですね」

参考: 1.4 アップデートタスク -- Rails アップグレードガイド - Railsガイド

🔗 PermissionsPolicyにdisplay-capturekeyboard-mapを追加

動機/背景

display-capture(Chrome 94)とkeyboard-map(Chrome 97)がpolicy controller featureで標準化された

このプルリクを作成した理由は、policy controller featureが更新されたため。

詳細

このプルリクは、ActionDispatch::PermissionsPolicyを更新する。
同PRより


つっつきボイス:「display-capturekeyboard-mapというポリシーがW3Cでサポートされるようになったのか、へ〜!」

「こういうポリシーはどう使うんでしょうか?」「サーバーがこれらの機能を使いますよという通知を正式なレスポンスとしてクライアントに返せるようになるということですね: それを受け入れるかどうかはクライアント側次第」「なるほど」「ちなみにブラウザのPermissions PolicyはCSP(Content-Security-Policy)ヘッダーとは別物ですが使い方が似ていますね: 典型的な使い方は以下の2つだと思います」

  • CSPと同じく、XSSがあったときに備えて「念のため」設定しておくことで、悪用リスクを減らす。
    jsbin.comのような、あえて任意コード実行を許可するタイプのWebサイトでも有用。

  • iframeで外部サイトを埋め込むときに、その外部サイトが使える権限を制限する。

参考: Permissions Policy - HTTP | MDN
参考: 8.3 Content-Security-Policyヘッダー -- Rails セキュリティガイド - Railsガイド

「サーバーがクライアント側のデバイスを利用するときにこうやって正規の方法で通知できれば、それ以外の方法でデバイスを使おうとする操作をクライアント側で拒否するときの判断に使えるようになると思いますし、WidgetやAnalyticsなどのような正規のものかXSSのような悪意のあるものかにかかわらず、自分たちでメンテしていないJavaScriptを動かす時にWebサイト側で制限をかけられるのは安心ですね👍」

「そういえばPermissionsPolicyはこの間のRailsセキュリティ修正でも修正が入っていましたね↓」「そうそう、ありました」

Railsセキュリティ修正7.1.3.4、7.0.8.4、6.1.7.8がリリースされました

参考: Rails API ActionDispatch::PermissionsPolicy

🔗 新ドキュメント: Tuning Performance for Deployment

このガイドは、PumaとCRubyにおけるコンカレンシーやパフォーマンスの主要な原理について解説する。
#50949のやり直し版)
同PRより


つっつきボイス:「Rails 7.2のGuidesに新しいドキュメントがマージされました」「お〜、PumaとCRubyのコンカレンシー解説が公式に入るのはありがたい🎉」「concurrent-rubyも更新されてますね」

ruby-concurrency/concurrent-ruby - GitHub


前編は以上です。

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

週刊Railsウォッチ: Rails 8でKamalがデフォルトのデプロイツールになるほか(20240529)

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

Rails公式ニュース

Ruby Weekly


CONTACT

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