- Ruby / Rails関連
週刊Railsウォッチ: 『RubyとRailsの何が強いのか』、書籍『Ruby on Railsステップアップ』ほか(20221213前編)
こんにちは、hachi8833です。ウォッチ20221129で紹介した"Railsに関わる技術の体系化を目指した本
"が、『Ruby on Railsステップアップ』というタイトルになってKindle Unlimitedで読めるようになりました🎉
参考: Rails初・中級者向けの技術の体系化を目指した本を書きました[追記・宣伝あり]
つっつきボイス:「早くもKindle Unlimitedで読めるようになった🎉」「Kindle Unlimitedはチェックアウトした本が上限に達するとどれかを返却する必要があるんですよね」「そうそう」「自分のKindle Unlimitedの上限を見たら20冊まででした」
参考: Kindle Unlimitedについて - Amazonカスタマーサービス
🔗Rails: 先週の改修(Rails公式ニュースより)
🔗 PostgreSQLのcitext
型カラムの検索時にlower()
を使わないようにした
動機/背景
lower()
を使うことで、インデックスが存在していてもインデックスの利用が妨げられてしまう。詳細
インデックスはuniquenessを持つカラムに存在するのが典型的だが、validates_uniqueness_of ..., case_sensitive: false
でlower()
が追加される。しかしインデックス自体が
lower()
ありで定義されていなければ、lower()
なしのクエリでインデックスが使われなくなってしまう。
検索エンジンでトップに出てきた資料でも、私たちのドキュメントでも、インデックスにlower()
を追加することは推奨されていない。驚き最小の原則に則り、通常の(
lower()
のない)インデックスを用いるために、検索クエリでlower()
を利用しないことを提案する。追加情報
インデックス定義にlower()
を追加しないとインデックスが効かないことに気づいて実直にlower()
を追加した人にとっては、この改修がパフォーマンス上のリグレッションと受け取られるだろう。
同PRより
つっつきボイス:「citext
のciって何だろうと思ったら、case insensitive(=大文字小文字を区別しない)の略なのか」「ぽすぐれにこんな型があったとは」「インデックスが作成されているcitext
型のカラムを検索する際に本来不要なlower()
を使って検索するとインデックスが効かなくなってしまうので、それを修正したんですね👍」
参考: PostgreSQL 14.5文書 F.8. citext
参考: PostgreSQL: Documentation: 15: 9.4. String Functions and Operators -- lower()
🔗 ActiveSupport::Inflector.transliterate
のパフォーマンスを改善
動機/背景
ActiveSupport::Inflector.transliterate
は、既にASCIIになっている文字列が渡される場合はまったく実行する必要がない。
これはActiveSupport::Inflector.parameterize
にも影響する。
解決されるissue: #46569
詳細
このプルリクは、文字列が既にASCIIの場合に高価な処理の実行を回避するためのチェックをtransliterate
に追加する。
追加情報
ベンチマークは以下を示している。
*transliterate
が実際に必要な場合(文字列に非ASCII文字がある場合)のパフォーマンスは変わらない
* 普通の長さのASCIIのみの文字列(30文字以内)の場合、現行のコードは20倍遅くなる。
* 非常に長いASCIIのみの文字列(2000文字以内)の場合、現行のコードは670倍遅くなる。
同PRより
つっつきボイス:「ActiveSupport::Inflector
は単数形と複数形の活用を行う機能」「personをpeopleに変換したりするヤツですね」
参考: Rails API ActiveSupport::Inflector
「transliterate
は?」「調べてみると、ü
をue
に変換したりö
をoe
に変換したりするんですって」「思い出した、アクサンやウムラウトみたいな記号を取り除いた文字に変換するメソッドでした」「音訳ということは文字から発音を得るということかな」「この改修では、既にASCIIになっていれば不要なtransliterate
をスキップすることで最適化したんですね👍」
# activesupport/lib/active_support/inflector/transliterate.rb#L64
def transliterate(string, replacement = "?", locale: nil)
- string = string.dup if string.frozen?
raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding)
+ string = string.dup if string.frozen?
+ return string if string.ascii_only?
input_encoding = string.encoding
参考: Rails API transliterate
-- ActiveSupport::Inflector
参考: アクセント符号 - Wikipedia
参考: ウムラウト - Wikipedia
transliterate: (…を)字訳する、書き直す、音訳する、(…を)(…と)書き直す
英語「transliterate」の意味・使い方・読み方 | Weblio英和辞書より
🔗 AbstractAdapter#lock
をデフォルトでスレッドローカルにする
修正: #45994
Ruby 3.0.2以来割とよくあるのが、トランザクション内でfiberを使うとデッドロックするという問題。
Post.transaction do enum = Enumerator.new do |y| y.yield Post.first # ここで詰まる end enum.next end
Rubyの#17827の変更によって、Monitorの所有者がThreadではなく、呼び出し元のFiberに変わったのが原因。
また、Active RecordのコネクションプールがThread単位のため、あるFiberが、他のFiberが所有するロックを取得しようとしても解決されないという状況になってしまう。
#46519では、このロックはシステムテストでしか必要とされていなかったので、ロックをオプショナルにした。
このプルリクは、Ruby 2.7までのMonitorのように振る舞う別のロック実装を導入する。
ActiveSupport::IsolatedExecutionState.context
がThreadの場合はこちらが使われる。
Fiberの場合は、Fiberベースのstdlibから派生した実装を引き続き利用する。
共著: @wildmaples
FYI: @eileencodes @matthewd @rafaelfranca @ngan @simi
同PRより
つっつきボイス:「先週に続いてスレッド絡みの改修ですね」「トランザクションでRubyのFiberがデッドロックすることがあったらしい」「あ〜、その処理がThreadで動いているのかFiberで動いているのかを区別してロック方法を分けないといけないのか↓」「言われてみればたしかに」
# activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L175
- def synchronized=(synchronized) # :nodoc:
- @lock = if synchronized
+ def lock_thread=(lock_thread) # :nodoc:
+ @lock =
+ case lock_thread
+ when Thread
+ ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new
+ when Fiber
ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
else
ActiveSupport::Concurrency::NullLock
end
end
参考: class Fiber
(Ruby 3.1 リファレンスマニュアル)
🔗 globalidのセキュリティ修正(現在オープン)
GlobalIDとSignedGlobalIDは、それら自身に
.find
メソッドがあるので、モデルとして指定できる。
SignedGlobalIDは内部でMarshalを使っているので、secret_key_base
が漏洩すると想定外のRCE(リモートコード実行)が発生する危険がある。
このプルリクは、GlobalIDとSignedGlobalIDが標的にされないようにする。
同PRより
つっつきボイス:「これはruby-jp Slackのセキュリティ関連チャンネルで見かけたプルリクです」「SignedGlobalIDの内部でMarshalが使われているので、#find
に対して想定外の任意文字列を渡せるようなコードを書いていると、そのキーの解決時にmarshalが展開されてRCEになるということのようですね」「今のところ特に動きはないようです」「この問題を実際に悪用して攻撃するには、おそらくGlobalIDに相当する部分に危険なコードを渡したうえで呼び出せるようにする必要がありそうなので、攻撃の難易度はかなり高いんじゃないかな」「なるほど」
# lib/global_id/global_id.rb#L53
def model_class
- model_name.constantize
+ model = model_name.constantize
+ if model <= GlobalID
+ raise ArgumentError, "GlobalID and SignedGlobalID cannot be used as model_class."
+ end
+ model
end
参考: module Marshal
(Ruby 3.1 リファレンスマニュアル)
🔗Rails
🔗 『RubyとRailsの何が強いのか』
つっつきボイス:「@knuさんが公開したスライドです」「RubyとRailsの強みと弱みについてまとまっていますね👍: 縛りが少ない分、制約の強制力も弱いというのもそのとおりだと思います↓」
「Railsは中身を詳しく知らなくても動かせるけど、自分の思い通りに正しくカスタマイズしようとすると、やはりそれなりに難しい」「そうですよね」
「"RuboCopはデフォルト設定だとつらい"は同意」「先週話題にしたMethodLengthなんかがそうですね(ウォッチ20221207)」
参考: Metrics/MethodLength :: RuboCop Docs
「このページ↓はBPS社内Slackでも他の言語の開発者から"これが鉄板構成なんですか?"という質問がありましたね」「そうそう、自分はこの構成が鉄板かどうかは人それぞれだと思います」
「@joker1007さんのツイートも納得↓」
現実を見据えた上でRailsを採用するには十分な理由がある、という話。割と誤解されがちだが、RubyとRailsの表現力や柔軟さは決して初心者向けではないんよね。でもちゃんとやれば高速に作れてそれなりにスケールする。モノリスとマイクロサービスのバランスを取る人が居れば強い。 https://t.co/84VZoilrww
— joker1007 (アルフォートおじさん) (@joker1007) December 3, 2022
つっつきの後で、『RubyとRailsの何が強いのか』は以下の「Qiita Night 〜Ruby〜」の発表だと社内で教わりました。
参考: Qiita Night~Ruby~ - connpass
さらに、その次の@QUANONさんによる発表で「Railsの情報をキャッチアップする方法」にTechRachoもリストアップいただいたことも教わりました(29:52)🎉。ありがとうございます!
🔗 RSpec記事2本
つっつきボイス:「いつの頃からか、Controller specよりRequest specの方が推奨になりましたね」「そういえばRequest specとController specの違いが当初よくわからなかった」「Controller specはコントローラ呼び出しのタイミングからテストが始まるけど、Request specは内部的に実際にリクエストを投げてテストするので、前後にミドルウェアが存在する場合もより現実に近い形でテストが動くはず」「なるほど」
参考: 「EverydayRails」のcontrollersのテストをrequest specで書き換える
書籍でも触れられていますが、RailsのControllerをテストする際にはRails4系統の時にはController specが用いられていましたが、現在ではそれらのメソッドは非奨励となりRequest specを用いたテストが奨励されているそうです。
「EverydayRails」のcontrollersのテストをrequest specで書き換えるより
「Controller specはHTTP 200かどうかのテストぐらいならさらっと書けるけど、それ以外のテストを書こうとすると結局request spec相当のテストの仕方をしないとちゃんと意味のあるテストになりづらいかなと思います: コントローラにセットされたVIEWに渡されるインスタンス変数のチェックなどはできるけど、それならレスポンスHTML自体をexpect
した方が安心できますね」
先日公開したQiita記事です。我ながらまたニッチな記事を書いてしまった……と思いながら公開しましたが、意外とニーズがあったみたいでちょっと驚いております😅
RSpecで遅いテストをふだんskipして、たまに全部実行する方法 https://t.co/0tLoebwBgU
— Junichi Ito (伊藤淳一) (@jnchito) December 2, 2022
「こちらはjnchitoさんのRSpec記事です」「遅いテストを普段はスキップする」「アプリの規模がある程度以上大きくなると、何らかの形でやりたくなりますね」
🔗 その他Rails
つっつきボイス:「The Rails Foundationのエグゼクティブディレクターですか」「読んでみたら普通に雇用募集だった」「報酬は年135,000ドル」
参考: Executive Director - The Rails Foundation -- apply.workable.com
前編は以上です。
バックナンバー(2022年度第4四半期)
週刊Railsウォッチ: JRubyが9.4.0.0でRuby 3.1に対応、IRB v1.5.0リリースほか(20221207後編)
- 20221206前編 月刊のHotwireニュースレター、pessimize gemほか
- 20221130後編 Ruby 3.2のParser目玉機能ほか
- 20221129前編 Hanami 2.0リリース、Railsに関わる技術の体系化を目指した本ほか
- 20221122 The Rails Foundation発足、Ruby 3.2.0 Preview 3リリース、Ruby演算子クイズほか
- 20221116後編 Rubyを使っている企業の時価総額リスト、irbのshow_source、GitHub Codespacesほか
- 20221115前編 RailsチュートリアルがRails 7対応版をリリース、ViewComponentで使えるLookbookほか
- 20221102後編 書籍『Programming Ruby 3.2 (5th Edition)』、ReDoSチェックサイトほか
- 20221101前編 Packwerkの詳しい解説書『Gradual Modularization for Ruby and Rails』ほか
- 20221026後編 Ruby 3.2のData.define、RubyPrize 2022最終ノミネート、Puma-dev gemほか
- 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ウォッチタグ)