記事内で紹介する商品を購入することで、当サイトに売り上げの一部が還元されることがあります。
「一問一答アプリ × ChatGPT」
先日、ChatGPTを活用して一問一答アプリケーション「情報1 一問一答トレーニングGYM」を開発したことをお伝えしました。
▶︎こちらの記事で紹介しています。
【ChatGPTと開発】高校「情報1」一問一答アプリ私が開発したこのアプリは、ChatGPTの力を借りながら、実装していきました。
この記事では、一問一答アプリ × ChatGPTの開発の舞台裏や、実際の活用方法について詳しく解説します。
今回は
- 設定(出題範囲や難易度など)で出題される問題を絞ることができるができる
- アプリケーションを閉じるまで、一度解いた問題は二度出題されない
という機能を実装するにあたって、どのように役立ったのか、その様子を紹介します!
ぜひこのシリーズの記事をチェックして、一問一答アプリの新たな魅力とChatGPTとの相乗効果を体感してください!
アプリケーションを新規作成し、DBを作成(このあたりの操作についてはコードなどを割愛)。
ここから要件を満たす実装をしていきます。
まずは「設定(出題範囲や難易度など)で出題される問題を絞ることができる」です。
モデルが管理しているレコードを取得するにはさまざまなコードの記述方法がありますが
chatGPTに相談してみました
それがこちら。
画像では提案してくれたコードが途切れているので、再度以下に記すと、
selected_genre_ids = params[:genre][:genre_ids].reject(&:empty?)
selected_genres = Genre.where(id: selected_genre_ids)
questions = Question.joins(:genres).where(genres: {id: selected_genre_ids}).order("RANDOM()").first
となりました。
なるほど。これだけのコードを瞬時に書いてくれるのは恐ろしいですね
使用しているSQLが私の場合はMySQLだったので「.order(“RANDOM()”)」の部分を修正する必要はあったのですが、土台となるコードをここまで的確に書いてくれると、開発がそりゃあもう捗る捗る。
実装したコードは後述でまとめます
続いて、一度出題された問題が何度も出題されないようにするという点です。
この点もセッションをうまく使えばなんとかなるかなぁと考えていました。
そして例の如く、chatGPTに土台となるコードを依頼してみました。
提案してくれたコードが途切れているので依頼したプロンプトとともに以下に再表示します。
一問一答アプリケーションは以下のような画面遷移・要件を満たすように設計したいと思います。コントローラーのコードを作ってください
▼画面遷移:①トップページ(分野選択)②出題(解答)③正誤判定
▼要件:
●②において、①で選択した分野と一致する問題(インスタンス)を全て取得するが、表示するのはその中の一つのみ
●③において「次の問題へ」ボタンを押すと、すでに出題された問題以外かつ①で選択した分野と一致する問題から次に出題する問題を一つ選定する。
class QuestionsController < ApplicationController
before_action :set_genres, only: [:index, :create]
def index
@question = Question.new
end
def create
genre_ids = params[:question][:genre_ids].reject(&:empty?)
@question = Question.where(genre_id: genre_ids).order("RANDOM()").first
if @question.present?
session[:asked_question_ids] ||= []
session[:asked_question_ids] << @question.id
redirect_to answer_question_path(@question)
else
redirect_to questions_path, alert: "出題できる問題がありません。"
end
end
def answer
@question = Question.find(params[:id])
end
def result
@question = Question.find(params[:id])
session[:asked_question_ids].delete(@question.id)
if @question.answer == params[:answer]
flash[:notice] = "正解です!"
else
flash[:alert] = "不正解です。"
end
genre_ids = params[:genre_ids].reject(&:empty?)
@question = Question.where(genre_id: genre_ids).where.not(id: session[:asked_question_ids]).order("RANDOM()").first
if @question.present?
session[:asked_question_ids] << @question.id
redirect_to answer_question_path(@question)
else
redirect_to questions_path, alert: "出題できる問題がありません。"
end
end
private
def set_genres
@genres = Genre.all
end
end
やはり以下のコードでセッションを利用していることがわかります。
if @question.present?
session[:asked_question_ids] ||= []
session[:asked_question_ids] << @question.id
redirect_to answer_question_path(@question)
else
具体的には
session[:asked_question_ids] ||= []
session[:asked_question_ids] << @question.id
1行目の「session[:asked_question_ids] ||= []」でセッション変数がなければ取得し、2行目「session[:asked_question_ids] << @question.id」でセッションにモデルから取得したインスタンス(question)のidを格納しています。
これにより
@question = Question.where(genre_id: genre_ids).where.not(id: session[:asked_question_ids]).order("RANDOM()").first
において、「where.not(id: session[:asked_question_ids])」の部分で、「セッションに格納されているid以外で検索する」という意味を持たせてSQL文を実行することができるようになりました。
ちなみにchatGPTが提案してくれたコードにおいて
session[:asked_question_ids].delete(@question.id)
とあり、セッションに格納したidを削除したら同じ問題が再度出題される可能性があることに気付いたため修正を加えました。
これらをもとに実装したのが以下のコードです
class QuestionsController < ApplicationController
def show
# 定義しているだけ
end
def choose
reset_session
@genres = Genre.all
end
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」は不要
else
redirect_to root_path, alert: "出題できる問題がありません。"
end
end
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」は不要
# 呼び出すビューファイルはaskアクションのビューファイル
render :ask
else
redirect_to root_path, alert: "出題できる問題がありません。"
end
end
end
これで実装した挙動がこちら。
とりあえずこのコードで実装できました!
今回の開発作業で
- 設定(出題範囲や難易度など)で出題される問題を絞ることができるができる
- アプリケーションを閉じるまで、一度解いた問題は二度出題されない
の2つを実装することができました。
ChatGPTを利用すると開発がグンと楽になりますね!ChatGPTの可能性を体験してみてはいかがでしょうか?
最後まで読んでいただきありがとうございました。
この記事が少しでも参考になれば幸いです。
それではまたここで会いましょう!
にほんブログ村ブログ村に参加中です。上のバナーをクリックいただくだけで当ブログにポイントが入ります。いつも応援クリックありがとうございます。
Twitter やってます。
新着記事のお知らせをしていますのでぜひフォローしてください!スポンサーリンク