- Ruby / Rails関連
週刊Railsウォッチ: Active Storageのミラーアップロードが非同期に、Rackアプリを手作りほか(20230829前編)
こんにちは、hachi8833です。WEB+DB PRESSの最終号をKindleで購入しました。
WEB+DB PRESSを受け取りました。たいへん残念ですが最終号です。担当記事の「おわりに」を間違った方向にめちゃくちゃがんばったので楽しんでいただけると幸いです#wdpress pic.twitter.com/PGOn3eu7Bb
— ANDO Yasushi🇺🇦 (@technohippy) August 19, 2023
🔗Rails: 先週の改修(Rails公式ニュースより)
やっと公式に追いつきました。
🔗 Webpackerガイドへのリンクを英語版Rails Guidesのindexページから削除
- PR: Removes Webpacker from the guides index page by akhilgkrishnan · Pull Request #48897 · rails/rails
動機/背景
Webpackerは現在非推奨となり、Rails7アプリケーションでは使われていない。そのため、Railsガイドのindexページでwebpacker
を参照する必要はなくなった。詳細
このプルリクは、ガイドのindexページからWebpackerへの参照を削除する。同PRより
つっつきボイス:「WebpackerガイドへのリンクがついにRails Guidesトップのインデックスから削除されるそうです」「お、ついに消えるのか」「はい、日本語のRailsガイドでも現行のWebpackerガイドそのものは残す予定です」「なるほど、今回の改修もリンクを消しただけですね: Webpackerの代わりにShakapackerを使いましょうみたいな情報もなくなるのかな?」「edgeGuidesのアセットパイプラインガイドにはShakapackerの記述がありますね」
🔗 Action Mailerのプレビューが未定義の場合にプレビューでわかりやすく表示するようになった
動機/背景
アプリケーションが対応するプレビューを定義せずにメーラーを定義すると、
GET /rails/mailers
リクエストは空の<body>
要素を持つページを返す。ページが完全に空になっていると、空のリストとエラーが無視された「失敗」状態とを区別しにくいため、混乱を招く可能性がある。
同様に、
ActionMailer::Preview
のサブクラスが定義されているが、アクションが何も宣言されていない場合、レスポンスに含まれるページがほぼ空になってしまう。詳細
このコミットは、上の両方のシナリオに対して空の状態メッセージを表示し、Action Mailer Basicsガイドへのリンクも提供する。
このコミットでは、振る舞いを効果的にカバーするために、Mailerプレビューテストのカバレッジも拡張し、
rails-dom-testing
のアサーションを利用している。同PRより
つっつきボイス:「Action Mailerのプレビューが未定義の場合にプレビューが未定義であることがわかるような表示に変えた、なるほど」「プレビューに情報がないとエラーなのか未定義なのかわかりにくいですよね」
後でmainブランチで改修後の未定義プレビューを表示してみました。
- Rails 7.0.7の場合
- mainブランチ(7.1.0.alpha)の場合
参考: §2.6 メールのプレビュー -- Action Mailer の基礎 - Railsガイド
🔗 Content-Type
ヘッダーに空文字を渡した場合のエラーを変更
動機/背景
このプルリクを作成した理由は、この変更(1071a39)以降、クライアントが空の
Content-Type
ヘッダーでリクエストを送信した場合に、actionpack/lib/action_dispatch/http/mime_type.rb
の166行目で"undefined method 'rstrip' for nil:NilClass"
というNoMethodError
が発生しているため。このクライアントは仕様に沿っていないと思うが、このコミットの前なら
ActionDispatch::Http::MimeNegotiation::InvalidType
エラーが発生し、受け取るレスポンスは「500 Server Error」ではなく「406 Not Acceptable」だっただろう。
NoMethodError
はエラートラッキングシステムを混乱させ、より具体的な無効なMIMEタイプのエラーを無視できてしまう。詳細
このプルリクは、MIMEタイプの検索を変更し、
rstrip
の呼び出しに安全なぼっち演算子&.
を使うことで、クライアントが空白のContent-Type
ヘッダーでリクエストを行った場合に対応する。追加情報
次のコードスニペットには、現在の
main
ブランチで問題を再現できるサンプルが含まれている。▶クリックすると展開します
# frozen_string_literal: true require "bundler/inline" gemfile(true) do source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem "rails", github: "rails/rails", branch: "main" gem "rack", "~> 2.0" end 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 routes.draw do get "/" => "test#index" end end class TestController < ActionController::Base include Rails.application.routes.url_helpers def index render plain: "Home" end end require "minitest/autorun" require "rack/test" class BugTest < Minitest::Test include Rack::Test::Methods def test_returns_success get "/", {}, 'CONTENT_TYPE' => '' assert last_response.server_error? assert (last_response.status == 500) end private def app Rails.application end end
同PRより
つっつきボイス:「リクエストのContent-Type
ヘッダーが空だとMIMEタイプの処理でNoMethodError
エラーになってたのか: NoMethodError
はたしかにわかりにくい」「以前取り上げた#48397の改修(ウォッチ20230628)で生じたバグだったんですね」「nil
エラーの対応なので、修正はぼっち演算子&.
を導入しただけ↓」
# actionpack/lib/action_dispatch/http/mime_type.rb#L164
def lookup(string)
# fallback to the media-type without parameters if it was not found
- LOOKUP[string] || LOOKUP[string.split(";", 2)[0].rstrip] || Type.new(string)
+ LOOKUP[string] || LOOKUP[string.split(";", 2)[0]&.rstrip] || Type.new(string)
end
🔗 Action Packのfixture_file_upload
をfile_fixture_upload
にリネーム
動機/背景
テストハーネスの
file_fixture
ヘルパー(およびfile_fixture_path
コンフィグ値)と、統合テストハーネスのfixture_file_upload
の命名が食い違っているため、常に混乱と驚きが引き起こされる。詳細
Active Supportの方が普及が進んでいるため、このコミットでは
fixture_file_upload
メソッドをfile_fixture_upload
にリネームした。これにより、file_fixture
とfile_fixture_path
の語順が一致するようになる。後方互換性を維持するため、
fixture_file_upload
のエイリアスを宣言することで将来に備える(または将来のどこかで削除する)。同PRより
つっつきボイス:「メソッド名の不統一を修正するために、多数派のfile_
始まりのメソッド名に寄せてリネームしたのか、なるほど」「リネーム前のメソッドもエイリアスで残すので互換性は大丈夫ですね」
🔗 Active Storageのミラーアップロードを非同期に
クローズ: 47918
文脈
Active Storageのミラーリングは実に重宝する。ただし、storage.ymlでミラーサービスを定義すると、ミラーへのアップロードが実際にはインラインで行われるため、
ActiveStorage::MirrorJob
が使われない。ミラーの個数を
n
とすると、(ActiveStorage::MirrorJob
が使われずに)インラインでn
回処理されるため、サービスへのアップロードが遅くなる。修正
このコミットより前は、ミラーサービスにファイルがアップロードされると、個別のサービス(プライマリおよびミラー)へのファイルのアップロードは同期的に行われていた。
def upload(key, io, checksum: nil, **options) each_service.collect do |service| io.rewind service.upload key, io, checksum: checksum, **options end end
このコミットは、プライマリサービスにはファイルを同期的にアップロードし、その後ミラーサービスにはファイルを非同期でアップロードするジョブをエンキューする形で振る舞いを変更する。
def upload(key, io, checksum: nil, **options) io.rewind primary.upload key, io, checksum: checksum, **options mirror_later key, checksum: checksum end
同PRより
つっつきボイス:「Active Storageにミラーのアップロード機能があるとは知らなかったけど、ミラーへのアップロードは非同期でできる方がいいですね👍」「プライマリへのアップロードは同期的で、ミラーへのアップロードが非同期になるんですね」
参考: Rails API ActiveStorage::Service::MirrorService
参考: Rails API ActiveStorage::MirrorJob
🔗Rails
🔗 Rails Foundationの"Rails Luminary Awards"(Rails公式ニュースより)
つっつきボイス:「Rails FoundationがRails Luminary Awardsという賞を創設したそうです」「今年10月に行われる例のRails World(ウォッチ20230427)の一環のようですね」「Ruby Prizeを連想しますね」「このフォームでノミネートできるのね」「誰が受賞するかな」
🔗 Rackアプリケーションをゼロから手作りするガイド
つっつきボイス:「記事には"Rackのことが今までよくわかってなかったのでRackアプリケーションを作ってみた"とありますね」「Rackアプリは基本的にcall
メソッド↓(env
引数を取り、status
、headers
、body
の値からなるnon frozen arrayを返す)を実装して結果を返すようになっていれば、それで動くようになりますよ」
# 同記事より
# app/app.rb
class App
def call(env)
[200, {}, ["Hello World"]]
end
end
「resolvedというのがそのサンプルアプリだそうです↓」
「Rackアプリ手作り企画は昔からちょくちょく見かけますし、一度は学びのためにやってみるといいと思います👍: Rackアプリは、routes.rbに直接書いても動くぐらいシンプルなので、ヘルスチェックのようにRailsを使うまでもないシンプルなアプリを書くときにも使えます」「そういえばSinatraもRackベースのフレームワークですね」
🔗 Railsコアメンバーによる「モンキーパッチ反対」(Ruby Weeklyより)
つっつきボイス:「RailsコアメンバーのEileenさんの記事です」「モンキーパッチの苦労がしのばれる、よさそうな記事👍」
「モンキーパッチは使わないに越したことはないし、モンキーパッチに頼らざるを得ないRailsプロジェクトは管理がちゃんとしていなかったり、下手をするとモンキーパッチのテストすら行われていなかったりする可能性もあるので、よほど凄腕の古参エンジニアがいるのでなければモンキーパッチは避けたい」「たしかに」
「Railsアプリにモンキーパッチを当てるよりも、ShopifyやGitHubがよくやっているように、本家Railsにプルリクを投げて反映するのが、本来は正当な方法」「そういえばだいぶ前に、GitHubが自分たちの古いRailsアプリケーションをアップグレードするために、ものすごく苦労してモンキーパッチを解消した話がありましたね↓」
参考: Upgrading GitHub from Rails 3.2 to 5.2 - The GitHub Blog -- 2018年の記事です
「記事によると、モンキーパッチの代替方法であるrefinementは相当遅いらしい」
「記事に貼られていた"責任あるモンキーパッチ"↓、これも参考になりそうな記事👍」「Module#prepend
を使う、gemのバージョンをチェックするなど、モンキーパッチがどうしても避けられない場合の方法がいろいろ紹介されていますね」
参考: Responsible Monkeypatching in Ruby | AppSignal Blog
「GitHubのような大規模Railsアプリでモンキーパッチを完全になくすのは難しいかもしれませんが、少なくとも正しい方法で最小限のモンキーパッチを当てつつ、Rails本家にもissueとプルリクを投げるのが望ましいですね」「記事にもあるように、モンキーパッチを削除する手順も事前に定めておくのも大事」
🔗 Railsでセッションcookieを設定しないようにする方法
つっつきボイス:「短い記事です」「RailsをAPIサーバーとして使うときにセッションcookieを止めたいという話ですね」
# 同記事より
# application_controller.rb
after_action lambda {
request.session_options[:skip] = current_api_token.present?
}
def current_api_token
# 認証がAPIトークン経由の場合にのみ値を返す
end
「lambda
でrequest.session_options[:skip]
に値を設定することで個別にセッションcookieを止められるんですね: なおセッションcookieを止めるだけならCookie関連のミドルウェアを削除する方が早いと思いますし、そもそもRailsアプリをAPIモードで新規作成した場合はデフォルトでセッションミドルウェアをインストールしませんが、いろんな方法があることは知っておいてよさそう」
参考: § 3.2.3 ミドルウェアを削除する -- Rails と Rack - Railsガイド
参考: §4.4 セッションミドルウェアを利用する -- Rails による API 専用アプリケーション - Railsガイド
前編は以上です。
バックナンバー(2023年度第3四半期)
週刊Railsウォッチ: ArelでCAST関数サポート、webdrivers依存を解消、YJIT高速化ほか(20230824後編)
- 20230823前編 Rails 7.0.7に含まれているRails 7.0.6のバグ修正ほか
- 20230809 Rails 7.0.5のcreate_association挙動変更取り消し、YJITの性能を最大限引き出す方法ほか
- 20230803後編 Railsフラグメントキャッシュ経由の情報漏洩に注意ほか
- 20230802前編 Active Storageバリアントの事前変換、Linkヘッダープリロードのオプトアウトほか
- 20230727後編 Rubyにdefp導入の提案、IRB 1.7.3リリースほか
- 20230725前編 config.autoload_libとconfig.autoload_lib_onceが追加ほか
- 20230721後編 Kaigi on Rails 2023プロポーザル募集、rubocop-magic_numbersほか
- 20230719前編 複合主キー関連の実装進む、Action TextでHTML5サニタイザほか
- 20230705後編 AWS LambdaでRailsをRackで動かすLambyほか
- 20230704前編 productionのforce_ssl=trueがデフォルトで有効に、rakeタスクをthorで書くほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)