→Railsビギナー 免責事項 に同意いただける方のみ、教材の利用をお願いいたします。
今のままでは質問があるだけで、質問に対する回答がありません。
そこで次は回答機能を作りましょう。
まずはanswerモデルを作ってください。
ターミナルに下記のコマンドを入力。
rails g model answer
作成されたマイグレーションファイルを開いて、下記のコードを追加してください。
class CreateAnswers < ActiveRecord::Migration[6.0]
def change
create_table :answers do |t|
t.references :user, foreign_key: true
t.references :question, foreign_key: true
t.text :body
t.timestamps
end
end
end
第2章ではquestionsテーブルに、後からuser_idカラムを付け加えました。
今回は最初からuser_idカラムを作ります。
そしてuser_idカラムだけでなく、question_idカラムも持たせます。
1人のユーザーは多くの回答を持ちますね?
そして1つの質問も多くの回答を持ちます。
ユーザーと回答は1対多の関係になり、質問と回答も1対多の関係になります。
そのためanswersテーブルには、user_idカラムとquestion_idカラム、そして本文であるbodyカラムを持たせました。
※今回は回答なのでtitleカラムは不要です。
書けたら下記コマンドをターミナルに入力してください。
rails db:migrate
次はモデルの関連付けを行いましょう。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
has_many :questions, dependent: :destroy
has_many :answers, dependent: :destroy
end
class Question < ApplicationRecord
has_many :answers, dependent: :destroy
belongs_to :user
validates :title, presence: true, length: { maximum: 100 }
validates :body, length: { maximum: 3000 }
end
class Answer < ApplicationRecord
belongs_to :user
belongs_to :question
end
バリデーションも書いておきます。
class Answer < ApplicationRecord
belongs_to :user
belongs_to :question
validates :body, presence: true, length: { maximum: 1000 }
end
次にコントローラーを作成。
rails g controller answers
ルーティングも忘れず書いておきましょう。
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'users/registrations'
}
if Rails.env.development?
mount LetterOpenerWeb::Engine, at: "/letter_opener"
end
get "/users/:id", to: "users#show"
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
root to: "questions#index"
resources :questions
resources :answers
end
それでは回答するためのフォームを作ります。
今回は質問詳細ページに回答できるフォームを作ります。
イメージはこんな感じ。
質問のタイトルと本文の下に、回答フォームがある感じです。
app/views/questions/show.html.erbに、フォームを作るコードを追加します。
<p><%= @question.title %></p>
<p><%= @question.body %></p>
<% if @question.user_id == current_user.id %>
<%= link_to("編集", "/questions/#{@question.id}/edit") %>
<%= link_to("削除", "/questions/#{@question.id}", method: :delete, data: {confirm: "本当に削除しますか?"}) %>
<% end %>
<div>
<%= form_with(model: @answer, local: true) do |f| %>
<div>
<%= f.label :body, "回答" %><br>
<%= f.text_area :body %>
</div>
<%= f.submit %>
<% end %>
</div>
今回作ったフォームは回答の新規作成なので、Answer.newが代入された@answerを使います。
しかしながらapp/views/questions/show.html,erbに対応する、questionsコントローラーのshowアクションには、Answer.newを代入した@answerは存在しません。
そのためにはquestionコントローラーのshowアクションに下記のコードを追加します。
class QuestionsController < ApplicationController
def index
@test = "テストテキスト"
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)
@question.user_id = current_user.id
if @question.save
flash[:notice] = "成功!"
redirect_to("/questions/new")
else
flash.now[:alert] = "失敗!"
render("questions/new")
end
end
def edit
@question = Question.find(params[:id])
end
def update
@question = Question.find(params[:id])
if @question.update(question_params)
flash[:notice] = "編集しました"
redirect_to("/questions/#{@question.id}")
else
flash.now[:alert] = "失敗!"
render("questions/edit")
end
end
def destroy
@question = Question.find(params[:id])
@question.destroy
flash[:notice] = "成功!"
redirect_to("/questions")
end
private
def question_params
params.require(:question).permit(:title, :body)
end
def ensure_correct_user
@question = Question.find_by(id: params[:id])
if @question.user_id != current_user.id
flash[:alert] = "権限がありません"
redirect_to("/questions/#{@question.id}")
end
end
end
これでフォームはできます。
それでは質問の新規作成と同じく、フォームに入力された値を、データベースに保存する処理を書きましょう。
answersコントローラーのcreateアクションを作ります。
それではapp/controllers/answers_controller.rbを開いて、下記のコードを追加してください。
class AnswersController < ApplicationController
before_action :authenticate_user!
def create
@answer = Answer.new(answer_params)
@answer.user_id = current_user.id
if @answer.save
flash[:notice] = "成功!"
redirect_to("/questions/???")
else
flash.now[:alert] = "失敗!"
render "questions/show"
end
end
private
def answer_params
params.require(:answer).permit(:body)
end
end
ログインしているユーザーのみ回答OKにしています。
そしてここで問題発生です。
answersテーブルは、bodyカラムと、user_idカラム、question_idカラムで構成されています。
つまりquestion_idも必要となりますが、questionのidは、どこからとってきたらいいのでしょうか?
URLを見ても、フォームから送られてくる値を見ても、question_idの手掛かりになりそうなものはありません。
またquestionのidが分からないので、redirect_toで、質問の詳細ページに飛ばすことができません。
そこでURLをネストしてあげます。
ネスト?
今のcreateアクションのURLは下記の形式ですよね。
localhost:3000/answers
これを下記のような形式にしてあげます。
localhost:3000/questions/2/answer
これでidが2のquestionに関連したanswerだと、URLを見れば分かるようになります。
このようなURLを作るために、config/routes.rbを下記のように書き換えます。
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'users/registrations'
}
if Rails.env.development?
mount LetterOpenerWeb::Engine, at: "/letter_opener"
end
get "/users/:id", to: "users#show"
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
root to: 'questions#index'
resources :questions do
resources :answers, only: [:create]
end
end
今回、answersコントローラーには、createアクションしか作られないので、only: [:create]をつけています。
ターミナルにrails routesを入力して、URLを確認してみましょう。
rails routes
注意深く見ると、/questions/:question_id/answersというURLが生成されているはずです。
createアクションを下記のように書き換えてあげます。
class AnswersController < ApplicationController
before_action :authenticate_user!
def create
@answer = Answer.new(answer_params)
@answer.user_id = current_user.id
@answer.question_id = params[:question_id]
if @answer.save
flash[:notice] = "成功!"
redirect_to("/questions/#{params[:question_id]}")
else
@question = Question.find(params[:question_id])
flash.now[:alert] = "失敗!"
render "questions/show"
end
end
private
def answer_params
params.require(:answer).permit(:body)
end
end
まずこの回答に関連する質問のidは、params[:question_id]で、URLから取得できます。
いつも使っているparams[:id]ではなく、なぜparams[:question_id]なのか?
もう一度、rails routesで、answersコントローラーのcreateアクションに対応するURLを確認してみましょう。
/questions/:question_id/answers(.:format)となっていますね。
このURLのquestionのidが入る部分は、:question_idというキーだと指定されています。
つまり {question_id: 1} という形で保持されます。
そのためparams[:question_id]と書くことで、questionのidが取得できるという訳です。
質問のidが分かったので、redirect先も指定できます。
あと @question = Question.find(params[:question_id]) という記述も付け加えました。
これは一体何か?
このcreateアクションは、renderで app/views/questions/show.html.erb を呼び出していますね?
app/views/questions/show.html.erbでは、@questionを使っています。
しかしcreateアクションには、@questionがありませんでした。
そのためこのままrenderで app/views/questions/show.html.erb を呼び出すとエラーが発生します。
そこでエラーが出ないように、@questionを定義してあげたわけです。
次はフォームも書き換えましょう。
<p><%= @question.title %></p>
<p><%= @question.body %></p>
<% if @question.user_id == current_user.id %>
<%= link_to("編集", "/questions/#{@question.id}/edit") %>
<%= link_to("削除", "/questions/#{@question.id}", method: :delete, data: {confirm: "本当に削除しますか?"}) %>
<% end %>
<div>
<%= form_with(model: [@question, @answer], local: true) do |f| %>
<div>
<%= f.label :body, "回答" %><br>
<%= f.text_area :body %>
</div>
<%= f.submit %>
<% end %>
</div>
どの質問に対応する回答なのかを指定するために、form_with(model: [@question, @answer])と記述します。
これでネストしたURLに対応したフォームができました。
しかし今のままでは作成した回答が表示されません。
app/views/questions/show.html.erbに回答を表示してあげましょう。
<p><%= @question.title %></p>
<p><%= @question.body %></p>
<% if @question.user_id == current_user.id %>
<%= link_to("編集", "/questions/#{@question.id}/edit") %>
<%= link_to("削除", "/questions/#{@question.id}", method: :delete, data: {confirm: "本当に削除しますか?"}) %>
<% end %>
<div>
<%= form_with(model: [@question, @answer], local: true) do |f| %>
<div>
<%= f.label :body, "回答" %><br>
<%= f.text_area :body %>
</div>
<%= f.submit %>
<% end %>
</div>
<hr>
<div>
<% @question.answers.each do |answer| %>
<p><%= answer.body %></p>
<% end %>
</div>
これでこの質問に関連している回答がすべて表示されます。
ブラウザを開いて「http://localhost:3000/questions/2」にアクセスして、回答を作成してみましょう。
作成すると…
このような表示になったと思います!
回答が表示されています。
質問サイトっぽい雰囲気が出てきましたね!
Railsビギナー 第3章「回答機能作成」まとめ
お疲れ様でした!
第3章終了です。
ここまでこなせたあなたは質問サイトの、基本的な作り方が分かったはずです。
かなりレベルアップした感じもあるのでは?
ぜひここで学習を終わらせず、さらにハイレベルな教材にも挑戦してくださいね。
おすすめはやはり「Railsチュートリアル」です。
現在、第4章も作成中です。
作成できたらTwitterで報告するので、ぜひフォローしてくださいね!
フリーランスとして仕事をとりたい方は、ぜひ下記noteをチェックしてください。
5~6年しっかりとフリーランスを経験した、僕のリアルなノウハウが詰まっています。
お金を払って実務経験を積むサービスが炎上しましたね。 そこまでして実務経験を積まないと、独学で頑張っている未経験者には道がないのでしょうか? 結論を言うと、お金を払って実務経験を積まなくても、未経験でもフリーランスとして仕事をとる方法はあります。 僕は高校時代に数学のテストで8点をとり、大学時代はTOEICで200点台をとるような学生でした。 さらに社会人になってからは、うつ病も経験してドン底を味わっています。 そんな僕でも独学で悠々自適な生活を送るフリーランスになれています! ・Rubyを使ったシステム開発を数多く担当 ・大学の起業支援イベントでプログラミング講師
けっこう頑張って作ったので、ぜひ熱い感想をお聞かせください!
Twitterで「#Railsビギナー」をつけて、感想をつぶやいていただくと、見に行けるので励みになります。
「いずれはプログラミング初心者の学習王道ルートが、『プロゲート』 → 『Railsビギナー』 → 『Railsチュートリアル』になったらいいなぁ…」という願いも抱いています!
それではここまでお付き合いいただき、ありがとうございました!
大好評!公式LINE!独学でフリーランスになった
リアルなノウハウを発信!
これだけ豪華なノウハウが無料…?
IT・AI・マーケティングのリアルなコツや裏ワザを公式LINEで発信中!
知っておくと初心者でも仕事獲得に繋がるツールなど、フリーランスや副業に興味がある方は要チェックの内容です!
さらに今なら下記の豪華プレゼントをゲットできます!