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

週刊Railsウォッチ: Kaigi on Rails 2022のタイムテーブル発表、書籍『Practicing Rails』ほか(20221003前編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

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

🔗 コネクションハンドラメソッドのバグを修正

active_connections?clear_active_connections!clear_reloadable_connections!clear_all_connections!flush_idle_connections!がデフォルトですべてのプールで動作するようになった。従来は指定がないとデフォルトでcurrent_roleまたは:writingロールを使っていた。
Eileen M. Uchitelle
同Changelogより


以前自分がRailsでマルチプルデータベースのロールを実装したときはハンドラーが2つあったので、active_connections?clear_active_connections!clear_reloadable_connections!clear_all_connections!flush_idle_connections!というメソッドが現在の(あるいは渡された)ロールのみで動作し、(ロールにかかわらず)対象がすべてのプールではなかったのは理にかなっていた。これを削除して、すべてのプールをプールマネージャが管理するハンドラに移動したときは、元の振る舞いが変わらないようこれらのメソッドをそのままにしておいた。

これらのメソッドはアプリケーションからしか呼ばれず、Railsからは呼び出されないので、そのときはこれでよいと思えた。しかし昨日、これらのメソッド(flush_idle_connections!clear_active_connections!clear_reloadable_connections!)の一部が起動時にActive Record railtieからすべて呼び出されていることに気づいた。

残念ながら、これではマルチプルデータベースのRailsアプリケーションを起動したときに、書き込みを除くすべてのコネクションがflushもクリアもされないということになる。

ここでの変更では、読み込みロールなどが直接渡された場合は既存の振る舞いを維持し、それ以外の場合、ロールがnil(新しいデフォルト値)ならすべてのコネクションをフォールバックして非推奨警告を出力する。今後はこれがデフォルトの振る舞いとなる。この非推奨警告を手軽にオフにできるようにするため、:all引数(警告を出力せずにすべてのプールを使う)を追加しておいた。この非推奨警告は、プールマネージャに複数のロールがある場合にのみ出力され、それ以外の場合は従来の振る舞いを前提とする。

このバグが影響するのはロールが複数あるアプリケーションだけであり、上述のメソッドがconnected_toコンテキストの外で呼び出された場合にのみ発生する。これらのメソッドは今後current_roleのセットを考慮しなくなるので、これらのメソッドをアプリケーションのすべてのプールで動作させたくない場合は、(ロールを)明示的に指定する必要がある。

cc/ @matthewd(今朝相談させてもらった)
同PRより


つっつきボイス:「比較的エッジケースのバグかな」「マルチプルデータベースコネクション関連のバグがまだ残っていたんですね」「マルチプルデータベースを使っているプロジェクトは多くないと思いますが、使っているところはだいたい複雑になっていてその分エッジケースのバグを踏みやすいかもしれませんね」

参考: Active Record で複数のデータベース利用 - Railsガイド

🔗 ActionViewレンダリングのinstrumentationが返すデータに:localsも出力可能になった

改修前:

{
identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
layout: "layouts/application"
}

改修後:

{
identifier: "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
layout: "layouts/application",
locals: {foo: "bar"}
}

Aaron Gough
同Changelogより


つっつきボイス:「ActionViewのレンダラーのinstrumentationで:localsの内容も出力できるようになったんですね: localsには個人情報のようなセンシティブな情報が含まれる可能性もあるので出力する場合は注意が必要だと思いますが、取れる情報が増えるのはありがたい👍」

参考: § 3.3 Action View -- Active Support の Instrumentation 機能 - Railsガイド

🔗 run_callbacksメソッドにbefore/around/afterコールバックのいずれかだけを実行するオプションを追加

概要
現在のrun_callbacksメソッドでは、あるグループのすべてのコールバック(すべての保存、作成、削除など)の実行を強制され、before/around/afterコールバックのいずれかだけを指定できなかった。
このプルリクは、ユーザーが実行したいコールバック種別を指定するオプションパラメータを受け取れるようrun_callbacksを変更することで、この機能を追加するためのもの。
その他の情報
このパラメータを指定しない場合、run_callbacksの振る舞いは従来と完全に同じになるべき。


つっつきボイス:「run_callbacksの第2引数で:before:after:aroundという種別を指定できるようになった」「aroundは、beforeとafterを両方実行するということですね」「コールバックのaroundはネストも含めて正しい順序で実行されることが重要」「そういえば正規表現のドキュメントでlookahead(先読み)とlookbehind(後読み)の両方を指すときにlookaroundと呼んでいたのを思い出しました」

# activesupport/test/callbacks_test.rb#1237
  class RunSpecificCallbackTest < ActiveSupport::TestCase
    def test_run_callbacks_only_before
      klass = AllSaveCallbacks.new
      klass.run_callbacks :save, :before
      assert_equal ["before_save_1", "before_save_2"], klass.history
    end

    def test_run_callbacks_only_around
      klass = AllSaveCallbacks.new
      klass.run_callbacks :save, :around
      assert_equal [
        "around_save_1_before",
        "around_save_2_before",
        "around_save_2_after",
        "around_save_1_after"
        ],
        klass.history
    end

    def test_run_callbacks_only_after
      klass = AllSaveCallbacks.new
      klass.run_callbacks :save, :after
      assert_equal ["after_save_2", "after_save_1"], klass.history
    end
  end

参考: §1.2 Callbacksモジュール -- Active Model の基礎 - Railsガイド
参考: Rails API run_callbacks -- ActiveSupport::Callbacks

はじめての正規表現とベストプラクティス#4 先読みと後読みを極める

🔗 ActionDispatch::CookiesのJSONデシリアライザのエラーをrescueするよう修正

この変更を行わないと、action_dispatch.cookies_serializer:jsonに設定されているアプリが:marshalシリアライズドcookieを読み取ろうとしてエラーが発生したときにcookieがクリアされず、アプリのユーザーがブラウザのcookieを手動でクリアしなければならなくなる。
(元のバグに関する議論は#45127を参照)
Nathan Bardoux
同Changelogより


つっつきボイス:「cookieのjsonデシリアライズでJSON::ParserErrorエラーがrescue対象に含まれていなかったためにmarshal dumpが残っていたのか↓」「なるほど」「Javaだとthrowsを書けるのでこういうrescue漏れは発生しないんですけどね」

# actionpack/lib/action_dispatch/middleware/cookies.rb#L675
      private
        def parse(name, encrypted_message, purpose: nil)
          deserialize(name) do |rotate|
            @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate, purpose: purpose)
          end
-       rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
+       rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature, JSON::ParserError
          nil
        end

参考: module Marshal (Ruby 3.1 リファレンスマニュアル)

🔗Rails

🔗 Kaigi on Rails 2022のタイムテーブルが公開

つっつきボイス:「ついにタイムテーブルが発表された🎉」「10/21(金)〜10/22(土)のそれぞれ12時から開始で、リモート開催のみか」「スロットはどちらも1つだけなのでどれを見るか悩まずに済みそう」「その分発表数もDay1が13件、Day2が11件と盛りだくさんで、発表2つごとに休憩が入るペースなんですね」「リモート開催は自分のペースで見られるのがありがたい」

「今回は託児サポートも行われるそうです↓」「お〜、技術系のオンラインイベントとしては珍しいかも」「最寄りの外部託児サービスの費用を先着20名まで負担する形なんですね」「託児サービスの運営はさまざまな面で気を遣う部分が増えるので、こういう形式にしたのもわかる」

以下はつっつき後に見つけたツイートです。プレイベントもお見逃しなく。

🔗 書籍『Practicing Rails』

つっつきボイス:「jnchitoさんのツイートを見るまでPracticing Railsを知りませんでした」「Railsチュートリアル™が終わったぐらいの人を対象にしているようですね」「サイトを見るとRails 5.1が最新だった時期に書かれたそうです」「Rails 5.1の頃ならWebpackerの話は入っていなさそうだからRails 7の時代に合わせて読みやすいかも」

「この本はRailsを学習するときのガイド的な位置づけで、コミュニティーとの繋がり方などにも言及しているようですね」「具体的な技術を解説する書籍だとどうしても古くなりがちですが、この本の場合は古くなった記述とそうでない記述を見分ける力がある程度必要だろうとは思うものの、今の時代に読んでもよさそう👍」「Railsの基本設計や理念がこれまで大きく変わっていないのはありがたいですね」「これに限らず、本は読む人との相性や読んだ時点のスキルといった要素も大きいので一概には言えませんが、自分に合っていそうと思ったらどんどん読んでみるのがいいと思います」「やっぱり本は出会いですね」

🔗 holidays: 休日を扱うgem(Ruby Weeklyより)

holidays/holidays - GitHub


つっつきボイス:「これまでウォッチで取り上げていそうでいなかったgemで、日本語も対応しているようです」「こういうgemは昔からあって使われていますね」

後で動かしてみました。

>> require 'holidays'
>> Holidays.year_holidays([:jp], Date.civil(2022, 1, 1))
=>
[{:date=>#<Date: 2022-01-01 ((2459581j,0s,0n),+0s,2299161j)>, :name=>"元日", :regions=>[:jp]},
 {:date=>#<Date: 2022-01-10 ((2459590j,0s,0n),+0s,2299161j)>,
  :name=>"成人の日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-02-11 ((2459622j,0s,0n),+0s,2299161j)>,
  :name=>"建国記念の日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-02-23 ((2459634j,0s,0n),+0s,2299161j)>,
  :name=>"天皇誕生日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-03-21 ((2459660j,0s,0n),+0s,2299161j)>,
  :name=>"春分の日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-04-29 ((2459699j,0s,0n),+0s,2299161j)>,
  :name=>"昭和の日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-05-03 ((2459703j,0s,0n),+0s,2299161j)>,
  :name=>"憲法記念日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-05-04 ((2459704j,0s,0n),+0s,2299161j)>,
  :name=>"みどりの日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-05-05 ((2459705j,0s,0n),+0s,2299161j)>,
  :name=>"こどもの日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-07-18 ((2459779j,0s,0n),+0s,2299161j)>, :name=>"海の日", :regions=>[:jp]},
 {:date=>#<Date: 2022-08-11 ((2459803j,0s,0n),+0s,2299161j)>, :name=>"山の日", :regions=>[:jp]},
 {:date=>#<Date: 2022-09-19 ((2459842j,0s,0n),+0s,2299161j)>,
  :name=>"敬老の日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-09-23 ((2459846j,0s,0n),+0s,2299161j)>,
  :name=>"秋分の日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-10-10 ((2459863j,0s,0n),+0s,2299161j)>,
  :name=>"スポーツの日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-11-03 ((2459887j,0s,0n),+0s,2299161j)>,
  :name=>"文化の日",
  :regions=>[:jp]},
 {:date=>#<Date: 2022-11-23 ((2459907j,0s,0n),+0s,2299161j)>,
  :name=>"勤労感謝の日",
  :regions=>[:jp]}]

「たしか小規模なプロジェクトでこのあたりのgemを使った覚えがあります↓」

holiday-jp/holiday_jp-ruby - GitHub

「休日は年によって変わるのでちゃんと扱おうとすると大変: たしか日本の祝日は前年の特定の時期に決まるんじゃなかったかな」「この間のオリンピックでも紙のカレンダーが使えなくなったりしましたよね」「そうそう」

参考: 国民の祝日 - Wikipedia

「以下のphonelibなんかもそうですが、こういうふうにデータを内蔵しているgemを使うときは、その後のデータ更新に追従する覚悟が必要」「そうそう、GitHubのDependabotがちょくちょく知らせてきたりしますよね」「単なる追加更新ぐらいならまだしも、国が増えたり減ったりする可能性もあるので油断できない」

daddyz/phonelib - GitHub

参考: Configuring Dependabot security updates - GitHub Docs

🔗 その他Rails

つっつきボイス:「jnchitoさんの記事: 本を端から読んで全部理解しようとするよりも、最初は脳内インデックスを作るつもりで、読み飛ばしながらでも一度最後まで読む方がいいと自分も思います」「そうそう、知らないものは探しようがないですよね」「学生の頃はオライリー本を意味もわからないままとにかく最後まで読んでました」「その後も定期的に脳内インデックスと用語を更新するのが大事: この週刊Railsウォッチのつっつき会がちょうど自分にとってそういう場になっていますね」


「2つ目のツイートの動画とスライドはRailsの例外処理の話で、Qiita記事にjnchitoさんがコメントを付けています」「元記事の冒頭にあるような書き方はたしかにつらいですね: トランザクションブロックの中でDBトランザクションに関係のないリダイレクトを行うと、例外発生時に想定外のロールバックが発生して処理がどう進むかを追うのが難しくなるので、自分なら少なくともrespond_toはトランザクションの外に書きます」「あ、たしかに」「トランザクションを書くとエラー時にロールバックするようになって処理が複雑になるので安易に書くべきではないと思いますし、書くとしても基本的にトランザクションの中にDBトランザクション以外の処理はなるべく書かない方がよいと思います」

「そしてツイートでも指摘されているように、rescueも安易に使うとロールバックが握りつぶされるので要注意」「たしかに」「やむを得ず使う場合でも、どのエラーオブジェクトをrescueするかをしっかり指定する必要があります」

参考: 制御構造 (Ruby 3.1 リファレンスマニュアル)

「そういえば例外ツリーの根っこにあるExceptionクラスをrescueで指定するのはよくないという記事を見たことがありました」「単なる投機的な処理のためにExceptionを指定してすべての例外を雑に拾う程度ならさして問題ではないかな(その分どこで例外が発生したかを突き止めるのが大変ですが)」「なるほど」「でも、たとえばメール送信のような重要な処理を含む場合にExceptionクラスを指定するのは本当にヤバいのでやめるべき: 最悪の場合本来行うべき例外処理が行われなくなったり例外ログが適切に出力されなくなったりしてしまいます」

参考: class Exception (Ruby 3.1 リファレンスマニュアル)


前編は以上です。

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

週刊Railsウォッチ: CRuby 3.2にオブジェクトシェイプがマージ、Cloudflare R2ほか(20220927後編)

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

Rails公式ニュース

Ruby Weekly


CONTACT

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