- Ruby / Rails関連
週刊Railsウォッチ: Packwerkの詳しい解説書『Gradual Modularization for Ruby and Rails』ほか(20221101前編)
こんにちは、hachi8833です。Kaigi on Rails 2022の動画がYouTubeにアップロード完了しました🎉
参考: Kaigi on Rails 2022 - YouTube
My Kaigi on Rails talk is up! All about queueing in the context of Rails: application, GVL, and Sidekiq. https://t.co/U98hAsiXIb
— Nate Berkopec (@nateberkopec) October 31, 2022
🔗Rails: 先週の改修(Rails公式ニュースより)
🔗 同一レコードでafter_commit :destroy
の重複トリガーを解消
この変更は、以下のようにデータベース内のレコードが
destroy
された場合にのみafter_commit :destroy
をトリガーするようにする。record = MyModel.find(42) record.destroy # => after_commitコールバックをトリガーする record.destroy # => after_commitコールバックを「トリガーしない」
従来は#27248で
after_commit :destroy
の振る舞いが更新され、同じレコードのインスタンスが2つあって一方がdestroy
されると、他方のレコードのdestroy
はafter_commit
コールバックでトリガーされなかった。しかし1つ目のレコードの以後のdestroy
は引き続きトリガーされるようになっていた。この変更によって現在は以下のように振る舞っている。record = MyModel.find(42) same_record = MyModel.find(42) record.destroy # => after_commitコールバックをトリガーする record.destroy # => after_commitコールバックを再度トリガーする same_record.destroy # => after_commitコールバックを「トリガーしない」
同PRより
つっつきボイス:「これは同じレコードでafter_commit :destroy
が複数回呼ばれたときにそもそもどう振る舞うべきかという話でもあるでしょうね」「改修前と改修後の振る舞いの違いがコメントにありましたけど、こんなふうに変わるんですね↓」「たしかにdestroyが2回呼ばれるのはよくないけど、そういうときはエラーにしてもいいんじゃないかなという気持ちはあります」
✅:
after_commit(on: :destroy)
コールバックをトリガーする
❌:after_commit(on: :destroy)
コールバックを「トリガーしない」現在の振る舞い
new_record?
persisted?
destroyed?
DB行が存在 ✅ ✅ ✅ DB行が不在 ✅ ❌ ✅ 私の変更提案
new_record?
persisted?
destroyed?
DB行が存在 ❌ ✅ ❌ DB行が不在 ❌ ❌ ❌ #46197のコメント(bensheldon)より
「元の振る舞いがアプリケーションのコードで当てにされていたりするでしょうか?」「同じレコードインスタンスを2回destroyすることは普通ないかなとも思ったけど、アプリのつくりによっては削除ボタンを連打したりすると起きる可能性もありそうですね」「なるほど」
🔗 ErrorReporter
で複数のエラークラスを扱えるようになった
動機/背景
ErrorReporter
は、以下のようなエラーハンドリングコードの定型文を置き換えるのが目的。begin do_something rescue SomethingIsBroken => error MyErrorReportingService.notify(error) end
上を以下の統一的なインターフェイスに置き換える。
Rails.error.handle(SomethingIsBroken) do do_something end
ただし、以下のように複数のエラークラスを処理できるようにしたいこともある。
Rails.error.handle(ArgumentError, TypeError) do [1, 2, 3].first(x) # ただし`x`は`-4`や`'4'` の可能性もある end
このプルリクは、エラークラスのリストを
Rails.error.handle
やRails.error.record
に渡せる機能を追加する。後方互換性も完全に維持される。
同PRより
つっつきボイス:「assert_raises
にエラーを複数渡すとき↓みたいなノリで、ErrorReporterにエラーのクラスを複数渡せるようになったんですね」「ErrorReporterはRails 7で入った機能でしたね(ウォッチ20211129)」
# activesupport/test/error_reporter_test.rb#71
test "#handle can be scoped to several exception classes" do
assert_raises ArgumentError do
@reporter.handle(NameError, NoMethodError) do
raise ArgumentError
end
end
assert_equal [], @subscriber.events
end
test "#handle swallows and reports matching errors" do
error = ArgumentError.new("Oops")
@reporter.handle(NameError, ArgumentError) do
raise error
end
assert_equal [[error, true, :warning, "application", {}]], @subscriber.events
end
参考: Minitest API Method: Minitest::Assertions#assert_raises
— Documentation for minitest (5.16.3)
参考: Rails API ActiveSupport::ErrorReporter
🔗 ciphertext_for
が暗号化前の値を返す問題を修正
このコミット以前の
ciphertext_for
は、永続化されていないレコードなどで暗号化される前の平文テキスト値を返していた。Post.encrypts :body post = Post.create!(body: "Hello") post.ciphertext_for(:body) # => "{"p":"abc..." post.body = "World" post.ciphertext_for(:body) # => "World"
このコミットは、
ciphertext_for
が常に暗号化済み属性のテキストを返すように修正する。Post.encrypts :body post = Post.create!(body: "Hello") post.ciphertext_for(:body) # => "{"p":"abc..." post.body = "World" post.ciphertext_for(:body) # => "{"p":"xyz..."
同PRより
つっつきボイス:「例のActive Record暗号化機能ですね」「暗号化されていない値が返されていたのは明らかにバグ」
参考: Rails API ciphertext_for
-- ActiveRecord::Encryption::EncryptableRecord
🔗 save
後の不要なserialize
呼び出しを回避
- PR: Avoid unnecessary
serialize
calls after save by jonathanhefner · Pull Request #46231 · rails/rails
レコードを
save
すると、レコードの属性ごとにActiveModel::Attribute#value_for_database
を呼び出し、value_for_database
はserialize
を呼び出す。レコードの保存が成功すると、ActiveModel::Attribute#forgetting_assignment
がその属性をリセットするが、ここでもvalue_for_database
が呼び出される。つまり属性はsave
の直後に不要なserialize
を再実行していることになる。このコミットは、
value_for_database
をメモ化することでsave
後にserialize
が2回呼び出されないようにする。value
は真の情報の唯一の根拠だが、その場で変更される可能性があるため、このメモ化ではvalue
がメモ化済みの@value_for_database
と一致しない場合は慎重にチェックするようにしている。これによって
save
のパフォーマンスが少し向上し、ほとんどの型でvalue_for_database
読み出しを繰り返すときのパフォーマンスが大幅に向上する。
同PRより
つっつきボイス:「こちらは最適化で、項目によっては倍以上速くなってるものもありました」「serialize
は呼び出される頻度が多いので最適化が効きそう👍」
🔗 ActiveRecord::QueryMethods#reselect
にもカラムやエイリアスを含むハッシュを渡せるようになった
動機/背景
最近#45612でActiveRecord::QueryMethods#select
にハッシュを渡せるようになった。このプルリクでは、ActiveRecord::QueryMethods#select
と同じ要領でActiveRecord::QueryMethods#reselect
にもハッシュを渡せるようにする。
詳細
@alextruemanが#45612で#select
に導入した機能と同様に、#reselect
メソッド内でカラムやエイリアスを含むハッシュを使える機能を追加する。
同PRより
つっつきボイス:「この間ActiveRecord::QueryMethods
のselect
にハッシュが渡せるようになっていましたね(ウォッチ20220926)」「改修はたった1行なんですね↓」「reselect
の引数の形式をselect
と同じにするために、ここではselect
のときに追加されたprocess_select_args
を呼んでいます」「なるほど」
# activerecord/lib/active_record/relation/query_methods.rb#407
def reselect(*args)
check_if_method_has_arguments!(__callee__, args)
+ args = process_select_args(args)
spawn.reselect!(*args)
end
参考: Rails API reselect--
ActiveRecord::QueryMethods`
🔗Rails
🔗 Railsで使えるminitest検証メソッドとAPIドキュメント
Qiita記事書きました。minitestでRailsをテストするときに使う、assert_ メソッドを一覧化して、さらにそれをどのgemが提供しているのかをまとめてみましたよ。
minitestでRailsをテストする際に使える検証メソッドの一覧と、そのメソッドに対応するAPIドキュメントのまとめ https://t.co/fslUrThBiW
— Junichi Ito (伊藤淳一) (@jnchito) September 11, 2022
つっつきボイス:「jnchitoさんの記事です」「assert_in_delta
という差分チェック用のアサーションがあるのか」「assert_in_epsilon
もあるんですね」「refute
よりもassert_not
を使うことが多いけど、refute_nil
とかは使いやすい」
参考: §2.6 利用可能なアサーション -- Rails テスティングガイド - Railsガイド
refute: 論破する、誤っているまたは不正確であると判明する
「複雑なモデルをテストするときはRSpecの方が記述の自由度は高いけど、単純なテストはminitestが書きやすくて好きです」
🔗 リアルタイム化の落とし穴
The slides from my #RailsConf talk on the pitfalls of real-time web apps with Rails:https://t.co/3G0ijGLswa
— Vladimir Dementyev (@palkan_tula) May 23, 2022
つっつきボイス:「Evil Martiansの中の人が今年5月のRailsConfで発表したスライドだそうです」「Action CableのPub/Sub周りの話ですね」「realtime-ificationという言葉がすごい」
参考: RailsConf on Notist
参考: Action Cable の概要 - Railsガイド
「あっさり目のスライドですね」「動画で見る前提なのかも」「Hotwireも使っているみたいですね」「HotwireとAction Cableの組み合わせは個人的にかなりよくできていると思っているので、もっと評価されて欲しい気持ち」「ですよね」
「HotwireとAction Cableの組み合わせはよいと思うんですが、サーバーのコードを書く人もブラウザのWebSocketを意識しておかないとデータ漏えいにつながったりするという面もあるので、正しく書くには結局RailsとJavaScriptを両方知っておく必要がありますね」「なるほど」「Hotwireの普及が遅いのはRubyとJavaScriptという2つの言語を書きたくないという気持ちもあるのかも🤔」「それもわかります」「昔ながらのマルチページアプリケーションは枯れていて安心感があるけど、Hotwireという解もあっていいと思います」
参考: WebSocket API (WebSockets) - Web API | MDN
「お、AnyCableの新バージョンも近々出るらしい↓」
「締めくくりの言葉が"安直な抽象化に惑わされるな"、"ツールを理解して落とし穴を避けよう"↓」
後で動画を見つけました↓。
🔗 書籍『Gradual Modularization for Ruby and Rails』
つっつきボイス:「まだ執筆中ですが、この間取り上げたShopifyのpackwerk(ウォッチ20220920、ウォッチ20201005)をフィーチャーした書籍だそうです」「段階的なモジュール化か」
「目次を見た感じでは俺たちが本当に欲しかったもの感あって期待できそう」「目次の"Enforce Visibility"や"Hide ActiveRecord"っていい言葉👍」「とりあえずポチってみたら進捗70%で170ページぐらいか、後で読んでみよう」
私も後でポチってみました↓。epubとpdfの両方をダウンロードできます。
🔗 その他Rails
また、今年はRails Girlsが日本で開催されてから、10年になります!
10周年を記念して、メッセージを募集しています。
Rails Girlsの思い出、意気込やこれから期待することなど、何でもOKです!#rgjp10th をつけてツイートしてくださいね。♥#railsgirls #railsgirlsjp #rggjp— Rails Girls Japan (@RailsGirlsJapan) October 2, 2022
つっつきボイス:「10年続くのってやっぱ凄い」「フィンランドのヘルシンキにある最初のrailsgirls.comも2010年からやっているそうなので、それに次ぐ歴史の長さですね」
参考: Rails Girls -- railsgirls.com
以下はつっつき後に見つけたツイートです。
Rubyコミュニティに憧れるばかりで前に進めなかった私が飛び込むきっかけになったのが仙台での #RubyKaigi とRails Girlsでした。
Rubyistとしてはまだよちよち歩きですが、これからも自分に出来ることを楽しくやっていきたいと思います! #rgjp10th— あのぶる+@1日1ACしたい🦥㌠ (@thatblue_plus) October 31, 2022
前編は以上です。
バックナンバー(2022年度第4四半期)
週刊Railsウォッチ: Ruby 3.2のData.define、RubyPrize 2022最終ノミネート、Puma-dev gemほか(20221026後編)
- 20221025前編 rodauth-rails gem作者の解説記事、turbo-railsの有料チュートリアルほか
- 20221019後編 Ruby技術者認定試験再受験無料キャンペーン、Starlink日本で販売開始ほか
- 20221018前編 Rails向けLanguage Server “refreshing”開発中、JetBrains Fleetほか
- 20221012後編 RailsとPostgreSQLで列挙型を作成する6つの方法、Ubuntu Proほか
- 20221011前編 Turbo 7.2.0リリース、GitLabのDevSecOpsサーベイ結果ほか
- 20221004後編 ヒアドキュメント拡張の提案、『組織に自動テストを根付かせる戦略』ほか
- 20221003前編 Kaigi on Rails 2022のタイムテーブル発表、書籍『Practicing Rails』ほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)
お知らせ: 来週は週刊Railsウォッチはお休みとし、通常記事を公開いたします。