JavaScriptでリロードを禁止する機能の実装~ChatGPTと一緒に一問一答アプリを開発した話⑦~

eyecatch-wchatgpt-7

「一問一答アプリ × ChatGPT」

先日、ChatGPTを活用して一問一答アプリケーション「情報1 一問一答トレーニングGYM」を開発したことをお伝えしました。

▶︎こちらの記事で紹介しています。

iqatlogo2【ChatGPTと開発】高校「情報1」一問一答アプリ

私が開発したこのアプリは、ChatGPTの力を借りながら、実装していきました。

この記事では、一問一答アプリ × ChatGPTの開発の舞台裏や、実際の活用方法について詳しく解説します。

今回は

  • JavaScriptでリロードを禁止する機能の実装

を実装するにあたって、どのように役立ったのか、その様子を紹介します!

ぜひこのシリーズの記事をチェックして、一問一答アプリの新たな魅力とChatGPTとの相乗効果を体感してください!

目的

img-4372v2

ブラウザで再読み込み(リロード)をすると、エラーが出てしまう問題が発生していました。

基本的にリロードをしなくても一問一答機能は使えますが、ユーザーがリロードすることは考えられますので、想定しておきたいところです。

そもそも、一問一答を行う際のHTTPメソッドはpostに設定しているにも関わらず、リロードをした際にはGETメソッドでリクエストが送られることが原因。

できることならリロードを禁止できないかと思い、chatGPTと相談してみました。

JavaScriptの実装

chatGPTとの対話履歴5(リロード)
コトゼニ
コトゼニ

以下の要件を満たすコードを作ってもらえますか?

▼要件:

  • ブラウザのリロードを検知するとアラート(確認?)を表示する(JavaScriptで)
  • アラートの文章は「再読み込みすすると、問題を再度作成することになります。よろしいですか?」とする
  • アラートに対して肯定的なボタンをクリックすると、controllerのaskアクションにpostメソッドで呼び出す
chatGPT
chatGPT

以下は、要件を満たす例のJavaScriptコードとRailsコードです。

window.addEventListener("beforeunload", function(event) {
  event.preventDefault();
  event.returnValue = "";
  const confirmationMessage = "再読み込みすすると、問題を再度作成することになります。よろしいですか?";
  event.returnValue = confirmationMessage;
  return confirmationMessage;
});

ただ実装すると、ダイアログはでないわ、引き続きGETメソッドでリクエストが送られてしまうわで解決に結びつきません。

いろいろと悩み、四苦八苦した結果、発想を変えて

コトゼニ
コトゼニ

GETメソッドでのリクエストがあった場合の処理を記述すれば良いのでは?

という発想を転換することにしました。

手順1:ルーティングの設定

programming

まずはルーティングの設定です。画面遷移のある#askアクション、#next_questionアクション、#printアクション、#retryアクションについてgetメソッド版のルーティングを設定します。

Rails.application.routes.draw do
(省略)
    collection do
      get 'choose'
      get 'ask'
      post 'ask'
      get 'next_question'
      post 'next_question'
      post 'check'
      get 'print'
      post 'print'
      get 'score'
      get 'retry'
      post 'retry'
    end
(省略)
end

手順2:コントローラーの設定

cyber

続いて、questionコントローラーを設定します。

class QuestionsController < ApplicationController
  def ask
    if request.get?
      render html: '<script>alert("選択内容が解除されトップページに戻ります。");window.location.href = "/";</script>'.html_safe
    elsif request.post?

      # まずはセッション変数を取得する
      # next_questionメソッドで使うため
      # session[:asked_question_ids] ||= [] でセッションが未定義なら新しく作るという意味になる
      session[:asked_question_ids] ||= []
      (省略)

        redirect_to root_path, alert: "出題できる問題がありません。"
      end
    end
  end


  def next_question
    if request.get?
      render html: '<script>alert("選択内容が解除されトップページに戻ります。");window.location.href = "/";</script>'.html_safe
    elsif request.post?
      if session[:question_range] != nil
        @question = Question.where(id: session[:question_range]).where.not(id: session[:asked_question_ids]).order("RAND()").first
      else
        #当メソッドを呼び出す際には最低1回はask~resultメソッドを実行している。そのため、セッションを参考にして分野を指定しながら出題済みの問題を避けて次の問題(インスタンス)を取得できる。
        @question = Question.where(genre_id: session[:selected_genre_ids],level_id: session[:selected_level_ids]).where.not(id: session[:asked_question_ids]).order("RAND()").first
      end
        # session[:asked_question_ids]に取得したインスタンスの問題idを追加格納する。
        if @question.present?
          session[:asked_question_ids] << @question.id
          @choices = @question.choices.includes(:question) #questionとアソシエーション関係なので()の中にはカラム名ではなく関係のあるモデル名にする「_id」は不要
          @answer = Answer.find(@question.id)

          if session[:question_nums].present?
            @question_nums = session[:question_nums]
          end
          
          # -1は出題数を調整するため
          @asked_question_count = (session[:asked_question_ids].length - 1)
          @correct_counts = session[:correct_answers].length

          # to_fは浮動小数(float)への変換
          @correct_rate = ((@correct_counts.to_f / @asked_question_count.to_f) * 100).round(1)
          # 呼び出すビューファイルはaskアクションのビューファイル
          render :ask
      else
          redirect_to score_questions_path
      end 
    end
  end

  def retry
    if request.get?
      render html: '<script>alert("選択内容が解除されトップページに戻ります。");window.location.href = "/";</script>'.html_safe
    elsif request.post?
      # いくつかのセッションをリセットする
      session[:asked_question_ids] = []
  (省略)
      if @question.present?
          # 不正解問題のidを出題済みセッションに格納する
          session[:asked_question_ids] << @question.id
          @choices = @question.choices.includes(:question) #questionとアソシエーション関係なので()の中にはカラム名ではなく関係のあるモデル名にする「_id」は不要
          @answer = Answer.find(@question.id)
          render :ask
      else
        redirect_to score_questions_path
      end 
    end
  end

  def print
    if request.get?
      render html: '<script>alert("選択内容が解除されトップページに戻ります。");window.location.href = "/";</script>'.html_safe
    elsif request.post?
      # 出題数をパラムスより取得する
      question_count = params[:question_count].to_i
  (省略)
    end

end

この手法は強引なんですけど、それぞれのアクションに対するURLを直入力した際の対策も同時にできるので、(before_actionなどを使わなくても良い)その点を考えても効果的かと思います。

そして実装できたのがこちら

リロードでルートパスへ

まとめ

今回の開発作業で

  • JavaScriptでリロードを禁止する機能の実装

を実装することができました。

ChatGPTを利用すると開発がグンと楽になりますね!ChatGPTの可能性を体験してみてはいかがでしょうか?

最後まで読んでいただきありがとうございました。

この記事が少しでも参考になれば幸いです。

それではまたここで会いましょう!

ブログランキング・にほんブログ村へにほんブログ村

ブログ村に参加中です。上のバナーをクリックいただくだけで当ブログにポイントが入ります。いつも応援クリックありがとうございます。

Twitter

Twitter やってます。

新着記事のお知らせをしていますのでぜひフォローしてください!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA