Rails: Action Viewのdom_idヘルパーは実は有能(翻訳)
本記事は、Boring Railsの新シーズンコンテンツ「Hotwire Summer」の一部です。
Railsのdom_id
ヘルパーは10年以上の歴史がありますが、Hotwireにおいてこの概念が貴重であることが明らかになりました。
参考: Rails API dom_id
-- ActionView::RecordIdentifier
この縁の下の力持ちは、RailsのあらゆるHTML関連の振る舞いを強化します。dom_id
の重要な機能は、アプリケーションのデータを手軽にDOM要素と関連付けることです。
dom_id
は、「レコード」とオプションの「プレフィックス」という2つの引数を受け取ります。
record
to_key
とmodel_name
に応答するオブジェクトなら何でも渡せます(99%はActive Recordモデルを渡すでしょう)。prefix
to_s
に応答するものなら何でも渡せます(99%はシンボルを渡すでしょう)。
以下はAPIドキュメントからの引用です。
dom_id(Post.find(45)) # => "post_45"
dom_id(Post.new) # => "new_post"
dom_id(Post.find(45), :edit) # => "edit_post_45"
dom_id(Post.new, :custom) # => "custom_post"
追記(2023/02/16)
Rails 7.1以降は、以下のようにdom_id
に渡すクラスをnew
せずに直接渡せるようになります(#46021)。
dom_id(Post) # => "new_post"
dom_id(Post, :edit) # => "edit_post"
このヘルパーが優秀な理由は、設定が自動的にRailsの規約に沿うからです。HTMLマークアップを識別する方法を手動で定義して後でそのパターンを頑張って思い出したりしなくても、Railsが標準の方法を提供しています。「idの形式は"post_23_comments"だったかな、それとも"comments-post-23" だっけ、いや"post_23-comments"かな?」などとコンテキストを切り替えながら調べる必要はもうありません。
要素のid値を安定して得られるので、最初の子要素をターゲットにしたりテキスト値ベースでノードを探索するといった壊れやすいコードを避けられるようになります。
以下では、Hotwireアプリを構築するときにdom_id
にアクセスすべき定番の部分について説明します。
🔗 1: タグビルダーをすっきりと書く
HTMLマークアップを真正性の根拠としてコードを書く場合、テンプレートをレンダリングするときに属性や条件式を追加することになります。ERBテンプレートで昔ながらの式展開で挿入する方法も一応ありますが、既にRailsには便利なtag
ビルダーが用意されているので、これを使えばコードが[]
や{}
や#
まみれになるのを避けられます。
<%= tag.div id: dom_id(@post, :comments), class: "flex flex-col divide-y" do %>
<%= render @post.comments %>
<% end %>
これらのヘルパーをさらに活用するときは、以下の小技集も合わせてチェックしておきましょう。
- VSCodeのTailwind Intellisenseプラグインをセットアップして、タグヘルパーの利用時にオートコンプリートが効くようにしましょう
- クラス名で条件付けするときは
class_names
helperを利用しましょう(React のclassNames
APIが由来) - よく使う要素はコンポーネントに切り出しましょう
- 軽量なヘルパーに切り出す
- ViewComponentを使う
🔗 2: アンカータグにディープリンクする
Railsはブラウザのネイティブ機能に強く依存しているので、リンクのアンカータグをぜひ活用しましょう。dom_id
を使えば、対応するid
を持つ(またはユーザーが共有可能な固定リンクを生成した)要素までブラウザをスクロールできるようになります。
<%= link_to "View comment", posts_path(@post, anchor: dom_id(@comment)) %>
このパターンはリダイレクトでも使えます。たとえば、リストに項目を作成した後リダイレクトしてindexページに戻ったときに、作成した項目までスクロールするようにできます。
class CommentsController < ApplicationController
def create
@post.comments.create!(comment_params)
redirect_to posts_path(@post, anchor: dom_id(@comment))
end
end
ディープリンク関連の小技をいくつか紹介します。
:target
擬似クラスを使えば、URLアンカーにマッチするidを持つ要素にスタイルを追加できます。Tailwindならtarget:
をプレフィックスするだけでできます。- 例:
target:bg-yellow-50
はURLアンカーにマッチする要素の背景を少しだけ黄色くする
- 例:
scroll-margin-top
というCSSプロパティも便利です。これを使うと、ブラウザが対象となる要素をウィンドウの一番上までスクロールします。- 少しパディングを追加したくなるかもしれませんが、ずれるのはその要素までスクロールしたときだけなので、画面デザインに余分なマージンやパディングを加えたり、変なラッパー
div
を追加したりしないこと。scroll-margin-top
(Tailwindの場合はscroll-mt
クラス)にお任せするのが答えです。
- 少しパディングを追加したくなるかもしれませんが、ずれるのはその要素までスクロールしたときだけなので、画面デザインに余分なマージンやパディングを加えたり、変なラッパー
🔗 3: Turbo Framesで使う
アプリケーションにTurbo Framesを追加し始めると、フレームタグ用のidを指定する必要が生じます。ご推察のとおり、Railsのturbo_frame_tag
も内部でdom_id
を使っていますが、以下のように独自のidを渡すことも可能です。
turbo_frame_tag @post # => <turbo-frame id="post_123"></turbo-frame>
turbo_frame_tag dom_id(@post, :comments) # => <turbo-frame id="comments_post_123"></turbo-frame>
Turbo Framesのフレームはページ内で一意でなければならないので、dom_id
ヘルパーはフレームidの生成にぴったりです。特にページ内に複数のフレームがある場合に有用です。
また、Turbo Framesは別のリンクをクリックしてフレームを操作できるので、以下の書き方に沿うのが定番です。
<%= turbo_frame_tag @comment, src: comment_path(@comment) %>
<!-- 他の場所 -->
<%= link_to "Edit", edit_comment_path(@comment), data: { turbo_frame: @comment } %>
🔗 4: Turbo Streamsのレスポンス
Turbo Streamsでページ内を小規模に改変する機能は非常に強力ですが、Turbo Streamsのアクションは別のturbo_stream.erb
ファイルにあるので、異なるビュー同士でidを一致させるのが面倒になることがあります。
ここでも、dom_id
を使えば以下のようにidを統一できます。
<!-- app/views/plans/quick_edit/update.turbo_stream.erb -->
<%= turbo_stream.replace dom_id(@plan, :title), partial: "plans/title" %>
<%= turbo_stream.replace dom_id(@plan, :notes), partial: "plans/notes" %>
<%= turbo_stream.replace dom_id(@plan, :assigned), partial: "plans/assigned" %>
dom_id
でマークアップに正しいidを追加しておけば、Turbo Streamsのレスポンスをこんなにすっきり書けます。
<!-- app/views/comments/destroy.turbo_stream.erb -->
<!-- 背後で`dom_id(@comment)`が呼ばれる -->
<%= turbo_stream.remove @comment %>
まとめ
シンプルなヘルパーでアプリケーションのモデルからHTML id
値を生成するという概念がこれほど有用だったとは、一体誰が想像できたでしょうか?しかもこのヘルパーは、導入後10年を経過してもなお、Railsで最も洗練された最新機能でも色あせることなく有用性を実証し続けているのです。
dom_id
を使ったことがなければ、次にRailsアプリのビューを書くときに検討してみてください。以下のようなコードを書き散らさなくてもHTMLをきれいに生成できることがどれほど快適かを実感したら驚くでしょう。
<div id='<%= "#{@post.id}-comments" %>'>
</div>
本記事が役に立つと思った方は、ぜひフォームでニュースレターの登録をお願いします。余分な文章を削ぎ落とした、読みやすく価値ある情報だけをニュースレターで配信いたします。
関連記事
Railsの技: Turbo Frameとskeleton loaderでHotwireのコンテンツをlazy loadingする(翻訳)
概要
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。