その4から続きます。
21:リファクタリング
app/views/questions/new.html.erbとapp/views/questions/edit.html.erb
の大部分が同じ記述なのでDry原則を破っている
ではどうするか?共通化します。
app/questionsに新規ファイル
_form.html.erbを作成してカットペースト↓
※共通化して呼び出すファイルには_をつけるのがルール
<%= form_with model: @question, local: true do |f| %> <div class="form-group"> <label>Name</label> <!--テキストフィールドをつける--> <%= f.text_field :name, class: "form-control" %> </div> <!--テキストフィールドをつける--> <div class="form-group"> <label>Title</label> <%= f.text_field :title, class: "form-control" %> </div> <!--テキストフィールドをつける--> <div class="form-group"> <label>Content</label> <%= f.text_area :content, class: "form-control" %> </div> <!--saveボタンを付ける--> <div class="text-center"> <%= f.submit "Save", class: "btn btn-primary" %> </div> <% end %>
app/views/questions/new.html.erbを↓のように編集します。
<div> <div class="col-md-4 offset-md-4"> <!--中央に寄せる設定--> <h2 class="text-center">New question</h2> <%= render 'form' %> </div> </div>
app/views/questions/_form.html.erb以下の記述にします。
<form action="/questions" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓" /><input type="hidden" name="authenticity_token" value="aAhSPGiLYkFNeq6FyqkvGFyLFpLooxLsNyIdR4+/e+zq8FgqYyMlmNlWgruyu34AHg22g315u/gbC3GMDw63bw==" /> <div class="form-group"> <label>Name</label> <!--テキストフィールドをつける--> <input class="form-control" type="text" name="question[name]" id="question_name" /> </div> <!--テキストフィールドをつける--> <div class="form-group"> <label>Title</label> <input class="form-control" type="text" name="question[title]" id="question_title" /> </div> <!--テキストフィールドをつける--> <div class="form-group"> <label>Content</label> <textarea class="form-control" name="question[content]" id="question_content"> </textarea> </div> <!--saveボタンを付ける--> <div class="text-center"> <input type="submit" name="commit" value="Save" class="btn btn-primary" data-disable-with="Save" /> </div> </form>
同様にapp/views/questions/edit.html.erbも行います。
<div> <div class="col-md-4 offset-md-4"> <!--中央に寄せる設定--> <h2 class="text-center">Edit question</h2> <%= render 'form' %> </div> </div>
22:質問削除機能の追加
rails routesで確認すると↓
DELETE /questions/:id(.:format) questions#destroy
app/controllers/questions_controller.rbにて↓のように記述します。
def destroy @question = Question.find(params[:id]) @question.destroy redirect_to root_path, notice: 'Success!' end
デストロイアクションが呼ばれるとき、クエスチョンのIDが渡されるので、そのIDを使ってデーターベースから削除したいデータを取得し@questionに代入、
destroyメソッドを呼ぶことで削除して
ルートURLにとばしSuccess!を表示
app/views/questions/index.html.erbを
↓のように記述editの下の部分
[ <%= link_to 'Delete', question_path(question), method: :delete, data:{ confirm: 'Are you sure?'} %> ]</td>
question_path(question)なのはルーティングから
(question)は引数を渡す(id)method: :delete,で削除
data:{ confirm: ‘Are you sure?’}で確認画面を作る
23:質問詳細画面へのリンク
app/views/questions/index.html.erbのtitle.idを↓のように記述します。
<%= link_to question.title, question_path(question) %> #↓idを渡す必要があるため↑questionのインスタンスを渡している question GET /questions/:id(.:format) questions#show
質問詳細画面へリンクしていればOKです。
24:回答投稿機能の概要
ユーザーが質問に対して、回答を投稿できる。
ユーザーが回答一覧を閲覧できる。
ユーザーが回答を編集できる。
ユーザーが回答を削除できる。
25:質問詳細画面の実装
app/controllers/questions_controller.rb↓を追加します。
def show @question = Question.find(params[:id]) end
app/views/questions/show.html.erbにて↓を記述します。
#rowはコンテナを水平に分割し上から下へと並べます。 <div class="row"> #rowの中にcolを定義しレイアウトを組みます #画面幅Mediumのグリッド個数12という意味です。 <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>
質問詳細画面が表示されていればOKです。
26:Answersコントローラーを作成します。
rails g controller answers edit
保有するアクションはそのまま編集する画面で使用するedit
↓のようにファイルが自動生成される
create app/controllers/answers_controller.rb route get 'answers/edit' invoke erb create app/views/answers create app/views/answers/edit.html.erb invoke test_unit create test/controllers/answers_controller_test.rb invoke helper create app/helpers/answers_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/answers.coffee invoke scss create app/assets/stylesheets/answers.scss
重要なのはapp/controllers/answers_controller.rb
app/views/answers/edit.html.erbあとはルーティングなどです。
27:Answerモデルの作成
テーブルの構造 一対多の関係で作ります。
ひとつの質問に対して複数の回答を表示するため、テーブルの構造が一対多になります。
rails g model answer question:references name:string content:text
モデルの名前が単数であることに注意して、
question:references1対多の定義をしています。
生成されたもの↓
invoke active_record create db/migrate/20210304022906_create_answers.rb create app/models/answer.rb invoke test_unit create test/models/answer_test.rb create test/fixtures/answers.yml
db/migrate/20210320022906_create_answers.rbは
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
answersテーブルを作成するための定義がRubyでされている
app/models/answer.rbには
class Answer < ApplicationRecord belongs_to :question end #Answerはquestion↑に紐づく(belong_to)という意味 #answer(答え)から見てquestion(質問)は一つあるという意味
app/models/question.rbに↓記述を追加します。
class Question < ApplicationRecord has_many :answers, dependent: :destroy #←追加 ~~~~~~~~~~~~~以下略 end
questionは複数のanswersを持っているという意味です。
(Yahoo知恵袋のように質問に対して複数の人が質問に答えてくれるから)
dependent: :destroyは親である質問が削除されたら複数の回答も
みんな削除するよという意味です。
変更をデータベースに反映します。
rails db:migrate
エラーが発生しなければOK
28:回答機能関連のルーティング
app/config/routes.rbにて↓のように記述
root 'questions#index' resources :questions do resources :answers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~以下略 end
rails routes ↓のようにanswers関連のルーティングが増えているのを確認します。 answers#index POST /questions/:question_id/answers(.:format) answers#create 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
29:回答投稿機能
app/controllers/questions_controller.rb↓のように記述します。
def show @question = Question.find(params[:id]) @answer = Answer.new end
空のインスタンスを作成してインスタンス変数@answerに代入してviewで使えるようにします。
30:回答投稿機能のviewを作成
app/views/answers/show.html.erbで記述します(<hr>タグから)
<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 %>
[@question, @answer]questionモデルに紐づくanswerモデルを
フォームで送信したい時は、
form_withのmodelに配列で@questionと@answerのインスタンスを渡します。
<%= f.hidden_field :question_id, { value: @question.id} %>
↑htmlには表示されているけど↑ページに描画されない値としてセットしています。
2つのインスタンスの値はcontrollerのshowの部分で渡しています。
def show @question = Question.find(params[:id]) @answer = Answer.new end
このようなビューが作成されてるのを確認したらOKです。
次にPostが押されたときの処理を実装します。