こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
更新情報が2つ出ていたので、以下の中から取り上げていなかったものを見繕いました。
- 公式更新情報: Ruby on Rails — Strict template locals, detecting unused routes and an improved find_or_create_by
以下は次回に取り上げます。
🔗 テンプレートが受け取れるローカル変数をマジックコメントで定義可能になった
概要
このプルリクは、デフォルト値付きの必須引数をテンプレートで利用可能にするオプションを導入する。
今後この変更によって、初期化中にテンプレートをプリコンパイルする機能も利用可能になるだろう。問題について
Railsのテンプレートには任意のローカル変数を受け取れる暗黙のAPIがあるため、テンプレートの依存関係の推測が難しくなることがある。しかも、背後のAction Viewのコードでローカル変数をなまじ柔軟に利用できてしまうため、実行時に渡されるローカル変数の一意の組み合わせごとにテンプレートをコンパイルしなければならなくなる。
提案されたソリューション
RailsConfで雑談した@tenderloveや@jhawthornや@byrootとともに、テンプレートのシグネチャをオプショナルのマジックコメントで利用可能にし、これらの引数をそのテンプレートのメソッドシグネチャとしてコンパイルするというアイデアを思い付いた。
これにより、将来はテンプレートを実行時ではなくアプリケーション起動時にプリコンパイル可能になる。詳しくは以下を参照。
- Precompiling Rails Templates - John Hawthorn
- Aaron PattersonのRailsConf 2019キーノート
- John Hawthornのスライド: Parsing and Rewriting Ruby Templates
例
変更前:
<%# issues/_card.html.erb %>
<% title = local_assigns[:title] || "Default title" %>
<% comment_count = local_assigns[:comment_count] || 0 %>
<h2><%= title %></h2>
<span class="comment-count"><%= comment_count %></span>
変更後:
<%# issues/_card.html.erb %>
<%# locals: (title: "Default title", comment_count: 0) %>
<h2><%= title %></h2>
<span class="comment-count"><%= comment_count %></span>
その他考慮すべき点
- 引数にオブジェクトのイニシャライザを用いて1個のテンプレートごとに1個のオブジェクトをコンパイルする
これは私たちがGitHubのViewComponentで構築してきたこととだいたい同じ。これは自分たちのところでは非常に効果的だったが、1個のテンプレートを1個のオブジェクトにラップすると、利用頻度が高い場合にオブジェクトのアロケーションが多数発生するという欠点があり、フォームヘルパーで問題が発生した。
- 暗黙のシグネチャコンパイル
理論上は、ASTをフルスキャンするとか新たな形の静的解析によってテンプレートのシグネチャを生成することも一応可能。しかし経験上これは困難であることが明らかだ。さらに、引数にデフォルト値を設定したり引数を必須にしたりすることはまだできない。
同PRより
つっつきボイス:「ビューのパーシャルに、以下のようにERBコメント形式でlocals
のシグネチャをマジックコメントとして書けるようになった↓、これは嬉しいかも」
<%# issues/_card.html.erb %>
<%# locals: (title: "Default title", comment_count: 0) %>
<h2><%= title %></h2>
<span class="comment-count"><%= comment_count %></span>
「これまでは、テンプレートで特定のローカル変数が渡されることが保証されない場合、コードでif defined?
でチェックするとか、以下のサンプルコード↓みたいにtitle = local_assigns[:title] || "Default title"
などと書かないといけなかったんですよ」「そうそう」「ビューの中にはそういうチェックロジックみたいなものをなるべく書きたくないので、渡していい引数をマジックコメントで書けばチェックしてくれるのは嬉しいですね👍」「これはいい」
<%# issues/_card.html.erb %>
<% title = local_assigns[:title] || "Default title" %>
<% comment_count = local_assigns[:comment_count] || 0 %>
<h2><%= title %></h2>
<span class="comment-count"><%= comment_count %></span>
「ところでlocals
って何でしょうか?」「ビューのrender
を呼ぶときに明示的にハッシュを渡せるオプションですね↓: 渡された側は普通のローカル変数として使える」
参考: Rails4 で render partial 部分テンプレートに変数を渡す(locals option)を使う時の注意点 - Qiita
「値をインスタンス変数で渡すとすべてのビューでグローバルに共有されてしまいますし、インスタンス変数を変更すると他の場所に影響する可能性も増えるので、なるべくインスタンス変数では渡したくない」「パーシャルにローカル変数として渡せば明示的になるし悪影響も防げるので、自分はあちこちで使われるパーシャルほど極力ローカル変数として値を渡すようにしていますね」「なるほど」「自分も普段からなるべくlocals
オプションで渡すようにしてます」
「パーシャル側にチェックコードを書きたくなかったので、これまではパーシャルにコメントの形でローカル変数の仕様を書いてました」「今回の改修でシグネチャをマジックコメントとして書くことで手軽にローカル変数をチェックできるようになったということですね」
参考: §3.4.4 ローカル変数を渡す -- レイアウトとレンダリング - Railsガイドより
🔗 find_or_create_by
でRecordNotUnique
エラーの場合にfind
をリトライするようになった
find_or_create_by
で一意性の制約に遭遇したときに2回目のfind
を行うようになった。これまでの
find_or_create_by
は、適切な制約が設定されていない場合に、重複レコードを作成するかActiveRecord::RecordNotUnique
で失敗するかのどちらかという競合状態が常に発生していた。
そういうときのためにcreate_or_find_by
が導入されたが、レコードが存在するケースがほとんどの場合はかなり無駄が多かった(INSERTはSELECTよりも多くのデータを送信しなければならず、データベースの負荷も増える)。一部のデータベースでは主キーがインクリメントされてしまうという望ましくないことも起きる。つまり、レコードがほぼ確実に存在することが期待される状況なら、
create
がActiveRecord::RecordNotUnique
で失敗したときはfind
をリトライすれば競合状態にはならない。ただし、これはそのテーブルに適切な一意性の制約が設定されていることが前提。設定されていない場合はfind_or_create_by
で引き続き重複レコードが作成されてしまう。Jean Boussier, Alex Kitchens
同Changelogより
つっつきボイス:「find_or_create_by
の競合状態を防ぐために、必要な場合だけfind
を2回実行するようにしたみたい」「2つの処理をまとめて行うメソッドはいろいろ注意が必要ですね」
参考: Rails API find_or_create_by
-- ActiveRecord::Relation
🔗 CHECK制約の削除でif_exists
オプションが利用可能になる
- CHECK制約の削除で
if_exists
オプションが利用可能になる
remove_check_constraint
にif_exists
を渡せるようになった。trueの場合はCHECK制約が存在しなくてもエラーを発生しない。
Margaret Parsa and Aditya Bhutani
同Changelogより
つっつきボイス:「remove_check_constraint
にif_exists
オプションをつけられるようになった: やりたいことはわかる」「if_exists
をつけても大丈夫とわかっていれば念のため書いておくということができますね」
remove_check_constraint :products, name: "price_check", if_exists: true
参考: Rails API remove_check_constraint
-- ActiveRecord::ConnectionAdapters::SchemaStatements
🔗 マイグレーションにdrop_enum
コマンドが追加(PostgreSQLのみ)
- PostgreSQL向けの
drop_enum
コマンドがマイグレーションに追加
これはcreate_enum
とちょうど逆の動作。enumを削除する前に、そのenumに依存するカラムを削除しておくこと。
Alex Ghiculescu
同Changelogより
つっつきボイス:「PostgreSQL専用の機能」「以下の記事にはdrop_enum
はないと書かれていたんですが、ついにできたんですね」
# activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L492
+ def drop_enum(name, *args)
+ options = args.extract_options!
+ query = <<~SQL
+ DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
+ SQL
+ exec_query(query)
+ end
「PostgreSQLでDROP TYPE
ができるということはCREATE TYPE
もできるということ?」「あ、ありますね↓」「ほんとだ、PostgreSQLでは新しいデータ型を定義できるのか」
参考: CREATE TYPE
-- PostgreSQL 14.0文書
参考: DROP TYPE
-- PostgreSQL 14.0文書
🔗 routes --unused
で冗長なルーティングを検出
ルーティングの作成には時間がかかり、Railsアプリのルーティングが多すぎると起動が時とともに遅くなることがある。このスクリプトは、作成されているが実際は無効なルーティングを検出するのに使える。このスクリプトで検出したルーティングを削除すると、アプリの高速化や不要なコードの削除に役立てられる。
例:
> bin/rails routes --unused
Found 2 unused routes:
Prefix Verb URI Pattern Controller#Action
one GET /one(.:format) action#one
two GET /two(.:format) action#two
これをRailsに取り込む価値があるか、それともgemにすべきか判断に迷っている。この恩恵を得られるアプリは多そうだし、使っているのはprivate APIなので、ここに投げるのが一番適していると思う。いかがだろうか?
余談だが、このスクリプトを書いたきっかけは、とある大規模アプリに未使用ルーティングがあることに気づいたことだった。このスクリプトのおかげで無効なルーティングを100件以上発見でき、現在削除を進めているところ。このスクリプトによって多くの不要なコードが明らかになったので、他のアプリでも使えるようにするとよいと思う。
同PRより
つっつきボイス:「これはきっとみんなが欲しかった機能🎉」「使ってないルーティングってどこまで検出できるんだろう?」「url_for
とかで生成されるものあたりは検出できそうな気はする」「コントローラやビューで実装されていないものを探して知らせてくれる感じかな」
「ところで、このコマンドはrubocop-railsあたりにあってもいいかもしれませんね」「たしかに」「もちろんRails本体にあってもいいと思います」
🔗 番外: Changelogの書式をチェックするlinterを追加
# .github/workflows/lint.yml#L8
jobs:
+ changelog-formatting:
+ name: Check CHANGELOGs formatting
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ repository: skipkayhil/rails-bin
+ ref: 44270430c14385fd7db002b47f0819af5d824352
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: 3.1
+ bundler-cache: true
+ - uses: actions/checkout@v3
+ with:
+ path: rails
+ - run: bin/check-changelogs ./rails
...
つっつきボイス:「Changelogの書式が揃ってくれるとRailsリリース記事が書きやすくなるので個人的に嬉しい機能です」「DependabotのようにChangelogの内容を抽出して知らせるCI機能なども多いので、Changelogをプログラムで解析可能にしておくのは大事ですね👍」
参考: Keeping your supply chain secure with Dependabot - GitHub Enterprise Server 3.4 Docs
🔗Rails
🔗 Turbo関連の更新(Rails公式ニュースより)
- Release v7.2.0-beta.2 · hotwired/turbo
- PR: Introduce
turbo:frame-missing
event by seanpdoyle · Pull Request #445 · hotwired/turbo - PR: Activate
<script>
in Turbo Streams by seanpdoyle · Pull Request #660 · hotwired/turbo - PR: Preserve input values in cache by seanpdoyle · Pull Request #666 · hotwired/turbo
つっつきボイス:「Railsの更新情報のうちTurboに関連するものをこちらにまとめました」「Turbo v7.2.0-beta.2はいろいろ変更や改修が行われてますね」「Basecampが必要とする機能から順に実装されているんでしょうね」「Railsとバージョン番号が揃ってたらいいのに」
🔗 Hotwireを「プログレッシブエンハンスメント」で理解する
参考: プログレッシブエンハンスメント - Wikipedia
つっつきボイス:「プログレッシブエンハンスメントでHotwireを理解するという記事で、今翻訳中です」「そういえば万葉さんも引き続きHotwireを推していますね↓」
「Hotwireキャッチアップ体験談」https://t.co/4KRwqdsLMl@koheitakahashi_ がHotwireをどのようにキャッチアップしたのかと、その過程で感じたことをご紹介します。#万葉note #Hotwire
— 株式会社万葉 (@everyleaf) August 9, 2022
「今いるRailsエンジニアだけで作業するのであれば、HotwireやTurboはとてもいいと思います」「ですね」「強いて言うならですが、今の時点では事例が少なくてHotwireやTurboを使えるエンジニアを新たに探しにくいのと、Storybookみたいなフロントエンドの便利ツールがHotwireにはまだ少なそうなことぐらいかな🤔」
🔗 図解Redis
図がわかりやすいし良い / “Redis Explained” https://t.co/h0i52C9ZQZ
— YuheiNakasaka (@razokulover) August 15, 2022
つっつきボイス:「絵心のある人がRedisを図解付きで解説しているのがいいですね👍」「他にもデータベース基礎の図解入り記事とかいろいろ書いているようです↓」
参考: Things You Should Know About Databases
🔗 その他Rails
- 元記事: Railsをメジャーアップデートするときの基本の「き」 | srockstyle
- 元記事: RubyKaigi 2022 に永和システムマネジメントから @fugakkbn @koic @ima1zumi の 3人が登壇します - ESM アジャイル事業部 開発者ブログ
つっつきボイス:「今日はすろっくさんお休みですが、記事が出てました」「Railsをメジャーアップデートするときは、普通の手順を普通に進めるのが一番」
「永和システムマネジメントさんはRubyKaigiで3人も登壇するの凄い」「gem_rbs_collectionの話題があるんですって↓」「RuboCopにサーバーモードが追加されたんですね」「サーバーモードは、Railsのspring gemと同じような感じで大量のcopをプリロードすることでCIでの実行を高速化するのかなと想像してみました」
前編は以上です。
バックナンバー(2022年度第3四半期)
週刊Railsウォッチ: RubyのGVLトレーサーgvl-tracing、casting gemでオブジェクトに振る舞いを追加ほか(20220802後編)
- 20220801前編 “リーダブルテストコードについて考えよう”スライド公開、Evil Martiansが日本上陸ほか
- 20220726後編 中高生国際Rubyプログラミングコンテスト2022、W3Cの分散型識別子仕様が勧告にほか
- 20220725前編 RailsConf 2022の動画が公開、マイクロサービスのテスト戦略ほか(
- 20220719 RubyのGCが高速化、RuboCopのストレスを減らす4つの方法、Defensive CSSほか
- 20220711前編 AR::RelationにCTEを利用できるwithメソッドが追加、Propshaftアップグレードガイドほか
- 20220705後編 6月のRubyコア動向、Stack Overflowアンケート結果ほか
- 20220704前編 マイグレーションをStrategyパターンで拡張可能にほか
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)