Ajaxでページ遷移せずに解説部分を表示する~ChatGPTと一緒に一問一答アプリを開発した話③~

eyecatch-wchatgpt-3

「一問一答アプリ × ChatGPT」

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

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

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

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

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

今回は

  • Ajaxでページ遷移せずに解説部分を表示する機能

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

Ajaxでページ遷移せずに解説部分を表示する

img-4372v2

ページ遷移せずに正誤判定する機能は実装できました。

しかし、正解/不正解しか表示されていませんので、その続きに各問題や選択肢の解説を表示させられるようにしたいと思います。

解説や各選択肢の補足説明文を表示する部分をこれまでresultアクションのビューファイルに記述していましたが、askアクションに統合し、選択肢ボタンや「解答を見る」ボタンを押すと表示されるようにJavascriptファイルを改修します。

また、これまでは

ask → result → next_question アクションの順で挙動していましたが、

resultアクションを省略することになるため、askアクションからnext_questionアクションに移れるようにコードを改修します。

class QuestionsController < ApplicationController

(省略)

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

    # 出題範囲が選択されているか否かで条件分岐
    # 出題範囲が選択されていない場合
    if params[:genre].blank?
      # Genreモデルからカラム「id」の値だけを取得し、セッションに格納する(これで全ての分野のidを取得できる)
      genre_ids = Genre.pluck(:id)
      session[:selected_genre_ids] << genre_ids
    else
      # 選択された分野をparamsから受け取り、セッションに格納する
      genre_ids = params[:genre][:genre_ids].reject(&:empty?)
      if !session[:selected_genre_ids].include?(genre_ids)
        session[:selected_genre_ids] << genre_ids
      end
    end

    # genre_idsに入っている分野idと一致するインスタンスを1つ取得する
    @question = Question.where(genre_id: genre_ids).order("RAND()").first

    if @question.present?
      # session[:asked_question_ids]に取得したインスタンスの問題idを格納する。
      if !session[:asked_question_ids].include?(@question.id)
        session[:asked_question_ids] << @question.id
      end

      #取得した問題インスタンスに付随する選択肢を取得する。
      @choices = @question.choices.includes(:question) #questionとアソシエーション関係なので()の中にはカラム名ではなく関係のあるモデル名にする「_id」は不要
      @answer = Answer.find(@question.id)
  
    else
      redirect_to root_path, alert: "出題できる問題がありません。"
    end
    
  end


  def check
    # binding.pry
    # 選択された値の正誤を判定
    choice = Choice.find(params[:choice_id])
    if choice.is_answer
      result = true
    else
      result = false
    end
    # binding.pry
    # 正誤判定の結果をJSON形式で返す
    render json: { result: result }
  end

  
	#下記resultアクションをコメントアウト

  # def result

  #   #paramsで送付されたquestion_idを利用して当該インスタンスを取得する。
  #   @question = Question.find(params[:question_id])
  #   @choices = @question.choices.includes(:question) 
  #   @answer = Answer.find(@question.id)
  #   @chosen_choice = Choice.find(params[:choice_id])


  # end

  def next_question
    #当メソッドを呼び出す際には最低1回はask~resultメソッドを実行している。そのため、セッションを参考にして分野を指定しながら出題済みの問題を避けて次の問題(インスタンス)を取得できる。
    @question = Question.where(genre_id: session[:selected_genre_ids]).where.not(id: session[:asked_question_ids]).order("RAND()").first

      # 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)

        # 呼び出すビューファイルはaskアクションのビューファイル
        render :ask
    else
        redirect_to root_path, alert: "出題できる問題がありません。"
    end 
  end

end

そしてビューファイルを修正します。

