😢

Ruby on Rails 「削除しますか?」のダイアログを出すのに苦労した話

2023/06/22に公開

カリキュラム学習中からずっと、「削除リンクをクリックしたら即削除になるの怖くない?」と思っていたので、削除確認ダイアログを実装しよう!としたら思いのほか苦戦しましたので記録に残します。

苦戦した...と言いましたが、めっちゃ簡単な方法があります。
link_toメソッドにdataオプションを追加すれば、ブラウザ規定のダイアログを利用できます。

<%= link_to '削除', post_path(@post), method: :delete, data: {confirm: "削除しますか?"} %>



削除リンクをクリックすると、下のようなダイアログが表示されます。



正直ちょっとダサ...味気ないので、JavaScriptで自作のダイアログボックスを表示させるような仕様にしてみようと思ったのですが、JavaScriptよわよわな私にはなかなかに大変でした。

余談:
色々調べていく中でダイアログボックスのことを「モーダル」と呼ぶことを知りました。
以下「モーダル」という表現を使います。

選択肢② 自作モーダルをJavaScriptで表示する。

まずはChatGPTさんが提案してくれたコードを試してみましたが、パスが上手く通らずに失敗しました。(この記述でHTTPメソッド:DELETEの指定が効くのかな?と思ったら案の定…)

失敗①ChatGPTさんの提案内容

<button id="delete-btn">投稿を削除</button>

<!-- 確認ポップアップ用のモーダル -->
<div id="confirmation-modal" class="modal">
  <div class="modal-content">
    <p>投稿を削除しますか?</p>
    <div class="modal-buttons">
      <button id="confirm-yes">はい</button>
      <button id="confirm-no">いいえ</button>
    </div>
  </div>
</div>

<script>
  document.addEventListener('DOMContentLoaded', function() {
    var deleteBtn = document.getElementById('delete-btn');
    var modal = document.getElementById('confirmation-modal');
    var confirmYesBtn = document.getElementById('confirm-yes');
    var confirmNoBtn = document.getElementById('confirm-no');

    deleteBtn.addEventListener('click', function() {
      showModal();
    });

    confirmYesBtn.addEventListener('click', function() {
      hideModal();
      // 削除の処理を実行するためのリンクへリダイレクト // ←ここが失敗ポイント
      window.location.href = '<%=post_path(@post) %>';
    });

    confirmNoBtn.addEventListener('click', function() {
      hideModal();
    });

    function showModal() {
      modal.style.display = 'block';
    }

    function hideModal() {
      modal.style.display = 'none';
    }
  });
</script>

<style>
  /* モーダルのスタイル */
  .modal {
    display: none;
    position: fixed;
    z-index: 999;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
  }

  .modal-content {
    background-color: #fff;
    margin: 200px auto;
    padding: 20px;
    width: 300px;
    text-align: center;
  }

  .modal-buttons {
    margin-top: 20px;
  }
</style>

失敗②open()メソッドでDELETEを指定してやってみた。

パスの指定方法をいじったらどうかな?と思い、Ajaxのカリキュラムで出てきたXMLHttpRequestオブジェクトopen()メソッドsend()メソッドで書き換えてみた。

  // window.location.href = '<%= post_path(@post) %>';
  const XHR = new XMLHttpRequest();
  XHR.open("DELETE", "<%= "/posts/#{params[:id]}" %>", true);
  XHR.send();

パスは無事に通ったのですが、下記のエラーで失敗。

ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):

~ ChatGPTの回答より抜粋 ~
このエラーメッセージは、CSRF(Cross-Site Request Forgery)攻撃対策としてRailsが提供しているCSRFトークンが正しく認識されなかった場合に発生します。
CSRFトークンは、フォームを送信する際にRailsアプリケーションが自動的に生成し、セッションに関連付けられます。これにより、リクエストが正当なものであることを検証し、不正なリクエストから保護します。
このエラーが発生する主な原因は、CSRFトークンがフォームに含まれていないか、または正しく設定されていない場合です。

内容を完全に呑み込めてはないけれど、link_toメソッドを使用しないとこの問題はクリアできなさそう。

思いついてみるとなんて簡単な解決策…と感じるのですが、ここまで来るのに苦労しました。
でも正直JavaScriptを上手く使って実装する方法あるのでは?とも思います。

上手くいったコード(ベースはChatGPTが提案したコード、cssは変わらず)

<button id="delete-btn">投稿を削除</button>

<!-- 確認ポップアップ用のモーダル -->
<div id="confirmation-modal" class="modal">
  <div class="modal-content">
    <p>投稿を削除しますか?</p>
    <div class="modal-buttons">
      <button id="confirm-yes"><%= link_to 'はい', post_path(@post), method: :delete %></button>
      <button id="confirm-no">いいえ</button>
    </div>
  </div>
</div>

<script>
  document.addEventListener('DOMContentLoaded', function() {
    const deleteBtn = document.getElementById('delete-btn');
    const modal = document.getElementById('confirmation-modal');
    const confirmYesBtn = document.getElementById('confirm-yes');
    const confirmNoBtn = document.getElementById('confirm-no');

    deleteBtn.addEventListener('click', function() {
      modal.style.display = 'block';
    });

    confirmYesBtn.addEventListener('click', function() {
    // 「はい」ボタンを押すことでコントローラーのdestroyアクションが動くので、モーダルを閉じる記述に書き換えた。
      modal.style.display = 'none';
    });

    confirmNoBtn.addEventListener('click', function() {
      modal.style.display = 'none';
    });
  });
</script>

おまけ

JavaScriptでどうやってlink_toメソッドを使ったらいいんだ…!と悩んでいた時に、世の中にはRubyを埋め込むことのできるjsファイルなるものが存在することを知りました。
詳しいことはこれからじっくり学んでいきたいですが、js.erbの説明や実装例が詳しそうな記事を貼って終わりとします。

https://qiita.com/motoki0208/items/45211df065e0c037d032

Discussion