Masahiro Okubo

初心者向け : Railsログイン機能をつけてQAサイトを作る 3 -回答機能+リアクション機能+ベストアンサー機能-

初心者向け : Railsログイン機能をつけてQAサイトを作る 3 -回答機能+リアクション機能+ベストアンサー機能-

※ rails6に対応させました

初心者向け : Railsログイン機能をつけてQAサイトを作る 1 -ログイン機能+質問機能-
初心者向け : Railsログイン機能をつけてQAサイトを作る 2 -Bootstrap+UI修正-
初心者向け : Railsログイン機能をつけてQAサイトを作る 3 -回答機能+リアクション機能+ベストアンサー機能-
初心者向け : Railsログイン機能をつけてQAサイトを作る 4 -タグ付け機能-
初心者向け : Railsログイン機能をつけてQAサイトを作る 5 -管理画面機能-
初心者向け : Railsログイン機能をつけてQAサイトを作る 6 -検索機能-

今回は回答することができるように修正を加えていきます

Answer modelの作成

Answerはuserとquestionに紐づくように生成します

ターミナルでこちらを実行

$ rails g scaffold Answer user:references question:references body:text

次はデータベースを適用させます

$ rails db:migrate

次はQuestoinモデルにAnswerモデルのリレーションを入れます

class Question < ApplicationRecord
  belongs_to :user
  has_many :answers, dependent: :destroy
end

Answer model完了です

 ヘッダーの修正

ヘッダーをuserのタイプによって質問と回答に振り分けます

views/layouts/application.html.erbをこのように修正します

<!DOCTYPE html>
<html>
<head>
  <title>QaSite</title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>

  <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
  <div class="container">
    <a class="navbar-brand" href="#">Start Bootstrap</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarResponsive">
      <ul class="navbar-nav ml-auto">
        <li class="nav-item">
          <!--ここから-->
          <% if user_signed_in? && current_user.role == '質問者' %>
            <%= link_to "質問", "/questions", class: "nav-link" %>
          <% elsif user_signed_in? && current_user.role == '回答者' %>
              <%= link_to "回答", "/answers", class: "nav-link" %>
          <% end %>
          <!--ここまで修正-->
        </li>
        <li class="nav-item">
          <% if user_signed_in? %>
            <a><%= link_to "ログアウト", destroy_user_session_path, class: "nav-link", :method => :delete %></a>
          <% else %>
            <%= link_to "ログイン", "/users/sign_in", class: "nav-link" %>
          <% end %>
        </li>
      </ul>
    </div>
  </div>
</nav>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= yield %>
<footer class="py-5 bg-dark">
  <div class="container">
    <p class="m-0 text-center text-white">Copyright &copy; Your Website 2019</p>
  </div>
</footer>
</body>
</html>

試しに回答者のアカウントを作成して試してみます。

「Sign up」をクリックしてアカウントを作成します

作成できたら右上の「回答」をクリックします

回答すると、回答ページに遷移します

回答者ページで質問をリストする

質問のページを修正した時と同じように回答者のページも修正していきます。

まずはanswers_controller.rbをこのように編集します

class AnswersController < ApplicationController
  # ここを追加
  before_action :authenticate_user!
  before_action :set_answer, only: [:show, :edit, :update, :destroy]

  def index
    # ここを修正
    @questions = Question.all
  end

  def show
  end

  def new
    @answer = Answer.new
  end

  def edit
  end

  def create
    @answer = Answer.new(answer_params)

    respond_to do |format|
      if @answer.save
        format.html { redirect_to @answer, notice: 'Answer was successfully created.' }
        format.json { render :show, status: :created, location: @answer }
      else
        format.html { render :new }
        format.json { render json: @answer.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @answer.update(answer_params)
        format.html { redirect_to @answer, notice: 'Answer was successfully updated.' }
        format.json { render :show, status: :ok, location: @answer }
      else
        format.html { render :edit }
        format.json { render json: @answer.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @answer.destroy
    respond_to do |format|
      format.html { redirect_to answers_url, notice: 'Answer was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    def set_answer
      @answer = Answer.find(params[:id])
    end
    def answer_params
      params.require(:answer).permit(:user_id, :question_id, :body)
    end
end

次はviews/answers/index.html.erbを以下のように修正します

<div class="container">
  <header class="jumbotron my-4">
    <h1 class="display-3">Answers</h1>
  </header>
  <div>現在回答募集中の質問一覧です</div>
  <div class="row text-center">
    <% @questions.each do |question| %>
      <div class="col-lg-3 col-md-6 mb-4">
        <div class="card h-100">
          <img class="card-img-top" src="http://placehold.it/500x325" alt="">
          <div class="card-body">
            <h4 class="card-title"><%= question.title %></h4>
            <p class="card-text"><%= question.body %></p>
          </div>
          <div class="card-footer">
            <%= link_to '回答する', new_answer_path %>
          </div>
        </div>
      </div>
    <% end %>
  </div>
</div>

これで回答者ページに質問を表示できました!

 

回答者ページで回答できる状態にする

回答を作成するページで質問の情報を取得することができるように
まずはルートを変更します

ネストすることでanswersでもquestionのidを取得できるようになり、
管理をしやすくします

※ shallow: true はこちらの記事を参考にしてください

Rails.application.routes.draw do
  root 'homepage#index'
  get 'questions' => 'questions#index'
  devise_for :users, controllers: {
    registrations: 'users/registrations',
    sessions: 'users/sessions'
  }
  # ここを修正
  get 'answers' => 'answers#index'
  resources :questions, shallow: true do
    resources :answers
  end
end

routesを変更した後にブラウザにいくとこのようなエラーが発生します

先ほどルートを修正したので、new_answerが無い!というエラーになっています。

この画面の下のほうに行くと

/questions/question_id/answers的なリンクがあります

ネストを行うと、親要素のquestionsからリンクが始まるようになりました

最初は戸惑うかもしれませんが、questionとのデータの紐付けが楽になるので実はとても楽になります

ではルートの修正していきます

app/views/answers/index.html.erb

<div class="container">
  <header class="jumbotron my-4">
    <h1 class="display-3">Answers</h1>
  </header>
  <div>現在回答募集中の質問一覧です</div>
  <div class="row text-center">
    <% @questions.each do |question| %>
      <div class="col-lg-3 col-md-6 mb-4">
        <div class="card h-100">
          <img class="card-img-top" src="http://placehold.it/500x325" alt="">
          <div class="card-body">
            <h4 class="card-title"><%= question.title %></h4>
            <p class="card-text"><%= question.body %></p>
          </div>
          <div class="card-footer">
            <!-- ここを編集 -->
            <%= link_to '回答する', new_question_answer_path(question.id) %>
          </div>
        </div>
      </div>
    <% end %>
  </div>
</div>

修正後はエラーが出ず画面が表示されます

それでは適用に「回答する」ボタンをクリックして回答画面を表示します

UserやQuestionはコントローラー側で制御できるのでこのように修正します

app/controllers/answers_controller.rb

def new
  question = Question.find(params[:question_id])
  @answer = question.answers.build(user_id: current_user.id)
end

# POST /answers
# POST /answers.json
def create
  question = Question.find(params[:question_id])
  @answer = question.answers.build(answer_params)
  respond_to do |format|
    if @answer.save
      format.html { redirect_to @answer, notice: 'Answer was successfully created.' }
      format.json { render :show, status: :created, location: @answer }
    else
      format.html { render :new }
      format.json { render json: @answer.errors, status: :unprocessable_entity }
    end
  end
end

app/views/answers/new.html.erb

<h1>New Answer</h1>

<!-- 回答する際に質問を表示しておくと分かりやすそうなので配置 -->
<div>質問情報</div>
タイトル:<%= @answer.question.title %><br>
内容:<%= @answer.question.body %><br>
<%= form_with(model: @answer, url: question_answers_path,local: true) do |form| %>
  <% if @answer.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@answer.errors.count, "error") %> prohibited this answer from being saved:</h2>
      <ul>
        <% @answer.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>

  <!-- ここにもuser_idを入れておかないとparamsに入らない -->
  <%= form.hidden_field :user_id, value: current_user.id %>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

<%= link_to 'Back', answers_path %>

修正したので画面にいきます

データを追加してみます

質問に対して回答を追加できました!

念のため正常に登録できたかどうかターミナルで確認します

$ rails c
irb(main):002:0> Answer.all
#<ActiveRecord::Relation [#<Answer id: 1, user_id: 3, question_id: 1, body: "231321", created_at: "2020-10-19 14:54:17", updated_at: "2020-10-19 14:54:17">, #<Answer id: 2, user_id: 3, question_id: 1, body: "3123131321321", created_at: "2020-10-19 14:56:48", updated_at: "2020-10-19 14:56:48">]>

無事に登録できています!

質問者のページから回答を確認できる状態にする

app/views/question/show.html.erbをこのように修正します

<div class="container">
  <header class="jumbotron my-4">
    <h2 class="card-title"><%= @question.title %></h2>
  </header>
  <div class="card-body">
    内容:<br>
    <%= @question.body %>
  </div>
  <!--ここから下が回答-->
  <div class="row">
    <!-- @questionがたくさん持っている(has_manyな)answersをloopさせて一つずつ表示
    has_manyなデータを取得するときは、配列でデータを取得します。 -->
    <% @question.answers.each do |answer| %>
      <div class="col-lg-12 col-md-12">
        <div class="card h-100">
          <div class="card-body">
            <div><%= answer.body %></div>
            <p class="text-right">回答ユーザー<%= answer.user.name %></p>
          </div>
        </div>
      </div>
    <% end %>
  </div>
  <%= link_to 'Edit', edit_question_path(@question) %> |
  <%= link_to 'Back', questions_path %>
</div>

questionsは質問者のみ見ることができるので、回答者アカウントはログアウトして質問者でログインします

赤枠をクリックします(私の場合は。作成した任意の質問をクリックしてください)

しかしこれでは回答を確認できるだけであり、回答に対してのリアクションを
行うことができません
次は回答に対するリアクションをすることができる状態にしていきます

質問者のページから回答に対して反応を行うことができる状態にする

リアクションを行うにはAnswer modelに紐づくモデルを作成します

冒頭でAnswer modelに回答と回答に対する反応を入れると想定していましたが、
設計ミスだったので、Reaction modelを追加します
追加後はこのようなモデル関係になります

何回もmodelのリレーションをやっているのでそろそろ慣れてきた頃かと思います
Answer modelはReaction modelをhas_manyな状態で
Reaction modelはAnswer modelに対してbelongs_toな状態です

つまり、回答一つに対して、たくさんの反応があることを想定しています

それではReaction modelを作成していきます

こちらをターミナルで実行

$ rails g scaffold Reaction user:references answer:references body:text
$ rails db:migrate

その後Answer modelにリレーションを与えます

class Answer < ApplicationRecord
  belongs_to :user
  belongs_to :question
  has_many :reactions, dependent: :destroy
end

次はanswerの時と同じように、リンクからanswerの情報を取得できるように
ルーティングを修正します
※reactionはanswerに紐づいているため、questionではなくanswerの情報を取得します

Rails.application.routes.draw do
  root 'homepage#index'
  devise_for :users, controllers: {
    registrations: 'users/registrations',
    sessions: 'users/sessions'
  }
  # ここを修正
  get 'answers' => 'answers#index'
  resources :questions, shallow: true do
    resources :answers, shallow: true do
      resources :reactions
    end
  end
end

次はapp/views/questions/show.html.erbにReactionのリンクを追加します

<div class="container">
  <header class="jumbotron my-4">
    <h2 class="card-title"><%= @question.title %></h2>
  </header>
  <div class="card-body">
    内容:<br>
    <%= @question.body %>
  </div>
  <!--ここから下が回答-->
  <div class="row">
    <!-- @questionがたくさん持っている(has_manyな)answersをloopさせて一つずつ表示
    has_manyなデータを取得するときは、配列でデータを取得します。 -->
    <% @question.answers.each do |answer| %>
      <div class="col-lg-12 col-md-12">
        <div class="card h-100">
          <div class="card-body">
            <div><%= answer.body %></div>
            <p class="text-right">回答ユーザー<%= answer.user.name %></p>
            <p class="text-right"><%= link_to '反応する', new_answer_reaction_path(answer.id) %></p>
          </div>
        </div>
      </div>
    <% end %>
  </div>
  <%= link_to 'Edit', edit_question_path(@question) %> |
  <%= link_to 'Back', questions_path %>
</div>

それでは確認してみましょう

questionの中のanswerの右下に「反応する」ボタンがあるのでクリックします

クリックするとこのようなエラーがめんが出るので、こちらのコードに修正します

app/views/reactions/new.html.erb

<h1>New Reaction</h1>

<%= form_with(model: @reaction, url: answer_reactions_path(@reaction.answer.id), local: true) do |form| %>
  <% if @reaction.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@reaction.errors.count, "error") %> prohibited this reaction from being saved:</h2>

      <ul>
        <% @reaction.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <!-- ここにもuser_idを入れておかないとparamsに入らない -->
  <%= form.hidden_field :user_id, value: current_user.id %>

  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

app/controller/reactions_controller.rb

# GET /reactions/new
def new
  answer = Answer.find(params[:answer_id])
  @reaction = answer.reactions.build(user_id: current_user.id)
end

# POST /reactions
# POST /reactions.json
def create
    answer = Answer.find(params[:answer_id])
    @reaction = answer.reactions.build(reaction_params)

    respond_to do |format|
      if @reaction.save
        format.html { redirect_to answer.question, notice: 'Reaction was successfully created.' }
        format.json { render :show, status: :created, location: @reaction }
      else
        format.html { render :new }
        format.json { render json: @reaction.errors, status: :unprocessable_entity }
      end
    end
  end

画面にいくと今度はエラーが出ずに表示されます

データを入れて保存してみます

問題なく保存され、questionまで戻りました!

redirectでquestionを指定しているのでquestionに戻ります

reactionは保存できたので次はquestionsからreactionsを見れるように修正します

views/questions/show.html.erbをこのように修正します

<div class="container">
  <header class="jumbotron my-4">
    <h2 class="card-title"><%= @question.title %></h2>
  </header>
  <div class="card-body">
    内容:<br>
    <%= @question.body %>
  </div>
  <!--ここから下が回答-->
  <div class="row">
    <!-- @questionがたくさん持っている(has_manyな)answersをloopさせて一つずつ表示
    has_manyなデータを取得するときは、配列でデータを取得します。 -->
    <% @question.answers.each do |answer| %>
      <div class="col-lg-12 col-md-12">
        <div class="card h-100">
          <div class="card-body">
            <div><%= answer.body %></div>
            <p class="text-right">回答ユーザー<%= answer.user.name %></p>
            <% answer.reactions.each do |reaction| %>
              <div class="row">
                <div class="col-lg-12 col-md-12">
                  <div class="card h-100">
                    <div class="card-body">
                      <div><%= reaction.body %></div>
                      <p class="text-right">回答ユーザー<%= reaction.user.name %></p>
                    </div>
                  </div>
                </div>
              </div>
              <br>
            <% end %>
            <p class="text-right"><%= link_to '反応する',  new_answer_reaction_path(answer.id)%></p>
          </div>
        </div>
      </div>
    <% end %>
  </div>
  <%= link_to 'Edit', edit_question_path(@question) %> |
  <%= link_to 'Back', questions_path %>
</div>

この修正の後にブラウザに戻るとreactionも表示されています

質問の詳細画面をみてみるとこのようなUIとなっています
「反応する」からデータを作成するとたくさんの反応をリストすることができます

回答者と質問者の画面を統合

次は回答者のページから回答に対する反応ができる状態にしていきますが、
回答者のユーザーと質問者のユーザーの画面はほとんど同じです
なので、今まで分かれて作成されていた画面を一つで管理し、
シンプルにします
また、今まで英語で書かれていた部分等も微修正を加えていきます

まずはヘッダーの内容を変更するためにviews/layouts/application.html.erbを
このように修正します

<!DOCTYPE html>
<html>
<head>
  <title>QaSite</title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>

  <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
  <div class="container">
    <a class="navbar-brand" href="#">Start Bootstrap</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarResponsive">
      <ul class="navbar-nav ml-auto">
        <li class="nav-item">
          <!--ここから-->
          <% if user_signed_in? && current_user.role == '質問者' %>
            <%= link_to "質問", "/questions", class: "nav-link" %>
          <% elsif user_signed_in? && current_user.role == '回答者' %>
              <%= link_to "回答", "/questions", class: "nav-link" %>
          <% end %>
          <!--ここまで修正-->
        </li>
        <li class="nav-item">
          <% if user_signed_in? %>
            <a><%= link_to "ログアウト", destroy_user_session_path, class: "nav-link", :method => :delete %></a>
          <% else %>
            <%= link_to "ログイン", "/users/sign_in", class: "nav-link" %>
          <% end %>
        </li>
      </ul>
    </div>
  </div>
</nav>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= yield %>
<footer class="py-5 bg-dark">
  <div class="container">
    <p class="m-0 text-center text-white">Copyright &copy; Your Website 2019</p>
  </div>
</footer>
</body>
</html>

次はquestion_controller.rbをこのように修正します

class QuestionsController < ApplicationController
  before_action :authenticate_user!
  before_action :set_question, only: [:show, :edit, :update, :destroy]

  def index
    # ユーザータイプによって取得内容を変更
    @questions = current_user.questions if current_user.role == '質問者'
    @questions = Question.all if current_user.role == '回答者'
  end

  def index
    @questions = current_user.questions.all
  end

  def show
  end

  def new
    @question = Question.new
  end

  def edit
  end

  def create
    @question = current_user.questions.build(question_params)

    respond_to do |format|
      if @question.save
        format.html {redirect_to @question, notice: 'Question was successfully created.'}
        format.json {render :show, status: :created, location: @question}
      else
        format.html {render :new}
        format.json {render json: @question.errors, status: :unprocessable_entity}
      end
    end
  end

  def update
    respond_to do |format|
      if @question.update(question_params)
        format.html { redirect_to @question, notice: 'Question was successfully updated.' }
        format.json { render :show, status: :ok, location: @question }
      else
        format.html { render :edit }
        format.json { render json: @question.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @question.destroy
    respond_to do |format|
      format.html { redirect_to questions_url, notice: 'Question was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

  def set_question
    @question = Question.find(params[:id])
  end

  def question_params
    params.require(:question).permit(:user_id, :title, :body)
  end
end

views/questions/index.html.erbをこのように修正

<div class="container">
  <header class="jumbotron my-4">
    <h1 class="display-3">質問一覧</h1>
    <!--質問者のみ質問ができるように修正-->
    <% if current_user.role == '質問者' %>
      <%= link_to '質問する!', new_question_path, class: 'btn btn-primary btn-lg' %>
    <% end %>
  </header>
  <div class="row text-center">
    <% @questions.each do |question| %>
      <div class="col-lg-3 col-md-6 mb-4">
        <div class="card h-100">
          <img class="card-img-top" src="http://placehold.it/500x325" alt="">
          <div class="card-body">
            <h4 class="card-title"><%= question.title %></h4>
            <p class="card-text"><%= question.body %></p>
          </div>
          <div class="card-footer">
            <%= link_to '詳細', question %>
          </div>
        </div>
      </div>
    <% end %>

  </div>
</div>

次はviews/questions/show.html.erbをこのように修正します

<div class="container">
  <!--使いやすいように上にも追加-->
  <div class="text-right">
    <!--この質問をしたユーザーであれば修正を許可-->
    <% if current_user.id == @question.user_id %>
      <%= link_to '修正', edit_question_path(@question) %> |
    <% end %>
    <%= link_to '戻る', questions_path %>
  </div>
  <header class="jumbotron my-4">
    <h2 class="card-title"><%= @question.title %></h2>
    <% if current_user.role == '回答者' %>
      <%= link_to '回答する!', question_answers_path(@question.id), class: 'btn btn-primary btn-lg' %>
    <% end %>
  </header>
  <div class="card-body">
    内容:<br>
    <%= @question.body %>
  </div>
  <div class="row">
    <% @question.answers.each do |answer| %>
      <div class="col-lg-12 col-md-12">
        <div class="card h-100">
          <div class="card-body">
            <div><%= answer.body %></div>
            <p class="text-right">回答ユーザー<%= answer.user.name %></p>
            <% answer.reactions.each do |reaction| %>
              <div class="row">
                <div class="col-lg-12 col-md-12">
                  <div class="card h-100">
                    <div class="card-body">
                      <div><%= reaction.body %></div>
                      <p class="text-right">回答ユーザー<%= reaction.user.name %></p>
                    </div>
                  </div>
                </div>
              </div>
              <br>
            <% end %>
            <p class="text-right"><%= link_to '反応する', answer_reactions_path(answer.id) %></p>
          </div>
        </div>
      </div>
    <% end %>
  </div>
  <div class="text-right">
    <!--この質問をしたユーザーであれば修正を許可-->
    <% if current_user.id == @question.user_id %>
      <%= link_to '修正', edit_question_path(@question) %> |
    <% end %>
    <%= link_to '戻る', questions_path %>
  </div>
</div>

それでは確認します

質問ユーザー画面
質問ユーザーかつ自分がした質問は「修正」ボタンが表示されています

回答ユーザー画面

・「修正」ボタンがない

・「回答する!」ボタンから回答することができる

2つのユーザーによって画面が少し変わっています
htmlにif文を入れるだけでものすごくアプリっぽくなるので色々試してみたください

ちなみに「反応する」からそれぞれのユーザーで登録しても問題なくできます

answer_controller.rbのcreateアクションはこのように記載されています
※修正等は必要ありません。説明のために提示しているので修正は必要ありません。

user_idには現在ログインしているユーザーのidを入れる設定をしているので
このままでも問題なくシステムを使うことができます

def create
    question = Question.find(params[:question_id])
    @answer = question.answers.build(answer_params)
    respond_to do |format|
      if @answer.save
        format.html { redirect_to @answer, notice: 'Answer was successfully created.' }
        format.json { render :show, status: :created, location: @answer }
      else
        format.html { render :new }
        format.json { render json: @answer.errors, status: :unprocessable_entity }
      end
    end
  end

ただし、現在はテストユーザーだけを登録していてわかりにくいので、
ヘッダーに自分のユーザー情報を表示し、
自分の名前が質問や回答の中で目立つようにしましょう

views/layouts/application.html.erbはこのように修正

<!DOCTYPE html>
<html>
<head>
  <title>QaSite</title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>

  <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
  <div class="container">
    <%= link_to 'Start Bootstrap', root_path, class: 'navbar-brand' %>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarResponsive">
      <ul class="navbar-nav ml-auto">
        <% if user_signed_in? %>
          <li class="nav-item">
            <span class="nav-link">ログイン中:<%= current_user.name %></span>
          </li>
        <% end %>
        <li class="nav-item">
          <!--ここから-->
          <% if user_signed_in? && current_user.role == '質問者' %>
            <%= link_to '質問', questions_path, class: "nav-link" %>
          <% elsif user_signed_in? && current_user.role == '回答者' %>
            <%= link_to '回答', answers_path, class: "nav-link" %>
          <% end %>
          <!--ここまで修正-->
        </li>
        <li class="nav-item">
          <% if user_signed_in? %>
            <a><%= link_to "ログアウト", destroy_user_session_path, class: "nav-link", :method => :delete %></a>
          <% else %>
            <%= link_to "ログイン", "/users/sign_in", class: "nav-link" %>
          <% end %>
        </li>
      </ul>
    </div>
  </div>
</nav>
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= yield %>
<footer class="py-5 bg-dark">
  <div class="container">
    <p class="m-0 text-center text-white">Copyright &copy; Your Website 2019</p>
  </div>
</footer>
</body>
</html>

app/javascript/stylesheets/application.scssにこちらを追加

.user_color{
    background-color: blue;
    color: white;
}

次はviews/questions/show.html.erbをこのように修正

<div class="container">
  <div class="text-right">
    <% if current_user.id == @question.user_id %>
      <%= link_to '修正', edit_question_path(@question) %> |
    <% end %>
    <%= link_to '戻る', questions_path %>
  </div>
  <header class="jumbotron my-4">
    <h2 class="card-title"><%= @question.title %></h2>
    <% if current_user.role == '回答者' %>
      <%= link_to '回答する!', question_answers_path(@question.id), class: 'btn btn-primary btn-lg' %>
    <% end %>
  </header>
  <div class="card-body">
    内容:<br><%= @question.body %><br>

    質問ユーザー:<span class="<%= "user_color" if current_user.id == @question.user_id %>"><%= @question.user.name %></span>
  </div>
  <div class="row">
    <% @question.answers.each do |answer| %>
      <div class="col-lg-12 col-md-12">
        <div class="card h-100">
          <div class="card-body">
            <div><%= answer.body %></div>
            <p class="text-right">
<span class="<%= "user_color" if current_user.id == answer.user_id %>">
回答ユーザー:<%= answer.user.name %>
</span>
            </p>
            <% answer.reactions.each do |reaction| %>
              <div class="row">
                <div class="col-lg-12 col-md-12">
                  <div class="card h-100">
                    <div class="card-body">
                      <div><%= reaction.body %></div>
                      <p class="text-right">
<span class="<%= "user_color" if current_user.id == reaction.user_id %>">
回答ユーザー:<%= reaction.user.name %>
</span>
                      </p>
                    </div>
                  </div>
                </div>
              </div>
              <br>
            <% end %>
            <p class="text-right"><%= link_to '反応する', answer_reactions_path(answer.id) %></p>
          </div>
        </div>
      </div>
    <% end %>
  </div>
  <div class="text-right">
    <% if current_user.id == @question.user_id %>
      <%= link_to '修正', edit_question_path(@question) %> |
    <% end %>
    <%= link_to '戻る', questions_path %>
  </div>
</div>

画面を確認すると、自分が回答したもしくは反応したユーザー名の色が変わっています

質問ユーザーでログインした際の画面

回答ユーザーでログインした際の画面

ちなみにまだupdateやdestoryのチェック等は行なっていません
この辺りは勉強もかねて自分で確認してみましょう

解決機能を作成する

解決機能を作ることは難しくありません
基本的に「解決できた」「解決できていない」などの0か1の判断を行う際は
データベースにtrue or falseで区別するか、
カラムをnullもしくは何か判断する情報を入れる、というがありますが、
今回は後者で行きます

まずはその前にデータベースのデータを統一させたいので、
こちらの情報をdb/seeds.rbに入れます

# ユーザーのデータ
User.create(
    [
        {name: '鈴木太郎', email: 'sitsumon1@gmail.com', role: '質問者', password: '11111111', password_confirmation: '11111111'},
        {name: '佐々木太郎', email: 'sitsumon2@gmail.com', role: '質問者', password: '11111111', password_confirmation: '11111111'},
        {name: '田中太郎', email: 'sitsumon3@gmail.com', role: '質問者', password: '11111111', password_confirmation: '11111111'},
        {name: '高橋花子', email: 'kaitou1@gmail.com', role: '回答者', password: '11111111', password_confirmation: '11111111'},
        {name: '斎藤花子', email: 'kaitou2@gmail.com', role: '回答者', password: '11111111', password_confirmation: '11111111'},
        {name: '池田花子', email: 'kaitou3@gmail.com', role: '回答者', password: '11111111', password_confirmation: '11111111'}])

Question.create(
    [
        {title: '東京のお土産で喜ばれるもの', body: '今週末東京に行きますが、東京土産で喜ばれるものありますか?', user_id: 1},
        {title: '好きな動物', body: '一番好きな動物は何ですか?', user_id: 1},
        {title: '美味しいご飯や', body: '名古屋周辺の美味しいご飯屋さん教えてください', user_id: 2},
        {title: '明日の天気', body: '明日の天気は?', user_id: 2},
        {title: '読書時間について', body: '一日の読書時間はどれぐらい?', user_id: 3},
        {title: '英語の勉強法', body: '英語はどうやって勉強したらいいですか?', user_id: 3}])

Answer.create(
    [
        {body: '東京ばな奈じゃないですか?', question_id: 1, user_id: 4},
        {body: 'ラスクみたいなやつ', question_id: 1, user_id: 5},
        {body: '中華まん', question_id: 1, user_id: 6},
        {body: '猫', question_id: 2, user_id: 4},
        {body: '犬', question_id: 2, user_id: 5},
        {body: 'とり', question_id: 2, user_id: 6},
        {body: '矢場とん', question_id: 3, user_id: 4},
        {body: '山ちゃん', question_id: 3, user_id: 5},
        {body: 'みせん', question_id: 3, user_id: 6},
        {body: '晴れ', question_id: 4, user_id: 4},
        {body: '曇り', question_id: 4, user_id: 5},
        {body: '雨', question_id: 4, user_id: 6},
        {body: '1時間', question_id: 5, user_id: 4},
        {body: '2時間', question_id: 5, user_id: 5},
        {body: '3時間', question_id: 5, user_id: 6},
        {body: '特かく話す', question_id: 6, user_id: 4},
        {body: '単語の勉強', question_id: 6, user_id: 5},
        {body: 'レッスンに行く', question_id: 6, user_id: 6}])

Reaction.create(
    [
        {body: '東京ばな奈いいですね', user_id: 1, answer_id: 1},
        {body: '猫いいですね!', user_id: 1, answer_id: 4},
        {body: '矢場とんいいですね!', user_id: 3, answer_id: 7}])

seeds.rbのデータはデータベースにデータを投入するときに使ったりします

次はdb/development.sqlite3を削除します

削除が終わったらもう一度データベースを作成し、データを入れます

# データベース作成
$ rails db:migrate
# データ投入
$ rails db:seed
# サーバー起動
# rails s

サーバーが起動したら先ほど投入したデータの「鈴木太郎」でログインします

User.create(
    [
        {name: '鈴木太郎', email: 'sitsumon1@gmail.com', role: '質問者', password: '11111111', password_confirmation: '11111111'},
        {name: '佐々木太郎', email: 'sitsumon2@gmail.com', role: '質問者', password: '11111111', password_confirmation: '11111111'},
        {name: '田中太郎', email: 'sitsumon3@gmail.com', role: '質問者', password: '11111111', password_confirmation: '11111111'},
        {name: '高橋花子', email: 'kaitou1@gmail.com', role: '回答者', password: '11111111', password_confirmation: '11111111'},
        {name: '斎藤花子', email: 'kaitou2@gmail.com', role: '回答者', password: '11111111', password_confirmation: '11111111'},
        {name: '池田花子', email: 'kaitou3@gmail.com', role: '回答者', password: '11111111', password_confirmation: '11111111'}])

ログインが完了するとこのようなデータを確認することができます

これでデータを統一できたので、確認作業が簡単になりました!

それではquestionに解決機能を追加していきます

まずはQuestion modelにbest_answer_idを追加します
解決したらこのカラムにanswer_idを追加して
もしbest_answer_idがあれば解決したとみなす、
というコードに修正していきます

まずはQuestion modelに「best_answer_id」カラムを追加するために
マイグレーションファイルを生成します

$ rails g migration AddBestAnswerIdToQuestions best_answer_id:integer

生成されたマイグレーションファイルはこのようなコードになっています

class AddBestAnswerIdToQuestions < ActiveRecord::Migration[5.2]
  def change
    # デフォルトでは完了していないnilを入れます。完了する際に1を入れます
    add_column :questions, :best_answer_id, :integer, default: nil
  end
end

次はターミナルでこちらを実行

$ rails db:migrate

その後、データベースが入っているかdb/schema.rbを確認します
questionsにis_finishedちゃんと入っています
※このファイルは修正したりしないでください。
今はわかりやすく説明するためにコメントを入れています、
schema.rbやマイグレーションファイルは絶対に手を加えてはなりません
shcema.rbやマイグレーションファイルを触るということは
データベースの設計を変えることになってしまうので
初心者のうちは触ると直せなくなってしまいます。

ActiveRecord::Schema.define(version: 2019_07_20_065004) do

  create_table "answers", force: :cascade do |t|
    t.integer "user_id"
    t.integer "question_id"
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["question_id"], name: "index_answers_on_question_id"
    t.index ["user_id"], name: "index_answers_on_user_id"
  end
  # ここにbest_answer_idが入っている!
  create_table "questions", force: :cascade do |t|
    t.integer "user_id"
    t.string "title"
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer "best_answer_id"
    t.index ["user_id"], name: "index_questions_on_user_id"
  end

  create_table "reactions", force: :cascade do |t|
    t.integer "user_id"
    t.integer "answer_id"
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["answer_id"], name: "index_reactions_on_answer_id"
    t.index ["user_id"], name: "index_reactions_on_user_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "name", default: "", null: false
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "role", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

end


次はviews/questions/show.html.erbをこのように修正します

<div class="container">
  <div class="text-right">
    <% if current_user.id == @question.user_id %>
      <%= link_to '修正', edit_question_path(@question) %> |
    <% end %>
    <%= link_to '戻る', questions_path %>
  </div>
  <header class="jumbotron my-4">
    <h2 class="card-title"><%= @question.title %></h2>
    <% if current_user.role == '回答者' %>
      <%= link_to '回答する!', question_answers_path(@question.id), class: 'btn btn-primary btn-lg' %>
    <% end %>
  </header>
  <div class="card-body">
    内容:<br><%= @question.body %><br>
    質問ユーザー:<span class="<%= "user_color" if current_user.id == @question.user_id %>"><%= @question.user.name %></span>
  </div>
  <div class="row">
    <% @question.answers.each do |answer| %>
      <div class="col-lg-11 col-md-11">
        <div class="card h-100">
          <!--ベストアンサーの色を変更するための修正-->
          <div class="card-body  <%= "best-answer" if @question.best_answer_id == answer.id %>">
            <div><%= answer.body %></div>
            <p class="text-right">
              <span class="<%= "user_color" if current_user.id == answer.user_id %>">
                回答ユーザー:<%= answer.user.name %>
              </span>
            </p>
            <% answer.reactions.each do |reaction| %>
              <div class="row">
                <div class="col-lg-12 col-md-12">
                  <div class="card h-100">
                    <div class="card-body">
                      <div><%= reaction.body %></div>
                      <p class="text-right">
                        <span class="<%= "user_color" if current_user.id == reaction.user_id %>">
                          回答ユーザー:<%= reaction.user.name %>
                        </span>
                      </p>
                    </div>
                  </div>
                </div>
              </div>
              <br>
            <% end %>
            <p class="text-right"><%= link_to '反応する', answer_reactions_path(answer.id) %></p>
          </div>
        </div>
      </div>
      <div class="col-lg-1 col-md-1 ba-parent">
        <!--ここから追加-->
        <div class="ba-child text-center">
          <%= form_with(model: @question, local: true) do |form| %>
            <%= form.hidden_field :best_answer_id, value: answer.id %>
            <div class="actions">
              <%= form.submit 'BA'%>
            </div>
          <% end %>
        </div>
        <!--ここまで-->
      </div>

    <% end %>
  </div>
  <div class="text-right">
    <% if current_user.id == @question.user_id %>
      <%= link_to '修正', edit_question_path(@question) %> |
    <% end %>
    <%= link_to '戻る', questions_path %>
  </div>
</div>

次はquestions_controller.rbをこのように修正

def question_params
    params.require(:question).permit(:user_id, :title, :body, :best_answer_id)
end

最後にapp/javascript/stylesheets/application.scssを修正

@import '~bootstrap/scss/bootstrap';

.user_color {
  background-color: blue;
  color: white;
}

.ba-parent {
  position: relative;
  .ba-child {
    position: absolute;
    top: 50%;
    left: 50%;
  }
}

.best-answer{
  background-color: red;
  color: white;
}

この修正が完了すると、[BA]ボタンを押すとこのようなUIになります

これで一応は完了です!

次はベストアンサーが決まっていたら

・修正ができない

・BAを変更できない

とうの修正を行なっていきます

解決した質問は表示しない設定を与える

views/questions/show.html.erbをこのように修正

<div class="container">

  <div class="text-right">
    <% if current_user.id == @question.user_id %>
      <%= link_to_unless @question.best_answer_id, '修正', edit_question_path(@question) %> |
    <% end %>
    <%= link_to '戻る', questions_path %>
  </div>
  <header class="jumbotron my-4">
    <h2 class="card-title"><%= @question.title %></h2>
    <% if current_user.role == '回答者' %>
      <%= link_to '回答する!', question_answers_path(@question.id), class: 'btn btn-primary btn-lg' %>
    <% end %>
  </header>
  <div class="card-body">
    内容:<br><%= @question.body %><br>
    質問ユーザー:<span class="<%= "user_color" if current_user.id == @question.user_id %>"><%= @question.user.name %></span>
  </div>
  <div class="row">
    <% @question.answers.each do |answer| %>
      <div class="col-lg-11 col-md-11">
        <div class="card h-100">
          <!--ベストアンサーの色を変更するための修正-->
          <div class="card-body  <%= "best-answer" if @question.best_answer_id == answer.id %>">
            <div><%= answer.body %></div>
            <p class="text-right">
              <span class="<%= "user_color" if current_user.id == answer.user_id %>">
                回答ユーザー:<%= answer.user.name %>
              </span>
            </p>
            <% answer.reactions.each do |reaction| %>
              <div class="row">
                <div class="col-lg-12 col-md-12">
                  <div class="card h-100">
                    <div class="card-body">
                      <div><%= reaction.body %></div>
                      <p class="text-right">
                        <span class="<%= "user_color" if current_user.id == reaction.user_id %>">
                          回答ユーザー:<%= reaction.user.name %>
                        </span>
                      </p>
                    </div>
                  </div>
                </div>
              </div>
              <br>
            <% end %>
            <!-- ベストアンサーが決まっていたら修正ができないようにする-->
            <p class="text-right"><%= link_to_unless @question.best_answer_id,  '反応する', answer_reactions_path(answer.id) %></p>
          </div>
        </div>
      </div>
      <div class="col-lg-1 col-md-1 ba-parent">
        <!--ここから追加-->
        <!--disablesと確認ダイアログを追加-->
        <div class="ba-child text-center">
          <%= form_with(model: @question, local: true, data: {confirm: "#{answer.user.name}さんの回答をベストアンサーにします。この修正は変更できませんが、よろしいですか?"}) do |form| %>
            <%= form.hidden_field :best_answer_id, value: answer.id %>
            <div class="actions">
              <%= form.submit 'BA', disabled: @question.best_answer_id.present? %>
            </div>
          <% end %>
        </div>
        <!--ここまで-->
      </div>

    <% end %>
  </div>
  <div class="text-right">
    <% if current_user.id == @question.user_id %>
      <!-- ベストアンサーが決まっていたら修正ができないようにする-->
      <%= link_to_unless @question.best_answer_id, '修正', edit_question_path(@question) %> |
    <% end %>
    <%= link_to '戻る', questions_path %>
  </div>
</div>

画面を確認します

ベストアンサーを選ぶ前

「BA」ボタンを押した後

確認ダイアログが出てきます

「OK」ボタンを押すとBAが確定し、「修正」ボタンや「BA」ボタンが押せなくなります

これで完成です、お疲れ様でした!

次回はタグ付を行っていきます

参考記事

Where to put Ruby helper methods for Rails controllers?

Rails – using CSS on some condition

Rails link_to or button_to post request with parameters

Button_to uses POST Link_to uses GET, why? ROR

Why confirm option of rails button_to helper is not working

Buy Me A Coffee

関連記事

copyright© 2016-2021 Masahiro Okubo