- Ruby / Rails関連
週刊Railsウォッチ(20210201前編)Webpackerのガイドがマージ、RailsはRuby 3でどのぐらい速くなったかほか
こんにちは、hachi8833です。
- 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やTwitterでの議論などにどうぞ
- 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
- お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙇
TechRachoではRubyやRailsの最新情報などの記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)
🔗Rails: 先週の改修(Rails公式ニュースより)
今回は以下の公式更新情報が出ていました。
- 公式更新情報: Defaults to “main” branch name, new webpacker guide and improved strict loading | Riding Rails
つっつきボイス:「今週は新年第2号の公式更新情報出てるんですね」「Railsリポジトリのmaster->mainブランチ変更は既にウォッチでも報じました(ウォッチ20210125)が、公式更新情報で以下のツイートにもリンクしていました↓」「今はブランチ名をmaster->mainに移行するのが世の中の流れですね」
Default branch for Rails is now main instead of master ✌️ pic.twitter.com/wdtupbyp1E
— DHH (@dhh) January 15, 2021
For all of you dudes that are asking why change this because you don’t know 1 person that got offended by this, let me just say this:
I made this change, it does offend me, as non-white, decedent of slaves, born in a country that was the last western country to abolish slavery. https://t.co/yrmSoCJ05R— Rafael França (@rafaelfranca) January 15, 2021
その他は以下のコミットリストのChangelogを中心に見繕いました。
🔗 WebpackerガイドがEdge Guidesに追加される
つっつきボイス:「BPSの社内Slackに流れてた情報ですね」
「Rails Guides(英語)とEdge Guides(英語)を見てみたところ、WebpackerガイドはまだEdge Guidesにしかありませんでした↓」「それはまだmaster、じゃなくてmainブランチにしか入っていないからですね: 通常は次のリリースで正規のRails Guidesに載ることになります」「あ、そうでした」
Edge Guides: Webpacker — Ruby on Rails Guides
Rails Guides: Ruby on Rails Guides
「WebpackerのREADME↓も簡単にチェックしてみたところ、Edge GuidesのWebpackerドキュメントはこれとは違う内容でした」「必要そうなことはWebpacker Guidesにだいたい盛り込まれている感じかな👍」
「Webpackerのドキュメント、ありそうでなかったのか」「そうなんです、Rails 5の頃にこんなつなぎ記事↓を書いたのを思い出しました」「Webpackerがリリースされてから随分経つのに今までなかったとはね...」「このプルリクは"いいね"が多めだったので、それだけ待ち望まれていたということでしょうね」
🔗 content_type
メソッドがContent-Typeヘッダーをそのまま返すよう修正
つっつきボイス:「なるほど、変更後はcontent_type
メソッドがtext/csv; header=present; charset=utf-16"
の部分を加工せずに丸ごと返してくれるようになったんですね」
「Rails Guides(英語版)の資料も更新されていて、デフォルトの挙動をreturn_only_request_media_type_on_content_type
コンフィグで変更できるようになっている↓」「既存のRailsアプリがcontent_type
メソッドを使っていたら、このコンフィグパラメータで以前の挙動に戻せますね」「なるほど」
# guides/source/configuring.md#1050
#### For '6.2', defaults from previous versions below and:
- `config.action_view.button_to_generates_button_tag`: `true`
- `config.action_view.stylesheet_media_default` : `false`
- `config.active_support.key_generator_hash_digest_class`: `OpenSSL::Digest::SHA256`
- `config.active_support.hash_digest_class`: `OpenSSL::Digest::SHA256`
+- `config.action_dispatch.return_only_request_media_type_on_content_type`: `false`
参考: Rails アップグレードガイド - Railsガイド
「なるほど、MIMEタイプだけ欲しい場合はActionDispatch::Request#media_type
メソッドで取れるのか」
「以下でmedia_type
メソッドにも修正が入ってますね↓」
🔗 コンフィグファイルで互換性を維持するRails
「この変更では、ライブラリに変更をかけるときに既存のコードに与えるインパクトをできるだけ抑えているのがいいですね: いろいろと参考になります👍」「そうですね」
「Railsは互換性をあんまり気にせずに変えるときは変えるのかなと何となく思ってましたけど、そうでもないんですね」「Railsがデフォルトの挙動を変えることはたまにありますが、そういう場合でも以前の挙動が必要な人はコンフィグで調整可能にする余地を作っておくあたりがRailsらしいと思います」「たしかに!」
「きっとこのコンフィグも、今後既存のRailsアプリでrails app:update
するときに生成されるconfig/initializers/new_framework_defaults.rb
に追加されるんでしょうね↓」
参考: 1.5 フレームワークのデフォルトを設定する -- Rails アップグレードガイド - Railsガイド
「その場合は古い設定のコンフィグが追加されるんでしょうか?」「Railsのこういうコンフィグは後方互換性を維持する方向で生成されるはずですし、少なくともrails app:update
すると"こういう設定があるからチェックして"みたいに知らせてくれますよ」「え、やったことなくて知らなかった」「rails app:update
は基本的に従来のデフォルトの挙動を使うコードがなるべくそのまま使えるように作られていると思います」「そうだったんですね」
ActionDispatch::Request#content_type
がContent-Typeヘッダーをそのまま返すようになった。
従来のActionDispatch::Request#content_type
が返す値にはcharset
パートが含まれていなかった。この振る舞いを変更して、charset
パートをそのままの形で含むContent-Typeを返すようにした。MIMEタイプだけが欲しい場合はActionDispatch::Request#media_type
を使っていただきたい。
# 変更前
request = ActionDispatch::Request.new("CONTENT_TYPE" => "text/csv; header=present; charset=utf-16", "REQUEST_METHOD" => "GET")
request.content_type #=> "text/csv"
# 変更後
request = ActionDispatch::Request.new("Content-Type" => "text/csv; header=present; charset=utf-16", "REQUEST_METHOD" => "GET")
request.content_type #=> "text/csv; header=present; charset=utf-16"
request.media_type #=> "text/csv"
Rafael Mendonça França
Changelogより大意
🔗 LogSubscriberを設定したコントローラでthrow
したときのエラーを修正
- PR: Handle throwing in controller action in log subscriber by janko · Pull Request #41223 · rails/rails
つっつきボイス:「プルリクメッセージを見ると、コントローラで投げた例外がどこで拾われるかという話になっている」「リグレッションが発生したのを修正したのか: &
を追加している↓のを見ても、これは改善ではなく修正でしょうね」「修正前はnil参照みたいになる場合があったということかな」
# actionpack/lib/action_controller/log_subscriber.rb#L26
- if status.nil? && (exception_class_name = payload[:exception].first)
+ if status.nil? && (exception_class_name = payload[:exception]&.first)
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
「LogSubscriberをコントローラに設定し、そのコントローラのアクション内でraiseすると、raiseされた例外がLogSubscriberに吸い込まれて、その後コントローラに投げ返してくれなくなったらしい」
「f0fdeaa↓を見ると、first
を呼ぶ前のpresent?
による存在確認を省略したことでこのリグレッションが起きたようですね」「なるほど、これですか」「一見問題なさそうな最適化が思わぬところに影響したのか」
# actionpack/lib/action_controller/log_subscriber.rb#L25
if status.nil? && payload[:exception].present?
exception_class_name = payload[:exception].first
if status.nil? && (exception_class_name = payload[:exception].first)
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
コントローラのアクションで
throw
が使われていると、マッチするものがRackミドルウェア周辺でキャッチされても:exception
がイベントペイロードにまったく現れなくなっている。
理由は、ActiveSupport::Notifications::Instrumenter.instrument
が:exception
をrescueハンドラー内で設定しているため。しかしrescueは以下のようなthrow/catchシナリオでは決して呼び出されない。
catch(:halt) do
begin
throw :halt
rescue Exception => e
puts "rescue" # ここに到達することはない
ensure
puts "ensure"
end
end
失われた
exception
は実際にはRails 6.1.0より前のバージョンでは扱えていたが、f0fdeaaの最適化のときに、:exception
が存在することを前提とする部分が更新された。そのコミットを取り消すのが修正としては最も簡単。そういうわけで本PRはリグレッション修正とみなせる。
なお、この問題はRodauthをRailsで使っていて見つけた。
同PRより大意
🔗 media=screen
をstylesheet_link_tag
のデフォルトから削除
つっつきボイス:「stylesheet_link_tag
を使うとCSSがmedia=screen
になってた、ということ?」「それをデフォルトから外してデフォルトがallになったようですね」
# 変更前
stylesheet_link_tag "style"
#=> <link href="/assets/style.css" media="screen" rel="stylesheet" />
# 変更後
stylesheet_link_tag "style"
#=> <link href="/assets/style.css" rel="stylesheet" />
「プルリクタイトルにlegacyって書いてありますけど、media=screen
って古いんでしょうか?」「従来はデフォルトでmedia=screen
だったけど、わざわざ指定する意味がないから外したのかな?」
参考: メディアクエリの使用 - CSS: カスケーディングスタイルシート | MDN
「DHHが立てたissue↓にこんなことが書かれてる」
これを修正しよう:
歴史的理由によって、
media
属性は常にデフォルトで"screen"に設定されるので、すべてのメディアタイプを適用するにはスタイルシートで明示的に"all"を設定しなければならない。
stylesheet_link_tag "http://www.example.com/style.css"
# => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
config.assets.stylesheets.media_default
的なものを追加したうえで、既存のアプリではデフォルトを"screen"にすればよいが、新しいアプリでは意味がないので、その後でmedia: "all"
による上書きをscaffoldテンプレートから削除しよう(訳注: 本PRで削除されたそうです)。
#41213より(by DHH)
「今のissueに書かれている情報を見た限りではですが、従来のようにデフォルトがmedia=screen
だとそうした部分が直感的でないから、デフォルトをallにすることでブラウザ画面用のCSSをデフォルトで印刷にも使うよう修正したんだろうと思います」「たぶんそうでしょうね」
「普段あまり気にしない部分ですが、数年に一度ぐらいはmedia="print"
を指定することもあったので、stylesheet_link_tag
と書いただけなのにmedia="screen"
が付けられるより、その方が自分も嬉しいですね」「たしかに」
🔗 rescue_from
でActive Jobのすべてのエラーを拾えるよう修正
つっつきボイス:「なるほど、rescue_from
で拾う例外が従来StandardError
だけだったのを、すべての例外を拾えるようにしたようですね↓」
このコミットより前は、
rescue_from
ハンドラで扱われる例外はStandardError
だけだった。
この変更によって、rescue句がすべてのException
オブジェクトをキャッチするようになり、StandardError
を継承していないException
クラスでrescueハンドラーを定義できるようになる。
つまりStandardError
の外にあるException
をrescueするrescueハンドラーは、今回の変更前に扱えなかった他の例外もrescueできるようになるだろう。
同PRより大意
「たしかRubyの仕様では、改修前のように単にrescue => exception
と書くとStandardError
を拾うんですよ」「あ、そうでしたっけ」
# activejob/lib/active_job/execution.rb#L41
def perform_now
# Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
self.executions = (executions || 0) + 1
deserialize_arguments_if_needed
run_callbacks :perform do
perform(*arguments)
end
- rescue => exception
+ rescue Exception => exception
rescue_with_handler(exception) || raise
end
「Rubyのドキュメントにも書かれてるのを見つけました↓」「ホントだ」「つまりRubyの仕様がそうなってます: 一般にはStandardError
を継承しますけどね」
参考: 制御構造 (Ruby 3.0.0 リファレンスマニュアル)
error_type が省略された時は
StandardError
のサブクラスである全ての例外を捕捉します。Rubyの組み込み例外は(SystemExit
やInterrupt
のような脱出を目的としたものを除いて)StandardError
のサブクラスです。
docs.ruby-lang.orgより
「Exception
を継承するのって非推奨なんでしょうか?」「非推奨とまではいかなかったと思いますが、Rubyでアプリケーションの例外を扱うときはStandardError
を継承するのが一般的だったと思います」「なるほど、アプリ開発のお作法的な感じですか」
「RubyのException
のクラス階層↓を見るとわかりますが、Exception
はStandardError
より上位にあるので、改修後のようにrescue Exception => exception
と書くとすべての例外を拾えるようになります」「なるほど!」
参考: library _builtin (Ruby 3.0.0 リファレンスマニュアル)
「Exception
の直下にあるNoMemoryError
やNotImplementedError
やSecurityError
のような例外クラスはStandardError
では拾えません」「なるほど理解できました」
「NoMemoryError
あたりはアプリで表示する可能性がゼロではないかもしれませんが、SyntaxError
はコードが解析される時点のエラーなので、まずアプリでは表示されないでしょうね」「アプリが立ち上がる前のエラーだからそうなるのか」
「そういう例外を拾えるように改修したということですね」「なるほど」
「何かの機会に、どうも取れない例外があるなって気づいて修正したのかもしれませんね: たとえばNotImplementedError
なら、DI(Dependency Injection)を使うつもりでスタブのクラスを書いているときに、まだメソッドが実装されてないのにエラーが出なくておかしいなと思って気づく可能性がそこそこありそう」「ありそうですね」
🔗 Active Storage向けのfixtureサポートを強化
fixture統合を進めるための
ActiveStorage::FixtureSet
とActiveStorage::FixtureSet.blob
が宣言されるようになった。
Changelogより
つっつきボイス:「お、Active Storageのfixtureサポートが増えた」「ActiveStorage::FixtureSet
が追加されてActive Storage向けのfixtureが使えるようになったみたい」「Active Storageのテストでfixtureが添付ファイルも扱えるようになったということですね」「これはありがたい」「今まで添付ファイルってどうやってテストしてたんでしょうね」「使えるなら欲しいヤツ、いい機能だと思います👍」
🔗Rails
🔗 RailsはRuby 3でどのぐらい速くなったか(Ruby Weeklyより)
つっつきボイス:「RubyやRailsのパフォーマンス記事でお馴染みのNoah Gibbsさんの新しい記事です」
「記事を眺めた感じでは、Ruby 3.0にしたときのパフォーマンスの違いは微差というしかなさそう」「誤差の範囲っぽいですね」
「Ruby自体はRuby 2.0か3.0でら3倍速くなったんですよね」「記事下にあるカラフルなグラフがわかりやすそう↓」「お、なるほど」「グラフを見る限りでは、Ruby 2.7から3.0では実質ほぼ変わらないかな」
「3.0に上げて遅くならなければいいと思います👍」「そうですよね」「もし上げて遅くなるとアップグレードしない人が出てくるかもしれませんが、Ruby 3.0でRailsが遅くなったわけではないからそこは大丈夫だと思います」「上げて大丈夫ですよ〜」
🔗 tzinfo-data
「記事ではtzinfo-data↓あたりでいろいろ苦労したみたい」「tzinfo-dataはrails new
するとデフォルトで入ってきますね」「そういえば皆さんtzinfo-dataってどうしてます?自分は速攻で削除してますけど」「tzinfo-dataはWindowsでRailsを動かすためのものですよね」「そうなんです、Macで動かすとwarningが出てくる」
「そのwarningを見るたびに、そもそもWindows環境でRailsを動かすことはほぼないよねという気持ちになります」「Windows環境でRailsを動かす人が少しでもいるうちは残るんでしょうね...」
参考: bundle installする際のtzinfo-dataのwarningがウザい - Qiita
「そういえば、この間ついにRuby 3.0でRails動かしてみましたよ」「お、どうでした?」「まだ動かしてヤッタバンザイしただけ😆」「気持ちわかります」「お気持ちお気持ち」「なおwaringやマイグレーション時にいろいろエラーが出ました」
🔗 ReactとRailsの新しいアーキテクチャ(Ruby Weeklyより)
つっつきボイス:「ReactとRailsの共存を進めている開発会社の記事のようです」「この図で見当が付きそう↓」
「図を見た限りでは、現在のアーキテクチャ↑ではReact UI ViewでRailsのRESTful APIにアクセスしている: まさに伝統的なRailsアプローチ」
「そして今後目指すアーキテクチャの図↓では、従来のRailsバックエンドとビューもあるけど、React側にAPIライブラリをはさんだうえで、ReactがアクセスするバックエンドをRailsのRESTful APIから徐々にGraphQLに移行しようとしているようですね」「へ〜」「全部移行するのかもしれませんし、RailsのRestAPIも残すのかもしれませんが、いずれにしろRailsを一度引き剥がしてやり直したい感じがしますね」
「このようなアーキテクチャ移行は最近よく見かけるやつですね」
🔗 GraphQLよもやま話
「今日ちょうどWebチームミーティングでRailsとGraphQLについての発表があったんですよ」「お、GraphQLやってるんですね」「自分もGraphQLに関わってみた感想としては、ReactとGraphQLの組み合わせはたしかに便利: でも便利であるがゆえに、GraphQLのエンドポイントで行う仕事が肥大化してくるんですよ」「何となくわかります」
「たとえばさっきの図で言うと、federated GraphQLの下に置くマイクロサービスを1個にするのか、それとも複数のマイクロサービスを扱うのかという選択肢があります」「ふむふむ」「そして後者を選ぶと、ファットなRailsモノリスの代わりにファットなfederated GraphQLができる、そういう未来をちょっと感じているところです」「自分もまさにそう思っています」
「具体的には、このfederated GraphQLで何もかもやろうとするとGraphQLのスキーマが巨大になるんですよ」「やっぱり育っちゃうんですね..」
「Railsの場合は、ルーティングにresources
と1行書くだけでRESTfulなルーティングが使えますが、GraphQLはQueryとMutationがあって、しかもRestfulのCREATEやUPDATEなどもMutationで定義することになります」「ふむふむ」「それをやっていくと、GraphQLのフラットな名前空間に大量のGraphQLメソッドができてくるんですよ」「ありゃ〜」「ネストすることもできるんですが、いずれにしろ、GraphQLのスキーマはちょっと大きすぎじゃないかなと感じているところです」「うーむ」
参考: クエリのクエリとミューテーションの実行 - AWS AppSync
参考: GraphQLのクエリを基礎から整理してみた - Qiita
「そうやってGraphQLスキーマが育ちすぎたら当然エンドポイントを分割しようという話が出てきますけど、今度は分割されたエンドポイント同士のセッション管理をどうするかという問題も出てくるんですよ」「エンドポイントごとのログイン状態のようなstateをフロントエンド側のコードで判断するのは大変そう...」
「そうなったらAWSのAPI Gateway的な方法を使うことになってくるでしょうね: Railsウォッチでも何度か話題にしたEnvoy(ウォッチ20190212)も、そういう流れで出てきたんだろうと思っています」「なるほど」「きれいにやろうと思ったら、さっきの図で言うReactのAPI Libraryの層の下にもうひとつAPI Gateway的な層も必要になってくるんじゃないかと思ったりしますね」「また層が増えるのか〜」「というようなことを最近GraphQLを使いながら思いました」
参考: Amazon API Gateway(規模に応じた API の作成、維持、保護)| AWS
参考: Envoy Proxy - Home
「質問です: 図のfederated GraphQのfederatedは、ここではどういう状態を指すんでしょうか?」「技術用語ではfederationという言葉がよく使われますが↓、ここで使われているfederated GraphQLは複数のマイクロサービスを取りまとめているGraphQL、という程度の意味じゃないかと思います」「なるほど、辞書的には連合とかそういう意味でしたね」
参考: フェデレーション (federation)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
前編は以上です。
バックナンバー(2021年度第1四半期)
週刊Railsウォッチ(20210126後編)Google Cloud FunctionsがRubyをサポート、Ruby 3のパターンマッチングでポーカーゲームほか
- 20210125前編 Railsリポジトリのデフォルトブランチがmainに変更、Rails 6.1はMySQLのENUM型に対応済みほか
- 20210120後編 Ruby 3.0の新機能で遊ぶ、RubyスニペットをJSに変換するRuby2JS、rspec-parameterized gemほか
- 20210113後編 Ruby 3.0 Ractor解説記事、Vercelホスティングサービス、教育用OS xv6ほか
- 20210112前編 Active Recordの範囲指定バリデーション改善、soleとfind_sole_byメソッド、AlgoliaとRailsほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
追記(2021/02/12)
ご指摘に基づき、本見出しに「Active Jobの」を追記しました。ありがとうございます🙇