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

週刊Railsウォッチ: ServerTimingミドルウェア追加、paramsで数値キーを許可、Railsで多要素認証ほか(20211011前編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

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

先週の更新情報で取り上げなかったものから見繕いました。

🔗 ServerTimingミドルウェアが追加

概要

server timingとは
サーバーが応答中のリクエストに関するuser agentのパフォーマンスメトリクスの伝え方を定義する仕様。
公式な仕様: Server Timing
しくみ
これはデフォルトではdevelopment環境のみで利用できるServerTimingミドルウェアを新たに導入する。これはconfig.server_timingの設定で行われる。
すべてのActiveSupport::NotificationsへサブスクライブしてServer-Timingヘッダーにduration(所要時間)を追加する形で行われる。
これが便利な理由
特にChrome devtoolsのNetwork Inspectorにメトリクスが表示されることで、development環境でのパフォーマンスメトリクスが非常に簡単になる。以下はその例:

これは@guilleiguaranとの共同作業で行われた。
同PRより


つっつきボイス:「いいねがいっぱい付いていました」「Server-Timingヘッダーという仕様があるんですね: Railsのpub/sub機能で取った結果をレスポンスのServer-Timingヘッダーに追加できるというのはなかなか面白い!」「これは?」「Chrome DevToolsなどを使うと、上のスクショにあるようにRailsのどの部分でどのぐらい時間がかかったかを表示できるようになるんですよ」「これは便利そう!」「development環境限定なんですね」

参考: Active Support の Instrumentation 機能 - Railsガイド
参考: Server-Timing - HTTP | MDN

「こういう機能は普通に開発で便利だと思います」「どんなふうに便利になるんでしょうか?」「Railsのログはあらゆるリクエストが分離されずに出力されるので見づらいんですよ: たとえばファビコンの404やヘルスチェックのリクエストがたくさん混じるとか」「ふむふむ」「この機能を使えば、Chrome DevToolsのNetworkタブ↓で言うとこれらの1個1個のリクエスト単位でRailsのログを見られるようになる、これは便利👍」「あ〜なるほど!」

「この機能はServer-Timingヘッダーを使うので、Rails側のduration(処理時間)を表示するためのものということになりますが、これが可能ということは、それ以外のRails側のデバッグ情報をHTTPレスポンスに付けて返すことも原理的には可能でしょうね」「たしかに」「Server-Timingヘッダーを扱えるChromeアドオンを作ればブラウザでそうした情報を手軽に見られるようになるんじゃないかな」「夢が広がりますね」

🔗 link_toのリンク名をModel#to_sで推測する

概要

Railsを使い始めてからずっと、モデルへのリンクを以下のように書けたらよいのにと思っていた。

link_to @profile

上を書くと以下のようにレンダリングされる。

<a href="/profiles/1">Eileen</a>

このプルリクはこれを実現する。モデルでto_sメソッドが定義されていれば、以下のように書く必要はなくなる。

link_to @profile, @profile

あるいは以下も不要になる。

link_to @profile, @profile.name

その他情報

もともとRailtieプラグインgemとして実装したものだが、ご想像のようにgemでurl_forの内部をいじるのは神経を使う。それよりもこれをオプションとして存在させれば、その後あまり使われなくなったときでも陳腐化するgemにこの機能を置くよりAction Viewに置く方がずっといいと思う。
このプルリクを出すということは、Railsチームにこの素晴らしい機能をメンテしてくれと言っているようなものだと認識している。しかし自分は喜んでメンテをサポートするつもりだ。
同PRより


つっつきボイス:「なるほど、適切な表示名を返すto_sが実装されていればlink_to @profileで第2引数にリンク名をわざわざ書かなくてもリンク名の文字列が出力されるようになった」「これも便利そう❤️」「前からあったような気もするけど、なかったんですね」

🔗 ビューのhiddenフィールドにautocomplete="off"を追加する

概要

Firefoxには、autocomplete="off"が付いていないhiddenフィールドに完全にランダムな値が入ってしまうという長年修正されていないバグ(#520561)がある。RailsはhiddenフィールドをCSRF保護や_method経由の非標準HTTPメソッドなどで多用しているので、他のブラウザでは問題なRailsアプリをFirefoxユーザーが操作すると"Invalid Authenticity Token"エラーがランダムに発生し、フォーム入力がHTTPメソッドとして無効と解釈されるなど予期しない動作になる。autocomplete="off"を追加しても他のブラウザで不具合は発生しないようだし、実際に有効なHTMLである。詳しい議論については#42610を参照。

最近自分はこの回避方法をRails 6.1用のrails-hidden_autocomplete gemにまとめた。このバグは現実のアプリでの診断や修正が非常に面倒なので(podqueue/rails-hidden_autocomplete#2を参照)、この本PRではこれに手を加えてすべてのRailsユーザーおよび開発者がメリットを得られるようにした。

その他情報

この変更は、Action Viewの新しいフレームワークのデフォルトの背後にゲートを置く必要がある可能性も考えられることを認識している。これについてコンセンサスを得られれば、喜んで追加作業を行いたい。
同PRより


つっつきボイス:「Firefoxで長年直っていないバグとは」「issueが12年前からオープンのままで↓、しかも全然盛り上がってませんね」

参考: 520561 - Autocomplete is too aggressive and overwrites values of hidden fields.

「Railsはhiddenフィールドをたくさん使っているから、知らないうちに変わる可能性があるのはたしかに困る」「autocomplete="off"属性をわざわざ付けるのはいかにも冗長だけど、Firefoxが対応していないならしょうがなさそう」「hiddenフィールドにランダムな値が入ってしまうことがあるらしいけど、任意の値が入るよりはマシか」

参考: HTML の autocomplete 属性 - HTML: HyperText Markup Language | MDN
参考: hidden - HTML: HyperText Markup Language | MDN

「それにしてもこのバグを踏んだ覚えがないな〜」「社内Slackでこのバグのこと聞いてみたけど、やはり踏んだことはないという回答ですね」

🔗 外部キー追加でdeferrable: trueを指定可能に(PostgreSQLのみ)

概要

デフォルトでは、PostgreSQLの外部キー制約はステートメントごとにチェックされる。これはほとんどのユースケースで有効だが、データベースに親レコードがINSERTされる前に関連レコードを作成するときは大きな制約となる。

ひとつの例として、1つ以上の一意のエイリアスを経由してpersonを探索または作成する場合を考える。

Person.transaction do
  alias = Alias
    .create_with(user_id: SecureRandom.uuid)
    .create_or_find_by(name: "DHH")

  person = Person
    .create_with(name: "David Heinemeier Hansson")
    .create_or_find_by(id: alias.user_id)
end

デフォルトの振る舞いでは、最初のINSERT実行時にトランザクションが失敗する。
このプルリクは、マイグレーション中のadd_foreign_keyステートメントに新しいオプションを追加して、「deferrable(先延ばし可能)」な外部キー制約をサポートする。

add_foreign_key :aliases, :person, deferrable: true

deferrable: trueはデフォルトの振る舞いになるが、トランザクション内でSET CONSTRAINTS ALL DEFERREDを用いて手動でdeferをチェックできる。これにより、外部キーはトランザクションの後でチェックされるようになる。
デフォルトの振る舞いを、(ステートメントの後の)即時チェックから、(トランザクションの後の)deferredチェックに変更することも可能。

add_foreign_key :aliases, :person, deferrable: :deferred

同PRより


つっつきボイス:「外部キーの関連先がない状態で先行してINSERTしたいという話: この間BPSの社内勉強会でも似たような話をしたかも」「ときどきとても欲しくなるヤツですね」

「外部キー追加時にdeferrable: trueを指定すると、通常だと制約に反するINSERTも可能になる」「PostgreSQLのみの機能なんですね」「トランザクションが終了した時点で整合性が取れていないとまずそうだけど、どんなSQLでやっているのかな?」

参考: PostgreSQL 13 ドキュメント SET CONSTRAINTS

「カラムでdeferrable: trueを指定すると、指定されたカラムが更新されるときにトランザクション内でSET CONSTRAINTS ALL DEFERREDが効くんですね」

🔗 数値のパラメータを許可できるようになった

数値パラメータを指定する場合、strong parametersを使えば、それぞれに同じpermittedパラメータを用いてすべてのパラメータを利用できる。
たとえば以下のようなparamsがあったとする。

book: {
        authors_attributes: {
          '0': { name: "William Shakespeare", age_of_death: "52" },
          '1': { name: "Unattributed Assistant" },
          '2': "Not a hash",
          'new_record': { name: "Some name" }
        }
      }

これは以下でpermittedにできる。

permit book: { authors_attributes: [ :name ] }

これは、nameフィールドを持ちキーが数値であるparamsでそれぞれのnameキーを返す。

{ "book" => { "authors_attributes" => { "0" => { "name" => "William Shakespeare" }, "1" => { "name" => "Unattributed Assistant" } } } }

ほとんどの場合、これがやりたいことになる。しかしまれに特定の数値属性で異なるキーを指定する必要が生じることもある。このプルリクは、そういう場合のための別のstrong parameters構文を利用可能にすることで、個別の数値属性ハッシュで許されているキーを指定できるようになる。
この変更後に上と同じparamsを用いると、0キーでは名前と年齢のみを、1キーでは名前のみを許可できる。

permit book: { authors_attributes: { '1': [ :name ], '0': [ :name, :age_of_death ] } }

これは指定したとおりのパラメータを返す。

{ "book" => { "authors_attributes" => { "0" => { "name" => "William Shakespeare", "age_of_death" => "52" }, "1" => { "name" => "Unattributed Assistant" } } } }

メモ: これによりpermitで以下と同等のことができる。

params.require(:book).permit(authors_attributes: { '1': [:name]})

これはbook: ...が存在しない場合にraiseしない。
よりシンプルな構文が好ましいが、もっと制御が必要な場合はこのオプションがあるとよい。
同PRより


つっつきボイス:「お〜こういうネステッドなパラメータを投げたいことはあるのでありがたい、しかもちゃんとフィルタリングされる」「数値だけ許可できるのが面白いですね」

「HTMLフォームで投げるパラメータには文字列しか使えないんですが、以下のようなパラメータを投げることは可能: そういうときにキーが1という数値の場合はnameだけを、キーが0という数値の場合はnameage_of_deathだけを許可するというふうに取り出せる」「あ〜なるほど」「今まではこういう処理を手書きしていたんですが、それをしなくて済むのはいい👍」「これは知ってたら使いたいですね」

book: {
        authors_attributes: {
          '0': { name: "William Shakespeare", age_of_death: "52" },
          '1': { name: "Unattributed Assistant" },
          '2': "Not a hash",
          'new_record': { name: "Some name" }
        }
      }

🔗Rails

🔗 DHHのHotwire記事(Ruby Weeklyより)


つっつきボイス:「DHHが最近公開したブログ記事です」「そうそう、HotwireがどういうものかをDHH自ら明らかにしてくれてスッキリしました」「要するにStimulus 3とTurbo 7なんですね」

「HotwireそのものはRubyやRails以外の環境でも使えるので、ライブラリというよりはプロトコルという位置づけに近い」「そういえばHotwireの発表当時もRailsに限定しないと言ってましたね↓」「PHPとかですね」

速報: Basecampがリリースした「Hotwire」の概要

「それもあってか、DHHはこれまでStimulus 3 + Turbo 7という言い方をしてなかったんですが、要するにHotwireが何なのかを知りたいという声が増えてこういう記事を書いたんじゃないかなという気がしています」「なるほど」

「DHHがRails 7をimport map↓でやることに決めたのもかなりの英断だったと思います」「以前Webpackerが入ったときにも驚いたのをちょっと思い出しました」「Webpackerのときは、フロントエンジニアに広く使われているWebpackをWebpackerという層で取り込むというRails独自性が強い形でしたが、import mapはブラウザの機能で実現しているのと、フロントエンドに選択肢があるというところが違っていますね」「なるほど」

Rails 7: import-map-rails gem README(翻訳)

「DHHのやりたいことに世の中が追いついてきたという見方もできるかな」「そんな気もしますね」「そういえば最近Babelをあまり使わなくなりましたよ: 今ならES2015に変換しなくてもネイティブJSでconstとかを余裕で使えるようになってきましたし」「たしかに」「やはりIEの終了が大きいですね」

babel/babel - GitHub

🔗 appraisal: gemのバージョンを切り替え

thoughtbot/appraisal - GitHub


つっつきボイス:「appraisalは以下の記事で見かけました」「thoughtbotさんのgemですね」

# 同リポジトリより
appraise "rails-3" do
  gem "rails", "3.2.14"
end

appraise "rails-4" do
  gem "rails", "4.0.0"
end

「CIでgemのバージョンを切り替えて両方テストするときに便利だそうです」「こういうふうに指定できるのか、なるほど↓」「元記事ではRailsをアップグレードするときにappraisalが重宝したそうです」「環境変数でgemを切り替えれば同じようなことはできるかなと思いますが、専用のgemがあるのはよさそう👍」

$ bundle exec appraisal rails-3 rake test

「個人的にはappraisalという名前がもうちょい短いと嬉しいかも」「コンソールでしょっちゅう入力するからそうですね」「bundle execでも長いと言われるぐらいなので、エイリアスのbeにしたりしますよね」「私もやってます」

「ちなみにappraisalを辞書で引くと『(慎重な)品定め』『査定』という意味でした」「大学受験とかで文脈から意味を推測する問題に出てきそう」「いかにもお固い言葉っぽいですよね」「語幹にpraiseが入ってるので『称賛』に近いのかと思ったら全然違ってました」

🔗 Railsで多要素認証(Ruby Weeklyより)


つっつきボイス:「WebAuthnとDeviseで多要素認証する記事、具体的にみっちり書かれていてよさそう👍」

参考: 多要素認証 - Wikipedia
参考: WebAuthn - Wikipedia
参考: YubiKey - Wikipedia

「Deviseの記事を久しぶりに見た気がします」「みんなDeviseのWikiで足りるようになったんでしょうかね?」「DeviseのWikiは古い記述も混じっているので要注意」「げ、そうでしたか」

参考: Home · heartcombo/devise Wiki

「ですから今でもDeviseの新しい記事を書いたら喜ぶ人は多いと思いますよ」「なるほど」「問題はDeviseの内部構造を詳しく説明できる人が少ないことでしょうね」「う〜む」「Deviseにはwardenも統合されていたりして相当複雑なんですよ: もちろんWebAuthnのような新しいものを追加可能にはなっています」

wardencommunity/warden - GitHub

「認証コードを手書きするのは本当に避けたい」「ですよね」「認証コードは失敗したときにつらすぎるので、実績のあるライブラリを使いたいですね」

🔗 その他Rails


つっつきボイス:「これはとてもいい記事でした: GitLabのことをちゃんとわかっている人が書いている👍」「お〜」

「GitLabの経営面のことを全然知りませんでした」「GitLabに投資がものすごく集まっているのはかなり前から有名ですね: だからこそ開発を手厚く継続できていますし、ワールドワイドで投資を募るようになってから資金調達が半端なく大きくなったんでしょうね」

「記事にもあるGitLabのハンドブック↓も有名: 組織編成、評価制度、国別の給与額の違いなどもひととおりここで公開されています」「これも知りませんでした😅」「すごいですね」

参考: Handbook | GitLab

「今は似たようなことをやっている会社も増えましたけど、GitLabはその先駆けのひとつだと思います」「こういう制度設計に早い時期から力を入れていたんですね」「こういう仕組みは会社がある程度以上大きくなってくると大なたを振るうのは非常に難しいんですよ」「たしかに」「GitLabはそれらを成し遂げたことが高く評価されていて、投資家や経営者やスタートアップ向けのGitLab関連本も結構出ています」

「記事にもありますが、GitLabは以下のようなリモートワークのガイドや企業文化の公開↓などにも力を入れています」「なるほど」「GitLabを単なるGitHubのクローン的なものと考えるのは早計だと思います」

参考: All Remote | GitLab
参考: GitLab Culture | GitLab


前編は以上です。

バックナンバー(2021年度第4四半期)

週刊Railsウォッチ: ruby/debug 1.2.0リリース、Railsにはthorが入っている、tendejitほか(20211006後編)

今週の主なニュースソース

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

Rails公式ニュース

Ruby Weekly


CONTACT

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