🗒️

作成・編集機能をTurbo への実装置き換え

2024/11/24に公開

ここからは前回の記事で Hotwire と Rails をインストールしたプロジェクトで、DM 機能を実装しましたがその実装を Hotwire に置き換えていきます。

DMの編集機能実装(Non Hotwire)

その前にやりたいこととして、DM で送信したメッセージを編集する機能を実装しておきます。

これは実際に不要なのですが、Turbo frames を説明するためにも実装しておきます。

MessagesControllerは作成済みのため、update, editメソッドを作成し編集できるようにします。

config/routes.rb

resources :messages, only: %i[new create]
↓
resources :messages, only: %i[new create edit update]

とりあえず編集画面に遷移できるように編集ボタンを追加、controller のメソッドの追加。

app/views/rooms/show.html.erb:14-15

<%= link_to_if message.own_message?(current_user), '編集', edit_room_message_path(@room.id, message.id),
    class: 'bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded' %>

app/controllers/messages_controller.rb

  def edit
    @room = Room.find(params[:room_id])
    @message = Message.find(params[:id])
  end

  def update
    message = Message.find(params[:id])
    if message.update(save_params)
      flash[:success] = "Message updated!"
      redirect_to room_path(message.room)
    else
      flash[:error] = message.errors.full_messages.join(", ")
      redirect_to edit_room_message_path(message.room, message)
    end
  end

app/views/messages/edit.html.erb

<h2 class="text-2xl">メッセージ編集画面</h2>

<%= form_with(model: @message, url: room_message_path, class: 'my-8') do |form| %>
  <%= form.hidden_field :room_id, :value => @message.room_id %>
  <%= form.hidden_field :user_id, :value => @message.user_id %>
  <div>
    <%= form.label :content, "送信内容" %>
    <%= form.text_area :content, rows: 5, class: "border rounded p-2 w-full" %>
  </div>
  <div>
    <%= form.submit "編集する", class: "bg-blue-500 text-white py-2 px-4 rounded" %>
  </div>
<% end %>

この時点で編集ボタンをクリックしたら DM の編集画面に遷移ならびに編集が可能となります。

この編集機能は通常の Rails の遷移で実現された実装になっております。

編集機能をTurbo Framesへ置き換え

ここからは実際に編集機能を Turbo Frames に置き換えていきます。

まず、置き換えに伴い編集フォームについては、DM の内容と送信ボタンを横に並べたようなデザインとし、DM ルームで置き換わっても違和感ないように変更します。

app/views/messages/edit.html.erb

<%= form_with(model: @message, url: room_message_path, class: 'my-8') do |form| %>
  <%= form.hidden_field :room_id, :value => @message.room_id %>
  <%= form.hidden_field :user_id, :value => @message.user_id %>
  <div class="flex items-start justify-end">
    <%= form.text_area :content, class: "border rounded p-2 mr-4 w-3/5" %>
    <%= form.submit "編集する", class: "bg-blue-500 text-white py-2 px-4 rounded" %>
  </div>
<% end %>

こうすることでフォームとボタンが横並びになり、ルームページで表示しても違和感なくなりました。

次に frame_id を設定していきます。

frame_id を設定することでその id が一致したものが入れ替わるということになります。

まず、 app/views/rooms/show.html.erb の個別メッセージに turbo_frame_tagを設定します。

<%= turbo_frame_tag message do %>
  <div class="my-8 <%= message.own_message?(current_user) ? 'text-right' : 'text-left' %>">
    <p class="mb-1">
      <strong><%= message.user.name %></strong>
      <span class="text-gray-500 text-sm"><%= message.created_at.to_fs %></span>
      <p class="my-2"><%= message.content %></p>
      <%= link_to_if message.own_message?(current_user), '編集', edit_room_message_path(@room.id, message.id),
      class: 'bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded' %>
    </p>
  </div>
<% end %>

そして、 app/views/messages/edit.html.erb にも turbo_frame_tagを設定します。

<%= turbo_frame_tag @message do %>
  <%= form_with(model: @message, url: room_message_path, class: 'my-8') do |form| %>
    <%= form.hidden_field :room_id, :value => @message.room_id %>
    <%= form.hidden_field :user_id, :value => @message.user_id %>
    <div class="flex items-start justify-end">
      <%= form.text_area :content, class: "border rounded p-2 mr-4 w-3/5" %>
      <%= form.submit "編集する", class: "bg-blue-500 text-white py-2 px-4 rounded" %>
    </div>
  <% end %>
<% end %>

こうすることで、show.html.erb でクリックした編集ボタンは同じturbo_frame_tagの id を探しにいき、コンテンツを置き換える動作をします。

turbo_frame_tag messageturbo_frame_tag @messageとしてるのは、turbo_frame_tag に渡す引数2モデルを渡すと暗黙的に id とします。

バリデーションエラー対応

バリデーションエラーに対応できるように改修をします。

まずエラーが起きたら edit を render するようにします。

render :edit, status: :unprocessable_entity

その edit.html.erb は下記のようにフォームエラーを表示するようにします。

<%= turbo_frame_tag @message do %>
  <%= form_with(model: @message, url: room_message_path, class: 'my-8') do |form| %>
    <%= form.hidden_field :room_id, :value => @message.room_id %>
    <%= form.hidden_field :user_id, :value => @message.user_id %>
    <div class="flex items-start justify-end">
      <div class="w-3/5 mr-4 ">
        <%= form.text_area :content, class: "border rounded p-2 w-full" %>
        <span class="text-red-500"><%= @message.errors.full_messages_for(:content).presence&.join(',') %></span>
      </div>
      <%= form.submit "編集する", class: "bg-blue-500 text-white py-2 px-4 rounded" %>
    </div>
  <% end %>
