Rails7が出てからそろそろ1年になりますね。
今回はそんなRails7の目玉機能?であったHotwireを触ってみようということで、いいねボタンを非同期で実装するということをやっていきたいと思います。
Hotwireとは
HotwireはRails7からRailsのフロントエンドのデフォルトとなった技術です。TurboとStimulusという2つのJSのフレームワークから構成されます。
詳しく知りたい方は、公式ドキュメントをご覧ください。
やってみよう
今回の環境は以下の通りです。
Ruby: 3.1.2
Rails: 7.0.3.1
今回の使用するgemは以下の通りです。
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# use haml
gem 'haml-rails'
# for component
gem "view_component"
イマドキRails勢揃いって感じですね。楽しくなってきました。
今回のモデル構造はこんな感じです。
erDiagram
  User ||--o{ Post : ""
  User ||--o{ Favorite : ""
  Post ||--o{ Favorite : ""
  User {
    number id
  }
  Post {
    number id
    number user_id
    string content
  }
  Favorite {
    number id
    number user_id
    number post_id
  }必要最小限って感じですね。
では、ここから実装していきましょう。
今回はあくまでViewの話がメインになるので、Model, Controllerは各々Rails wayに従ったスタンダードな構成があるものとします。
今回は、PostsController#showで返却されるViewに対してにいいねボタンを実装します。
いいねボタンはViewComponentとして切り出しているものとします。
ざっくり雑にこんな感じですね。
class Favorite::Component < ViewComponent::Base
  include Turbo::FramesHelper # これを読み込んでおかないとturbo_frame_tagでエラーになる
  def initialize(favorited: false, favorited_count: 0, path: '#')
    @favorited = favorited
    @favorited_count = favorited_count
    @path = path
  end
  def data
    @favorited ? { turbo_method: :delete } : { turbo_method: :post }
  end
  def icon
    if @favorited
      tag.i, class: 'active fas fa-heart' # 適当にactive感あるタグを書いてるつもり
    else
      tag.i, class: 'fas fa-heart' # 適当にactiveでないタグを書いてるつもり
    end
  end
end
= turbo_frame_tag 'post_favorites' do
  = link_to @path, data: data do
    = icon
  %span
    = @favorited_count
= @post.content
= render Favorite::Component.new(favorited: current_user.favorite_posts.include?(@post), path: post_favorites_path(@post), favorited_count: @post.favorites.count)
ポイントは以下の2つです
- Favorite::Componentでinclude Turbo::FramesHelperをしていること
- これをしておかないとcomponentのhaml内でturbo_frame_tagを呼び出すとエラーになります
 
 - turbo_frame_tagで影響範囲を明示的にしていること
- HotwireなRailsでは、turbo_frame_tag内のみに変更差分がある場合は自動でその差分のみを差し替えるようにしてくれます。
 - これのおかげで非同期的な処理として動作します
 - 他にも例えばflashメッセージを追加したい場合などは、turbo_streamという機能を使います。こちらはまた別途記事にします。
 
 
注意点として、今回のサンプルはpathとして渡している値がcontrollerの#createと#destroyで共通なことが前提のコードになっています。
例えば複数回いいねができるケースとかだとroutingが変わってくると思うので、その場合はfavorites_pathとunfavorites_pathなど、分けてcomponentに渡してあげてください。
参考までに、今回のroutes.rbも貼っておきます。
各postに対して1ユーザーから見るとfavoriteはひとつに定まるので、今回はresourcesではなくresourceでroutingを定義しています。
Rails.application.routes.draw do
  resources :posts, only: %i[show] do
    resource :favorites, only: %i[create destroy]
  end
end
これであなたもイケイケイマドキRailsエンジニアですね。
個人的には、Hotwireかなり熱いと思っていて、モノリポならRails!という時代に改めてなるんじゃないかな?くらい思っています。
Hotwireに関する記事をこれからどんどん書いていこうと思いますので、別記事が出たらまた読んでくださいね。
今日はこの辺りで。

![[Rails] view_componentというgemがすごく便利なので紹介します](/assets/ogp/rails-view-component.webp)