<div class="contents row">
  <div class="question_id">
    <%= "問題 #{@question.id}"%>
  </div>
  <div class="sentense">
  <p>次の問題文の答えとして最も適している語句を選びなさい。</p>
  </div>
  <div class="question_text">
    <%= @question.text%>
  </div>
  <div class="choices">
      <h4><解答一覧></h4>
      <p>ア.  <%= @choices[0].choice%></p>
      <p>イ.  <%= @choices[1].choice%></p>
      <p>ウ.  <%= @choices[2].choice%></p>
      <p>エ.  <%= @choices[3].choice%></p>
    
    <div class="button-list" style="display:flex;" id="button-list">
    <%= button_to "ア", "/questions/result", {method: :post, params: {choice_id: @choices[0].id, question_id: @question.id}, class: "buttons",data: { "choice-id" => @choices[0].id, "choice-name" =>"ア"},"csrf-token" => form_authenticity_token  }%>
    <%= button_to "イ", "/questions/result", {method: :post, params: {choice_id: @choices[1].id, question_id: @question.id}, class: "buttons",data: { "choice-id" => @choices[1].id, "choice-name" =>"イ"},"csrf-token" => form_authenticity_token  }%>
    <%= button_to "ウ", "/questions/result", {method: :post, params: {choice_id: @choices[2].id, question_id: @question.id}, class: "buttons",data: { "choice-id" => @choices[2].id, "choice-name" =>"ウ"},"csrf-token" => form_authenticity_token  }%>
    <%= button_to "エ", "/questions/result", {method: :post, params: {choice_id: @choices[3].id, question_id: @question.id}, class: "buttons",data: { "choice-id" => @choices[3].id, "choice-name" =>"エ"},"csrf-token" => form_authenticity_token  }%>
    </div>

  <%= button_to "解答を見る", "/questions/result", {method: :post, params: {choice_id: 0, question_id: @question.id}, id: "seeAnsButton", class: "buttons",data: { "choice-id" => 0,"choice-name" =>"-" },"csrf-token" => form_authenticity_token  }%>

  <div id="result"></div>
  <div id="chosenName"></div>

 <%# 選択肢のボタンを押すとこれ以降の要素が表示されるようにする%>
  <div class="contents row " id="answerArea" style="display: none;">
    <div class="question_id">
      <%= "問題 #{@question.id}"%>
      <p>解答</p>
    </div>

    <div class="question_text">
      <%= @question.text%>
    </div>
    <div class="answer">
      <%= @answer.answer%> <%# 2個目のanswerはカラム名%>
    </div>

    <div class="choices">
        <h4><選択肢一覧></h4>
        <% @choices.each do |choice| %>
          <p>
            <%= choice.choice %> <%# 2個目のchoiceはカラム名%>
          </p>
        <% end %>
    </div>
	</div>

  
  <%= button_to "次の問題", "/questions/next_question", {method: :post, class: "nextQuestion" }%>

  <p>------</p>
  <p><%= link_to '問題選択へ戻る', root_path, method: :get %>

</div>

ボタンを非表示にすることで、どの選択肢を選択したかわからなくなるので、

button_toに”data-choice-name”を付与(19行目〜22行目、25行目)、javascriptで取得&表示するように。

function check_answer(){
  const buttons = document.querySelectorAll(".buttons");

buttons.forEach(function(button){
    button.addEventListener("click",function(e){

      e.preventDefault();

      var token = document.getElementsByName('csrf-token')[0].content;
      var choice_id = this.getAttribute('data-choice-id');
			// 選択肢の名前(アやイなど)を取得
      var chosenName = this.getAttribute('data-choice-name');


      // 解答・解説部分の表示
      const answerArea = document.getElementById("answerArea");
      answerArea.removeAttribute("style");

			// 選択した解答(選択肢)をテキスト情報として要素に加える
      document.querySelector('#chosenName').textContent = "あなたの解答:" + chosenName;

      const xhr = new XMLHttpRequest();
      xhr.open('POST', '/questions/check', true);
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
      xhr.setRequestHeader('X-CSRF-Token', token); // トークンをリクエストヘッダーに追加
      xhr.onload = function() {
        if (xhr.status === 200) {
          // 正誤判定の結果を表示
          var data = JSON.parse(xhr.responseText);
          var result = data.result ? '正解!' : '不正解...';
          document.querySelector('#result').textContent = result;
        }
        else {
          console.error(xhr.statusText);
        }
      };
      // xhr.onerror = function() {
      //   console.error(xhr.statusText);
      // };
      // XHR.responseType = "json";
      // xhr.send('choice_id=' + choice_id);

      xhr.send('choice_id=' + encodeURIComponent(choice_id));

      // 選択肢ボタンもしくは「解答を見る」ボタンを押すと、ボタンを非表示へ。解答を見てから解答することを防ぐ
      const buttonList = document.getElementById("button-list");
      buttonList.setAttribute("style","display:none;");
      const seeAnsButton = document.getElementById("seeAnsButton");
      seeAnsButton.setAttribute("style","display:none;");
    });
  });
};


window.addEventListener('load',check_answer);

これにより実装した結果がこちら

Ajaxで解説部分表示

まとめ

今回の開発作業で

  • Ajaxでページ遷移せずに解説部分を表示する機能

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

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

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

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

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

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

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

Twitter

Twitter やってます。

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

コメントを残す

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

CAPTCHA