質問投稿サイトの作成(3/3)
ここでは,ユーザーが質問に対して,回答を投稿できるようにする
130.回答投稿機能の概要
- ユーザーが質問に対して,回答を投稿できる
- ユーザーが回答一覧を閲覧できる
- ユーザーが回答を編集できる
- ユーザーが回答を削除できる
131.質問詳細画面の実装
コントローラ
def show @question = Question.find(params[:id]) # 追加 end
viewの編集
<div class="row"> <div class="col-md-12"> <h2><%= @question.title %></h2> <div> Content: <%= @question.content %> </div> <div> Name: <%= @question.name %> </div> <hr> <div> <%= link_to '> Home', root_path %> </div> </div> </div>
3番目の質問のタイトルをクリック,その詳細画面は↑こんな感じ
132.Answers コントローラの作成
質問に対する回答用のコントローラを作成する.
rails g controller answers edit
回答の一覧はquestionsコントローラのshowメソッドを利用
133.Answerモデルの作成
テーブルの構造
Questions | Answers | |||
---|---|---|---|---|
カラム | 説明 | 1対多 | カラム | 説明 |
id | id | id | id | |
name | 質問投稿者名 | question_id | question_id | |
title | 質問タイトル | name | 質問回答者名 | |
content | 質問本文 | content | 回答本文 |
question_id
は回答にひもづくid.1つの質問(question)に対して,複数の回答(answers)が投稿されるので「1対多」の関係.
rails g model answer question:references name:string content:text
answerから見ると、1つのanswerが1つのquestionにひもづく.
Running via Spring preloader in process 6161 invoke active_record create db/migrate/XX_create_answers.rb create app/models/answer.rb invoke test_unit create test/models/answer_test.rb create test/fixtures/answers.yml
answerモデルは↓こんな感じ.
class Answer < ApplicationRecord belongs_to :question end
つまり,ひもづくという意味answerから見てquestionは1つあるという意味.
class Question < ApplicationRecord has_many :answers, dependent: :destroy #追加 validates :name, presence: true validates :title, presence: true validates :content, presence: true end
has_many :answers
:1つのquestionは複数のanswerを持っているという意味dependent: :destroy
:親であるquestionが削除されたら,ひもづくanswerが全て削除されるという設定class CreateAnswers < ActiveRecord::Migration[5.2] def change create_table :answers do |t| t.references :question, foreign_key: true t.string :name t.text :content t.timestamps end end end
rails db:migrate == XX CreateAnswers: migrating ==================================== -- create_table(:answers) -> 0.0052s == XX CreateAnswers: migrated (0.0064s) ===========================
rails dbconsole sqlite> .schema answers CREATE TABLE "answers" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "question_id" integer, "name" varchar, "content" text, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL, CONSTRAINT "fk_rails_3d5ed4418f" FOREIGN KEY ("question_id") REFERENCES "questions" ("id") ); CREATE INDEX "index_answers_on_question_id" ON "answers" ("question_id");
134.回答機能関連のルーティング設定
get 'answers/edit' root 'questions#index' resources :questions do #追加 resources :answers #追加 end
rails routesでanswers関連のルーティングが用意できたことが分かる.
new_question_answer GET /questions/:question_id/answers/new(.:format) answers#new edit_question_answer GET /questions/:question_id/answers/:id/edit(.:format) answers#edit question_answer GET /questions/:question_id/answers/:id(.:format) answers#show PATCH /questions/:question_id/answers/:id(.:format) answers#update PUT /questions/:question_id/answers/:id(.:format) answers#update DELETE /questions/:question_id/answers/:id(.:format) answers#destroy
使いながら理解していく
135.回答の投稿機能(Questionsコントローラ)
質問詳細画面(app/views/questions/show.html.erb
)で,回答の投稿をできるようにするため,
空のインスタンス@answer = Answer.new
を追加
def show @question = Question.find(params[:id]) @answer = Answer.new #追加 end
136.回答の投稿機能View
Questionsコントローラのshowアクションに@answer = Answer.new
を追加したので、
質問詳細画面(app/views/questions/show.html.erb)に,回答投稿フォームを追加
<div class="row"> <div class="col-md-12"> <h2><%= @question.title %></h2> <div> Content: <%= @question.content %> </div> <div> Name: <%= @question.name %> </div> <hr> <!-- 追加ココから --> <h3>Post new answer.</h3> <%= form_with model: [@question, @answer], local: true do |f| %> <%= f.hidden_field :question_id, { value: @question.id } %> <div class="form-group"> <label>Name</label> <%= f.text_field :name, class: 'form-control' %> </div> <div class="form-group"> <label>Content</label> <%= f.text_area :content, class: 'form-control' %> </div> <div class="text-center"> <%= f.submit "Post", class: 'btn btn-primary' %> </div> <% end %> <!-- ココまで --> <div> <%= link_to '> Home', root_path %> </div> </div> </div>
form_with model: [@question, @answer], local: true do |f|
questionモデルにひもづくanswerモデルをフォームに送信したいときは,このように配列でかく.
@question
と@answer
はコントローラーから渡している.
ここも質問の入力画面と同様に,@answer
が空(つまり新規)ならanswers
コントローラのcreate
メソッドを実行してくれる?
f.hidden_field :question_id, { value: @question.id }
ユーザーに見せる必要はないシステム内の処理をするのに必要な画面情報(ID値や商品名など)を画面に保持しておき,次の処理のときに渡すためのパラメータなどを格納しておくといった役割.第一引数にシンボル(パラメータ名),第二引数にvalueとして受け渡したい値を設定する
渡す値
:question_id, :name, :content
詳細画面に回答投稿画面を追加
137.質問が投稿された時の保存処理
answersコントローラ
#追加ココから def create @question = Question.find(params[:question_id]) #質問は入力フォームで渡されたquestion_idで検索 @answer = Answer.new #回答は空のインスタンスを用意し、 if @answer.update(answer_params) #①、ここでupdateできれば redirect_to question_path(@question), notice: 'Success!' else #updateできなければ redirect_to question_path(@question), alert: 'Invalid!' end end #ココまで def edit end #追加ココから private def answer_params params.require(:answer).permit(:content, :name, :question_id) end #ココまで
if @answer.update(answer_params)
⇒ .save
だと上手くいかないredirect_to question_path(@question)
⇒ 質問詳細ページへ,投稿した回答の表示は後で実装
138.Answerモデルのバリデーション
class Answer < … belongs_to :question # 追加ココから validates :name, presence: true validates :content, presence: true # ココまで end
139.回答一覧表示
質問詳細ページ(app/views/questions/show.html.erb
)に,回答が1件以上あれば回答一覧を,0件の場合は「No Answer」と表示
<div class="row"> <div class="col-md-12"> <h2><%= @question.title %></h2> <div> Content: <%= @question.content %> </div> <div> Name: <%= @question.name %> </div> <hr> <!-- 追加ココから --> <div> <h3>Answers</h3> <table class="table table-striped"> <% if @question.answers.any? %> <thead class="thead-light"> <tr> <td>Answer</td> <td>Name</td> <td>Menu</td> </tr> </thead> <tbody> <% @question.answers.each do |answer| %> <tr> <td> <%= answer.content %> </td> <td> <%= answer.name %> </td> <td> [Edit][Delete] </td> </tr> <% end %> </tbody> <% else %> <p>No answer yet.</p> <% end %> </table><!-- ココまで --> <h3>Post new answer.</h3> <%= form_with model: [@question, @answer], local: true do |f| %> <%= f.hidden_field :question_id, { value: @question.id } %> <div class="form_group"> <label>Name</label> <%= f.text_filed :name, class: 'form-control' %> </div> <div class="form_group"> <label>Content</label> <%= f.text_area :content, class: 'form-control' %> </div> <div class="text-center"> <%= f.submit "Post", class='btn btn-primary' %> </div> <% end %> <div> <%= link_to '> Home', root_path %> </div> </div> </div>
if @question.answers.any?
questionにひもづく回答が1件以上ある場合.
question
は単数で,answers
は複数形であることに注意.ここは間違えやすいな.
answers
と複数形なのは,questionモデルの
class Question < ApplicationRecord has_many :answers, dependent: :destroy
回答を投稿してみる
無事,回答ができた
140.回答の編集①
rails routesでルーティングの確認
edit_question_answer GET /questions/:question_id/answers/:id/edit(.:format) answers#edit
URLの中に、questionのid(question_id
,回答の入力フォームでわたされたもの)とanswersのidが含まれていることに注意.よって,次のように変更
<div class="row"> <div class="col-md-12"> <h2><%= @question.title %></h2> <div> Content: <%= @question.content %> </div> <div> Name: <%= @question.name %> </div> <hr> <div> <h3>Answers</h3> <table class="table table-striped"> <% if @question.answers.any? %> <thead class="thead-light"> <tr> <td>Answer</td> <td>Name</td> <td>Menu</td> </tr> </thead> <tbody> <% @question.answers.each do |answer| %> <tr> <td> <%= answer.content %> </td> <td> <%= answer.name %> </td> <td> [<%= link_to 'Edit', edit_question_answer_path(@question, answer) %>] [Delete]<!-- 変更 --> </td> </tr> <% end %> </tbody> <% else %> No answer yet. <% end %> </table> <h3>Post new answer.</h3> <%= form_with model: [@question, @answer], local: true do |f| %> <%= f.hidden_field :question_id, { value: @question.id } %> <div class="form_group"> <label>Name</label> <%= f.text_filed :name, class: 'form-control' %> </div> <div class="form_group"> <label>Content</label> <%= f.text_area :content, class: 'form-control' %> </div> <div class="text-center"> <%= f.submit "Post", class='btn btn-primary' %> </div> <% end %> <div> <%= link_to '> Home', root_path %> </div> </div> </div>
answersコントローラのeditアクション
def edit @question = Question.find(params[:question_id]) # 追加,question_idで検索 @answer = @question.answers.find(params[:id]) # 追加,answersと複数形に注意! end
view
回答者の名前と内容を編集できるようにする.
質問詳細画面(app/views/questions/show.html.erb
)内に作成した回答投稿フォーム(Post new answer.)とほぼ同じ.
<div> <h2>Update answer</h2> <%= form_with model: [@question, @answer], local: true do |f| %> <div class="form-group"> <label>Name</label> <%= f.text_field :name, class:"form-control" %> </div> <div class="form-group"> <label>Content</label> <%= f.text_area :content, class:"form-control" %> </div> <div class="text-center"> <%= f.submit "Update", class: "btn btn-primary" %> </div> <% end %> </div>
1番目の回答を編集してみる
回答の編集画面
updateボタンを押したときの保存処理は,次で行う.
141.回答の編集②
ここもanswersコントローラのcreate
アクションとほぼ同じ.
def edit @question = Question.find(params[:question_id]) @answer = @question.answers.find(params[:id]) end # 追加ココから def update @question = Question.find(params[:question_id]) @answer = @question.answers.find(params[:id]) if @answer.update(answer_params) redirect_to question_path(@question), notice: "Success!" else flash[:alert]='Invalid!' render :edit end end # ココまで
回答を編集してみる
無事に回答を編集できた
142.回答の削除
回答にある仮のボタン[Delete]を削除できるようにする.
まずはrails routesでURIを確認
Prefix Verb URI Pattern Controller#Action DELETE /questions/:question_id/answers/:id(.:format) answers#destroy
View
<div class="row"> <div class="col-md-12"> <h2><%= @question.title %></h2> <div> Content: <%= @question.content %> </div> <div> Name: <%= @question.name %> </div> <hr> <div> <h3>Answers</h3> <table class="table table-striped"> <% if @question.answers.any? %> <thead class="thead-light"> <tr> <td>Answer</td> <td>Name</td> <td>Menu</td> </tr> </thead> <tbody> <% @question.answers.each do |answer| %> <tr> <td> <%= answer.content %> </td> <td> <%= answer.name %> </td> <td> [<%= link_to 'Edit', edit_question_answer_path(@question, answer) ] [<%= link_to 'Delete', question_answer_path(@question, answer), method: :delete, data: { confirm: 'R U sure?'} %>] <!-- ↑変更 --> </td> </tr> <% end %> </tbody> <% else %> No answer yet. <% end %> </table> <h3>Post new answer.</h3> <%= form_with model: [@question, @answer], local: true do |f| %> <%= f.hidden_field :question_id, { value: @question.id } %> <div class="form_group"> <label>Name</label> <%= f.text_filed :name, class: 'form-control' %> </div> <div class="form_group"> <label>Content</label> <%= f.text_area :content, class: 'form-control' %> </div> <div class="text-center"> <%= f.submit "Post", class='btn btn-primary' %> </div> <% end %> <div> <%= link_to '> Home', root_path %> </div> </div> </div>
answersコントローラ
def update @question = Question.find(params[:question_id]) @answer = @question.answers.find(params[:id]) if @answer.update(answer_params) redirect_to question_path(@question), notice: "Success!" else flash[:alert]='Invalid!' render :edit end end # 追加ココから def destroy @question = Question.find(params[:question_id]) @answer = @question.answers.find(params[:id]) @answer.destroy redirect_to question_path(@question), notice: 'Deleted!' end # ココまで
143.Questionsコントローラのリファクタリング
@question = Question.find(params[:question_id])
が4か所あるのでまとめる.
class QuestionsController < ApplicationController before_action :set_question, only: [:show, :edit, :update, :destroy] # 追加 def index @questions = Question.all end def show # @question = Question.find(params[:id]) # 変更 @answer = Answer.new end def new @question = Question.new end def create @question = Question.new(question_params) if @question.save redirect_to root_path, notice: "Success!" else flash[:alert] ="Save error!" render :new end end def edit # @question = Question.find(params[:id]) # 変更 end def update # @question = Question.find(params[:id]) # 変更 if @question.update(question_params) redirect_to root_path, notice: 'Success!' else flash[:alert]='Save error!' render :edit end end def destroy # @question = Question.find(params[:id]) # 変更 @question.destroy redirect_to root_path, notice: 'Success!' end private # 追加ココから def set_question @question = Question.find(params[:id]) end # ココまで def question_params params.require(:question).permit(:name, :title, :content) end end