<% end %>

content の text_area にはfield_with_errorsクラスが付与されるため、css で調整します。

.field_with_errors {
  @apply contents;
}

.field_with_errors textarea {
  border: 2px solid red;
  background-color: #ffe6e6;
}

こうすることでバリデーションエラーが起きた場合フォームは赤くなり、エラー内容が表示されます。

成功時の処理

現状の controller は、下記の通りです。

  def update
    @message = Message.find(params[:id])
    if @message.update(save_params)
      redirect_to room_path(@message.room)
    else
      render :edit, status: :unprocessable_entity
    end
  end

このままルームページにリダイレクトするので更新後の値が表示されるはずです。

flash はいらないので削除しました。

ここまでくるとインラインで DM を編集できるようになるはずです。

DMの新規送信をTurbo Streamで実装

ここからはすでに実装された DM 送信を Turbo Stream を使って実装していきます。

流れとしては以下です。

  • ルームページで新規作成ボタンを押したら登録フォームがインラインで表示される
  • 新規作成を押すとルームページの DM に追加される

新規ボタンを押すことで、messages_controller の new メソッドが呼ばれ、new.html.erb が呼ばれます。

これを DM ページで表示させるようにします。

まず、新規作成ボタンで以下の通りとしました。

<%= turbo_frame_tag 'new_message' %>
<div class="mt-24">
  <%= link_to "メッセージを作成", new_room_message_path(@room.id), class: 'bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded',
      data: {turbo_frame: 'new_message'} %>
  <%= link_to "リストに戻る", rooms_path, class: 'bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded' %>
</div>

link_to メソッドに data 属性の turbo_frame: 'new_message'を指定しています。

こうすることでリンクをクリックした際、turbo_frame の new_message を探しにいきます。

新規ボタンを押すことで、messages_controller の new メソッドが呼ばれます。
その後、new.html.erb が呼ばれる erb には、以下のように turbo_frame_tag をnew_messageで指定してます。

そうすることで、show.html.erb のnew_messageと置き換えるようになります。

<%= turbo_frame_tag 'new_message' do %>
  <%= form_with(model: @message, url: room_messages_path, class: 'my-8') do |form| %>
    <%= form.hidden_field :room_id, :value => @message.room_id %>
    <%= form.hidden_field :user_id, :value => @message.user_id %>
    <div>
      <%= form.label :content, "送信内容" %>
      <%= form.text_area :content, rows: 5, class: "border rounded p-2 w-full" %>
    </div>
    <div>
      <%= form.submit "送信する", class: "bg-blue-500 text-white font-bold py-2 px-4 rounded" %>
      <%= link_to 'キャンセル', room_path(@message.room_id), class: 'ml-2 border border-black py-2 px-4 rounded' %>
    </div>
  <% end %>
<% end %>

つぎに、新規作成を押すとルームページの DM に追加される実装をするのですが、messages_controller の create メソッドが呼ばれます。

ただし、今回は DM のリストに新規追加したものをリストとして増やしたいのと、入力フォームは消したいので、2 箇所を更新する必要があります。

そこで、turbo_frame ではなく、turbo_stream を利用します。

messages_controller の create メソッドは以下のようにします。

  def create
    @message = Message.new(save_params)
    if @message.save
      flash[:success] = "Message sent!"
      # redirect_to room_path(@message.room)
    else
      flash[:error] = @message.errors.full_messages.join(", ")
      redirect_to new_room_message_path(@message.room)
    end
  end

変更点としては、これまでは作成が成功したら DM ルームへリダイレクトしていましたがこれを辞めます。

その代わりに、create.turbo_stream.erbを作成します。

<%= turbo_stream.append "messages" do %>
  <%= render partial: 'rooms/list' %>
<% end %>

<%= turbo_stream.update "new_message", '' %>
  • messagesに新規追加したものを append する処理
  • new_messageは入力フォームを非表示にするように update する処理

こうすることで登録時にリスト追加され、フォームは非表示となります。

また、バリデーションに失敗した場合、flash の:error にエラーメッセージを追加します。
new_room_message_path にリダイレクトするようしていますが、ここのリダイレクトはやめます。

    else
      # flash[:error] = @message.errors.full_messages.join(", ")
      # redirect_to new_room_message_path(@message.room)
      render :new, status: :unprocessable_entity
    end

new.html.erb は下記のようにすることでエラーメッセージを確認しつつ、フォームを赤く表示可能です。

<%= turbo_frame_tag 'new_message' do %>
  <%= form_with(model: @message, url: room_messages_path, class: 'my-8') do |form| %>
    <%= form.hidden_field :room_id, :value => @message.room_id %>
    <%= form.hidden_field :user_id, :value => @message.user_id %>
    <div>
      <%= form.label :content, "送信内容" %>
      <%= form.text_area :content, rows: 5, class: "border rounded p-2 w-full" %>
      <span class="text-red-500"> ←追加
        <%= @message.errors.full_messages_for(:content).presence&.join(',') %> ←追加
      </span> ←追加
    </div>
    <div>
      <%= form.submit "送信する", class: "bg-blue-500 text-white font-bold py-2 px-4 rounded" %>
      <%= link_to 'キャンセル', room_path(@message.room_id), class: 'ml-2 border border-black py-2 px-4 rounded' %>
    </div>
  <% end %>
<% end %>

以上で作成と編集を Hotwire に置き換えました。

GitHubで編集を提案

Discussion