- Ruby / Rails関連
週刊Railsウォッチ: MinitestとRSpecの比較、商用版NGINXの重要機能がオープンソース化ほか(20220829前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
なお、もう次のが出ていました。
🔗 drop_enum
にもenum定義を渡せるようになった
#45735の続き。
このコミット以前は、drop_enum
がif_exists: true
オプションを受け取れなかったのでマイグレーションをrevertできなかった。
このコミットはその点を修正しつつテストカバレッジも追加する。
また、enum値が指定されていない場合はdrop_enum
をrevertするとActiveRecord::IrreversibleMigration
エラーが発生するようにしている。
同PRより
つっつきボイス:「先週追加されたdrop_enum
(ウォッチ20220822)の続きが出ていました」「if_exists: true
を渡しておけばdrop_enum
をリバース可能にする、これは欲しいでしょうね」
# activerecord/test/cases/migration/command_recorder_test.rb#435
def test_invert_create_enum
drop = @recorder.inverse_of :create_enum, [:color, ["blue", "green"]]
assert_equal [:drop_enum, [:color, ["blue", "green"]], nil], drop
end
def test_invert_drop_enum
create = @recorder.inverse_of :drop_enum, [:color, ["blue", "green"]]
assert_equal [:create_enum, [:color, ["blue", "green"]], nil], create
end
def test_invert_drop_enum_without_values
assert_raises(ActiveRecord::IrreversibleMigration) do
@recorder.inverse_of :drop_enum, [:color]
end
assert_raises(ActiveRecord::IrreversibleMigration) do
@recorder.inverse_of :drop_enum, [:color, if_exists: true]
end
end
🔗 Flashミドルウェアが利用できない場合のEtagWithFlash
の挙動を修正
修正: #45781
flashがdelegate
されているのでリクエストではrespond_to?(:flash)
を呼び出す必要がある。
参考: rails/flash.rb at 46c7420ebfd94314cef1606baf044230007bacfe · rails/rails
同PRより
つっつきボイス:「このFlashはいわゆるFlashメッセージのことなんですね」「そうそう」
参考: §5.2 Flash -- Action Controller の概要 - Railsガイド
参考: Rails API ActionDispatch::Flash
「Flashミドルウェアがない場合にEtagWithFlash
の挙動がおかしかったのが修正されたらしい↓」「なるほど」「#44195を見ると、RailsをAPIモードにしたときにEtagWithFlash
で例外が発生していたようですね」「あ〜」「実はRailsのAPIモードでは、普段使っている機能がデフォルトでいくつか無効にされるんですよ: セッション関連のミドルウェアが使えなくて困ったことがあったのを思い出した」
# actionpack/lib/action_controller/metal/etag_with_flash.rb#L12
module EtagWithFlash
extend ActiveSupport::Concern
include ActionController::ConditionalGet
included do
- etag { flash unless flash.empty? }
+ etag { flash if request.respond_to?(:flash) && !flash.empty? }
end
end
参考 Fix exception on EtagWithFlash when api_only by mihaic195 · Pull Request #44195 · rails/rails
参考: § 4.4 セッションミドルウェアを利用する -- Rails による API 専用アプリケーション - Railsガイド
🔗 assert_enqueued_email_with
を改善
概要
assert_enqueued_email_with
は、パラメータと引数を両方渡されてキューに入れられたメーラーのテストをサポートしていない。このアサーションは、パラメータ化メーラーにマッチするパラメータ(パラメータ引数がハッシュの場合)か、メーラーにマッチする引数(パラメータ引数がハッシュでない場合)のどちらかを前提としている。
さしあたって修正すべきissue
現在は、キューに入ったメール・メッセージはassert_enqueued_email_with: UserMailer.with(user: @user).deliver_invoice(invoice)
とマッチできない。
このプルリクは、params
という名前付き引数をassert_enqueued_email_with
に追加することで、パラメータ化メーラーにマッチするパラメータを明示的に提供できるようにする。
assert_enqueued_email_with UserMailer, :deliver_invoice, params: { user: @user }, args: [invoice]
また、このプルリクは、パラメータ化メーラーをアサーションに直接渡すことでパラメータ化メーラーをテスト可能にする機能も導入する。これはテストされる実際のコードに最も近いアサーションを美しく作成できる方法。
assert_enqueued_email_with UserMailer.with(user: @user), :deliver_invoice, args: [invoice]
その他
このプルリクは既存のコードを破壊しないが、今後どこかの時点で、ハッシュをargs
引数として渡すとparams
を明示的に使用するよう通知する非推奨化警告を導入してもよいだろう。
同PRより
つっつきボイス:「Action Mailerのアサーションの修正だそうです」「名前付き引数params
を追加して、パラメータと引数を両方アサーションに渡せるようにしたらしい」「自分はレンダリングしたメールでbodyの内容をチェックすることが多かったけど、このアサーションを使うとパラメータを渡す時点でチェックできるということですね」
参考: Rails API assert_enqueued_email_with
-- ActionMailer::TestHelper
🔗 メーラーのプレビューパスを複数指定可能になる
config.action_mailer.preview_path
オプションは非推奨化され、config.action_mailer. preview_paths
が推奨される。このコンフィグにパスを追加すると、メーラーのプレビューを探索するパスとして使われるようになる。
fatkodima
同Changelogより
つっつきボイス:「preview_path
が複数形のpreview_paths
に変わってマルチプルプレビューをサポートするそうです」「preview_path
ってどの機能かなと思ったら、メールをHTMLでプレビューする機能のことなんですね: 普段letter_opener gemでやっているのでAction Mailerのこの機能は使ってなかった」
# actionmailer/lib/action_mailer/preview.rb#L144
private
def load_previews
- if preview_path
+ preview_paths.each do |preview_path|
Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
end
end
- def preview_path
- Base.preview_path
+ def preview_paths
+ Base.preview_paths
end
参考: Action Mailerのpreview機能を使って、Railsアプリケーションから送るメールを一覧するページを作った - pockestrap
参考: 【Rails】開発中に送ったメールを確認する(Gem: letter_opener) - おぴよの気まぐれ日記
🔗 ルーティングで発生するリダイレクトのログ出力を修正
メモ: これについてdiscuss.rubyonrails.orgのフォーラムに(ガイドラインに沿って)投稿したが特に返信がなかったので、このプルリクをオープンした。必要なら自由にクローズしてもらって構わない。
概要
ルーティングで直接発生するリダイレクトにログ出力を追加する変更を提案したい。現在は、ルーティングでリダイレクトが発生したときに、実際にリダイレクトが発生したかどうかログを見てもはっきりしない。コントローラで発生するリダイレクトのログ出力はルーティングの場合と異なるので、リダイレクトがどこで発生したのかを突き止めようとして時間を吸われてしまったことがある。
たとえば以下のルーティングがあるとする。
get :moo, to: "rails/welcome#index"
get :redirect, to: "regular#redirect"
root to: redirect("moo")
直接リダイレクトするルーティングにリクエストを送信すると、ログには最初のGETとリダイレクト先ページのGETしか出力されない。
Started GET "/" for 127.0.0.1 at 2021-10-21 13:09:51 +0200
Started GET "/moo" for 127.0.0.1 at 2021-10-21 13:09:51 +0200
Processing by Rails::WelcomeController#index as /
...
しかしコントローラでのリダイレクトではずっと詳しい情報が生成される。
Started GET "/redirect" for 127.0.0.1 at 2021-10-21 13:11:33 +0200
Processing by RegularController#redirect as /
Redirected to http://localhost:3000/moo
Completed 302 Found in 0ms (ActiveRecord: 0.0ms | Allocations: 414)
Started GET "/moo" for 127.0.0.1 at 2021-10-21 13:11:33 +0200
Processing by Rails::WelcomeController#index as /
...
そこで、
ActionDispatch::Routing::Redirect
にいくつかのinstrumentationを追加して、ルーティングで発生するリダイレクトでもこれと同様のログを出力するようにした。この変更によって、ルーティングでのリダイレクトで以下のようなログが出力されるようになる。
Started GET "/" for 127.0.0.1 at 2021-10-21 13:14:49 +0200
Redirected to http://localhost:3000/moo
Completed 301 Moved Permanently in 0ms
Started GET "/moo" for 127.0.0.1 at 2021-10-21 13:14:49 +0200
Processing by Rails::WelcomeController#index as /
...
皆さんの考えを聞かせてもらえると嬉しい。意見や改良点を歓迎する。
同PRより
つっつきボイス:「お〜、routes.rbにこういうふうにリダイレクトを書けるのか↓」「やったことない書き方」
# 同PRより
get :moo, to: "rails/welcome#index"
get :redirect, to: "regular#redirect"
root to: redirect("moo")
「routes.rbでのリダイレクトとコントローラでのリダイレクトで、ログ出力の情報が違っていたらしい」「コントローラのリダイレクトだと、以下のように最初のGETでhttp://localhost:3000/moo
にリダイレクトされたことがログでわかるけど、ルーティングでリダイレクトするとそういう情報がログに出てなかったんですね↓」
# 同PRより: コントローラでのリダイレクト
Started GET "/redirect" for 127.0.0.1 at 2021-10-21 13:11:33 +0200
Processing by RegularController#redirect as /
Redirected to http://localhost:3000/moo
Completed 302 Found in 0ms (ActiveRecord: 0.0ms | Allocations: 414)
Started GET "/moo" for 127.0.0.1 at 2021-10-21 13:11:33 +0200
Processing by Rails::WelcomeController#index as /
...
「修正後のログを見ると、ルーティングでのリダイレクトが301 Moved Permanentlyになっている: これが今までログに出てなかったということらしい↓」
# 同PRより: 修正後のルーティングリダイレクト
Started GET "/" for 127.0.0.1 at 2021-10-21 13:14:49 +0200
Redirected to http://localhost:3000/moo
Completed 301 Moved Permanently in 0ms
Started GET "/moo" for 127.0.0.1 at 2021-10-21 13:14:49 +0200
Processing by Rails::WelcomeController#index as /
...
参考: 301 Moved Permanently - HTTP | MDN
「Railsが301 Moved Permanentlyを実際に返していたのにログに出力されていなかったのは、個人的には従来の振る舞いはバグと言ってもよい気がします」「そうですね」「ログがStarted
行で始まっているのにCompleted
行で終わっていないと、ログが壊れているかと思うし、ログのパーサーを書くときなんかに困りそう」
🔗 touch
、update_column
、update_columns
がreadonlyレコードでエラーを出すよう修正
touch
メソッド、update_column
メソッド、update_columns
メソッドは、レコードがreadonlyの場合にエラーを出していなかった。
修正: #44839
同PRより
つっつきボイス:「このプルリクは少し前のもので、以下の記事で知りました↓」「たしかにreadonlyのレコードでtouch
とかを実行したらraiseしないとおかしいですね」
参考: ActiveRecord methods touch and update_columns no longer work for readonly models | Saeloun Blog
「readonlyレコードならupdate_column
やupdate_columns
でエラーが出なくても更新されることはないだろうけど、エラーなしでtouch
できちゃってたとしたら良くない」「#44839を見ると、touch
でreadonlyレコードのタイムスタンプが更新されていたらしいですね」「怖い」「touch
を暗黙で使っているgemなどがあるかもしれないので気をつけておくといいかも」
参考: touch
-- ActiveRecord::Persistence
🔗Rails
🔗 属性がネストしている場合のバルクINSERT(Ruby Weeklyより)
つっつきボイス:「これは面倒になりがちなヤツ」「nested attributesのバルクINSERTは基本的にやりたくないですね」「こういう場合はbelongs_to
先のレコードに親のidを入れないといけないんですが、親のidはINSERTするまで決まらないという問題がある」「ちゃんと書かないとすぐ激重になりますよね」
「この記事とは別の解決方法になりますけど、UUIDが利用可能ならそれを使ってidを事前に決め打ちするという力技な方法も一応考えられます」「あ〜、たしかにUUIDが使えるならDB問い合わせ前にidを確定できますね」「あまり褒められた方法ではありませんし、使わずに済むならそうしたいですけどね」「最後の手段ぐらいに思っておく方がよさそう」「でもシステムの外部でUUIDが発行されるなら原理的にはありかも」
参考: UUID - Wikipedia
🔗 validates_email_format_of: RFC 2822/5322に沿ったメールバリデーション(Ruby Weeklyより)
つっつきボイス:「メールのバリデータだそうです」「validates_email_format_of、使ったことがあるかも」「リポジトリを見るとコミットも盛んで歴史もありそうですね」
「check_mx
でMXレコードをチェックできるのはいいかも: 書式チェックだけなら正規表現などでもできますけど、いたずらやスパムを排除するにはMXレコードでメールアドレスが実在するかどうかもチェックする必要があるので」「MXレコードチェックするとDNSにクエリをかける分遅くなるけどしょうがないですね」
参考: DNS MXレコードとは? | Cloudflare
後で調べると、RFC 2822は既に廃止されていて、RFC 5322は修正版だそうです。
参考: RFC 5322 - Internet Message Format 日本語訳
🔗 NGINXの商用版機能の一部がオープンソース化
GM of #NGINX @rwhiteley0 has just announced three #opensource promises that will come to life during #NGINXSprint 2022. Read more on how we're getting back to our open source roots https://t.co/4rVxitx5cU pic.twitter.com/1cTu2mAGSs
— NGINX (@nginx) August 23, 2022
つっつきボイス:「エンタープライズ向けNGINX Plusの機能の一部がオープンソースとして使えるようになる、いい話🎉」「記事に"無料のNGINX Open Source版によるSaaSを提供する"とあるけど、SaaSそのものは有料なのかな?」「SaaSは今後もずっと無料とありますね↓」
We are also going to release a new SaaS offering that natively integrates with NGINX Open Source and will help you make it useful and valuable in seconds. There will be no registration, no gate, no paywall. This SaaS will be free to use, forever.
The Future of NGINX: Getting Back to Our Open Source Roots - NGINXより
参考: NGINX Plus|高性能アプリケーション配信システム
「今回の発表の中では、DNSサービスディスカバリ機能がオープンソース版NGINXに入る話↓が個人的にイチオシ👍」「おぉ」「オープンソース版NGINXはずっと前から使っているんですが、この機能がなくてつらかったんですよ」「早く実現されるといいですね」
DNSサービスディスカバリなど有償版の重要な機能をオープンソースに移植し無料化する
同記事より
「この機能がないとどうつらくなるんでしょうか?」「たとえばNGINXをロードバランサーとして使っていて、その配下にRailsサーバーが5台あるとします: NGINXのコンフィグにRailsサーバーをホスト名で書くんですが、オープンソース版のNGINXはRailsサーバーを最初に参照したときのDNS情報をキャッシュしてしまうんですよ」「あ〜」
「AWSのALBはIPアドレスを5個ぐらい返すんですが、キャッシュがあると最初の1個しか使われなくなってしまう」「う〜む」「さらに厄介なことに、ALBはAWS側の都合でときどきIPアドレスが変更されるのに、キャッシュがあると変更に追従できなくなってエラーになったりする」「なるほど」「その点エンタープライズ向けのNGINXは、ちゃんとIPアドレス変更の追従やラウンドロビンなどを行ってくれます」
参考: Application Load Balancer とは? - Elastic Load Balancing
🔗 RailsでMinitestとRSpecのどちらを使うか(Ruby Weeklyより)
つっつきボイス:「HoneyBadgerによるMinitestとRSpecの比較記事です」「自分はMinitestは割と好きなんですが、モデルのテストはRSpecの方がシンプルに書けることも多いですね」「そうそう、Minitestでモデルのテストはつらい」「Request SpecのようなものはMinitestで問題なく書けると思いますけどね」
「RSpecはつい複雑に書いてしまうという問題はありますけど、Minitestはモデルのテストコードが冗長になりやすいけどRSpecだと書きやすくなることを考えると、原則としてRSpecを選ぶのはそれはそれでありだと思います」「アプリが大きく育たないことが事前にわかっていればMinitestで十分でしょうね」
「記事の最後の方で、以下のスライドを引用しつつ"全般にMinitestの方が高速だけど、そうとは限らないぞ"と書かれていますね」「そういうのってテストフレームワークよりもDBの速度やfactoryあたりの方が影響しそうですけど」「たしかに」
その他Rails
Qiita記事書きました。めちゃくちゃニッチなRuby中級者向けの話題です。
Ruby 2.7まで動いていたデフォルト値付きのブロックパラメータをRuby 3.0向けに書き替える https://t.co/r46TubFIRE
— Junichi Ito (伊藤淳一) (@jnchito) August 24, 2022
つっつきボイス:「ブロックパラメータにデフォルト値付き引数を使う書き方がRuby 3.0以降だと動かないらしい」「x: 0, y: 0
のような書き方ですね」「ブロック引数でこの書き方をうまく扱えたためしがなかったような気がします」
# 同記事より
def sample
data = {
a: nil,
b: { x: 1, y: 2 },
c: { x: 1 },
d: { y: 2 },
}
data.map do |k, x: 0, y: 0|
[k, x, y]
end
end
「記事ではブロック引数をh
で受けて内部で展開することでシグネチャを変えずに修正しているんですね↓」「ブロック引数だといつもの**
が使えないのか〜」「この書き方を多用している人は大変そう」「Railsのスコープあたりで使いそうな書き方だけど、x: 1
よりもx=1
みたいな書き方にする方が多い気はしますね」
# 同記事より
def sample
data = {
a: nil,
b: { x: 1, y: 2 },
c: { x: 1 },
d: { y: 2 },
}
- data.map do |k, x: 0, y: 0|
+ data.map do |k, h|
+ x, y = { x: 0, y: 0 }.merge(h || {}).values_at(:x, :y)
[k, x, y]
end
end
前編は以上です。
バックナンバー(2022年度第3四半期)
週刊Railsウォッチ: byebugからruby/debugへの移行ガイド、YJIT解説記事ほか(20220823後編)
- 20220822前編 ビューテンプレートに渡せるローカル変数をマジックコメントでチェック可能にほか
- 20220802後編 RubyのGVLトレーサーgvl-tracing、casting gemでオブジェクトに振る舞いを追加ほか
- 20220801前編 “リーダブルテストコードについて考えよう”スライド公開、Evil Martiansが日本上陸ほか
- 20220726後編 中高生国際Rubyプログラミングコンテスト2022、W3Cの分散型識別子仕様が勧告にほか
- 20220725前編 RailsConf 2022の動画が公開、マイクロサービスのテスト戦略ほか(
- 20220719 RubyのGCが高速化、RuboCopのストレスを減らす4つの方法、Defensive CSSほか
- 20220711前編 AR::RelationにCTEを利用できるwithメソッドが追加、Propshaftアップグレードガイドほか
- 20220705後編 6月のRubyコア動向、Stack Overflowアンケート結果ほか
- 20220704前編 マイグレーションをStrategyパターンで拡張可能にほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)