今回は前々回と前回をまとめたアンケートアプリを作ります。
Railsの単一テーブル継承 (STI)を使って単一回答と複数回答をまとめました。
環境
Ruby 3.3.0
Rails 7.1.3.2
モデルを生成します。
rails new qrc_sti && cd $_
bin/rails g model User name
bin/rails g model question content type
bin/rails g model question_radio --parent=Question
bin/rails g model Radio content question:belongs_to
bin/rails g model question_check --parent=Question
bin/rails g model Check content question:belongs_to
bin/rails g model answer type user:belongs_to question:belongs_to radio:belongs_to
bin/rails g model answer_radio --parent=Answer
bin/rails g model answer_check --parent=Answer
bin/rails g model choice answer:belongs_to check:belongs_to
Answerを派生したAnswerCheckはラジオボタンを持ちません。
ラジオボタンへの外部キーはnull値を許可します。
# db/migrate/..._create_answers.rb
t.belongs_to :radio, null: true, foreign_key: true
# app/models/answer.rb
belongs_to :radio, optional: true
AnswerRadioはラジオボタンを必須にします。
# app/models/answer_radio.rb
validates :radio, presence: true
AnswerCheckは複数の選択を持ちます。
AnswerCheckはChoice経由で複数のCheckを持ちます。
# app/models/answer_check.rb
class AnswerCheck < Answer
has_many :choices, foreign_key: "answer_id", inverse_of: :answer
has_many :checks, through: :choices
end
bin/rails db:migrate
初期データを生成します。
今回はログインユーザーの管理、管理者による質問の編集機能は省きます。
# db/seeds.rb
User.find_or_create_by(name: "ユーザー1")
User.find_or_create_by(name: "ユーザー2")
qr1 = QuestionRadio.find_or_create_by(content: "好きな映画のジャンルは?")
["アクション", "コメディー", "ドラマ", "ホラー"].each do |content|
Radio.find_or_create_by(content: content, question: qr1)
end
qr2 = QuestionRadio.find_or_create_by(content: "好きな果物は?")
["いちご", "もも", "みかん", "りんご", "ぶどう"].each do |content|
Radio.find_or_create_by(content: content, question: qr2)
end
qc1 = QuestionCheck.find_or_create_by(content: "好きな映画のジャンルは?")
["アクション", "コメディー", "ドラマ", "ホラー"].each do |content|
Check.find_or_create_by(content: content, question: qc1)
end
qc2 = QuestionCheck.find_or_create_by(content: "好きな果物は?")
["いちご", "もも", "みかん", "りんご", "ぶどう"].each do |content|
Check.find_or_create_by(content: content, question: qc2)
end
bin/rails db:seed
コントローラーを生成します。
bin/rails g controller questions index show --skip-routes --skip-helper
bin/rails g controller answer_radios --skip-routes --skip-helper
bin/rails g controller answer_checks --skip-routes --skip-helper
# config/routes.rb
Rails.application.routes.draw do
resources :questions, only: %i[ index show ]
resources :answer_radios, only: %i[ create update ]
resources :answer_checks, only: %i[ create update ]
get "up" => "rails/health#show", as: :rails_health_check
root "questions#index"
end
一覧ページを開きます。
bin/rails s
open http://localhost:3000/questions
# app/controllers/questions_controller.rb
class QuestionsController < ApplicationController
def index
@questions = Question.all
end
def show
current_user = User.second # TODO: ログインユーザーを取得する。
question = Question.find(params[:id])
@answer = question.answer_class.find_or_initialize_by(user: current_user, question: question)
end
end
# app/views/questions/index.html.erb
<% @questions.each do |question| %>
<div>
<%= link_to question.content, question.becomes(Question) %>
</div>
<% end %>
最初のアンケートページを開きます。
open http://localhost:3000/questions/1
# app/models/question_radio.rb
class QuestionRadio < Question
has_many :radios, foreign_key: "question_id"
def answer_class
AnswerRadio
end
end
# app/models/question_check.rb
class QuestionCheck < Question
has_many :checks, foreign_key: "question_id"
def answer_class
AnswerCheck
end
end
ラジオボタンを表示します。
# app/controllers/answer_radios_controller.rb
class AnswerRadiosController < ApplicationController
def create
@answer = AnswerRadio.new(answer_radio_params)
if @answer.save
redirect_to @answer.question.becomes(Question), notice: "Answer was successfully created."
else
render "questions/show", status: :unprocessable_entity
end
end
def update
@answer = AnswerRadio.find(params[:id])
if @answer.update(answer_radio_params)
redirect_to @answer.question.becomes(Question), notice: "Answer was successfully updated."
else
render "questions/show", status: :unprocessable_entity
end
end
private
def answer_radio_params
params.require(:answer_radio).permit(:user_id, :question_id, :radio_id)
end
end
# app/views/questions/show.html.erb
<p style="color: green"><%= notice %></p>
<%= @answer.question.content %>
<%= form_with(model: @answer) do |form| %>
<% if @answer.errors.any? %>
<div style="color: red">
<h2><%= pluralize(@answer.errors.count, "error") %> prohibited this answer from being saved:</h2>
<ul>
<% @answer.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form.hidden_field :user_id, value: @answer.user_id %>
<%= form.hidden_field :question_id, value: @answer.question_id %>
<%= render "questions/#{@answer.class.name.underscore}", form: form, answer: @answer %>
<div>
<%= form.submit %>
</div>
<% end %>
<br>
<%= link_to "一覧", questions_url %>
部分テンプレートファイルを作ります。
touch app/views/questions/_answer_radio.html.erb
# app/views/questions/_answer_radio.html.erb
<%# locals: (form:, answer:) -%>
<div>
<%= form.collection_radio_buttons :radio_id, answer.question.radios, :id, :content %>
</div>
チェックボックスを表示します。
open http://localhost:3000/questions/3
# app/controllers/answer_checks_controller.rb
class AnswerChecksController < ApplicationController
def create
@answer = AnswerCheck.new(answer_check_params)
if @answer.save
redirect_to @answer.question.becomes(Question), notice: "Answer was successfully created."
else
render "questions/show", status: :unprocessable_entity
end
end
def update
@answer = AnswerCheck.find(params[:id])
if @answer.update(answer_check_params)
redirect_to @answer.question.becomes(Question), notice: "Answer was successfully updated."
else
render "questions/show", status: :unprocessable_entity
end
end
private
def answer_check_params
params.require(:answer_check).permit(:user_id, :question_id, check_ids: [])
end
end
部分テンプレートファイルを作ります。
touch app/views/questions/_answer_check.html.erb
# app/views/questions/_answer_check.html.erb
<%# locals: (form:, answer:) -%>
<div>
<%= form.collection_check_boxes :check_ids, answer.question.checks, :id, :content %>
</div>
前回と同様にレイアウトや文言など見栄えを洗練する必要がありますが、機能としては完成しました。
▼この記事がいいね!と思ったらブックマークお願いします