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

Rails: Action Viewのdom_idヘルパーは実は有能(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

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_keymodel_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 %>

これらのヘルパーをさらに活用するときは、以下の小技集も合わせてチェックしておきましょう。

🔗 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する(翻訳)

Rails: 5.1以降のtagヘルパー記法はcontent_tagより便利


CONTACT

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