ミニQ & Aサービスの開発

Udemyの教材でミニQ&Aサービスの開発について学んだので、備忘録として記録します。
教材：[https://www.udemy.com/course/the-ultimate-ruby-on-rails-bootcamp/](https://www.udemy.com/course/the-ultimate-ruby-on-rails-bootcamp/)


## Rubyのバージョン管理

今回はバージョン2.5.1で進めました。

```terminal:ターミナル
$ rvm -v #rvmのバージョンを調べる
$ rvm 1.29.8 (1.29.8) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]

$ rvm list
=* ruby-2.5.1 [ x86_64 ]
   ruby-2.6.3 [ x86_64 ]

# => - current
# =* - current && default
#  * - default

$ rvm install 2.5.1 #バージョンを指定してインストールしたい場合

$ rvm use 2.5.1  #currentになる
$ rvm --default use 2.5.1  #current && defaultになる
```

## Railsのインストール

バージョンを指定する場合の書き方。-Nはドキュメントをインストールしないという意味で時間短縮になる。

```terminal:ターミナル
$ gem install rails -v 5.2.1 -N
```
`Bundler`　RubyGemsを管理するツール

`RubyGems`　Rubyで書かれたサードパーティ製のライブラリ（プログラムのまとまり）
ex) ユーザー認証機能、画像の管理機能、管理画面の機能、など
[http://rubygems.org/](http://rubygems.org/)


## ミニQ and Aアプリ作成

```terminal:ターミナル
$ rails _5.2.1_ new qanda
```

```ruby:Gemfile
#エラー回避
#変更前
gem 'sqlite3'
#変更後
gem 'sqlite3', '~> 1.3.6'
```

```terminal:ターミナル
$ bundle update
```

データベースを作成する

```terminal:ターミナル
rails db:create
```

Bootstrapの導入

```ruby:Gemfile
gem 'bootstrap', '~> 4.1.1'
gem 'jquery-rails', '~> 4.3.1' #bootstrapはjQueryに依存している
```

```terminal:ターミナル
$ bundle install
```

stylesheetのファイル形式変更（CSS→Sass）

```terminal:ターミナル
$ mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss
```

```scss:application.scss
@import "bootstrap";
```

```javascript:application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery3 #ここを追記
//= require popper #ここを追記
//= require bootstrap-sprockets #ここを追記
//= require_tree .
```

##route
ルーティングの設定


```terminal:ターミナル
$ rails routes

Prefix Verb   URI Pattern                                                                              Controller#Action
                     root GET    /                                                                                        questions#index
         question_answers GET    /questions/:question_id/answers(.:format)                                                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
                questions GET    /questions(.:format)                                                                     questions#index
                          POST   /questions(.:format)                                                                     questions#create
             new_question GET    /questions/new(.:format)                                                                 questions#new
            edit_question GET    /questions/:id/edit(.:format)                                                            questions#edit
                 question GET    /questions/:id(.:format)                                                                 questions#show
                          PATCH  /questions/:id(.:format)                                                                 questions#update
                          PUT    /questions/:id(.:format)                                                                 questions#update
                          DELETE /questions/:id(.:format)                                                                 questions#destroy
       rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                      active_storage/disk#update
     rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                           active_storage/direct_uploads#create
```

```ruby:config/routes.rb
Rails.application.routes.draw do  
  root 'questions#index' #indexアクションのビューをrootに設定されるようする
  resources :questions do #questionsコントローラーに関係するルーティングを自動で用意してくれる
    resources :answers
  end
end
```

##model

![table.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/546712/167f59be-6781-107c-fc5a-13505a9caa4e.jpeg)


####Questionモデルの作成

```terminal:ターミナル
$ rails g model Question name:string title:string content:text
$ rails db:migrate
```

####Answerモデルの作成

```terminal:ターミナル
$ rails g model Answer question:references name:string content:text #question:references→1対多の関係を定義出来る
$ rails db:migrate
```

####データベース構造の確認

```terminal:ターミナル
$ rails dbconsole
SQLite version 3.7.17 2013-05-20 00:56:22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .schema #.schemaと入力
CREATE TABLE "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY);
CREATE TABLE "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
CREATE TABLE "questions" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "title" varchar, "content" text, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL);
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");
sqlite> .q　#.qと入力
```

```ruby:question.rb
class Question < ApplicationRecord
  has_many :answers, dependent: :destroy #questionは複数のanswerを持っているという意味
  #dependent: :destroy→親のquestionが削除されたら、それに紐づくanswersも全て削除される
  
  validates :name, presence: true #データが未入力の時に保存しないようにする
  validates :title, presence: true
  validates :content, presence: true
end
```

```ruby:answer.rb
class Answer < ApplicationRecord
  belongs_to :question #answerはquestionによって所有されていると言う関係

  validates :content, presence: true
  validates :name, presence: true
end
```

##controller

```terminal:ターミナル
$ rails g controller questions index show new edit #questionsコントローラーのindex,show,new,editアクションを作成
```

```ruby:question_controller.rb
class QuestionsController < ApplicationController

  #コントローラーの各アクションが実行される前にset_questionを実行する
  before_action :set_question, only: [:show, :edit, :update, :destroy]


  #一覧を表示する
  def index
    #Questionの全データの配列を取得して、@questionsインスタンス変数へ代入する
    @questions = Question.all
  end

  #詳細を表示する
  def show
    # @question = Question.find(params[:id])　#リファクタリング
    @answer = Answer.new #Answerクラスのインスタンスを作り、@answerインスタンス変数へ代入
  end

  #新規作成
  def new
    @question = Question.new #Questionモデルの空のインスタンスを作り、@questionインスタンス変数へ代入
  end
  
  #保存
  def create
    @question = Question.new(question_params) #name, title, contentを@questionに代入
    if @question.save #保存されたら
      redirect_to root_path, notice: 'Success!' #「Success!」と表示してルートパスにリダイレクトする
    else #保存できない場合は
      flash[:alert] = "Save error!" #「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) #name, title, contentをアップデートできたら
      redirect_to root_path, notice: 'Success!' #「Success!」と表示してルートパスにリダイレクトする
    else #アップデートできない場合は
      flash[:alert] = "Save error!" #「Save error!」と表示して
      render :edit #編集ページに戻る
    end
  end
  
  def destroy
    # @question = Question.find(params[:id]) #リファクタリング
    @question.destroy #削除する
    redirect_to root_path, notice: 'Success!' #「Success!」と表示してルートパスにリダイレクトする
  end
  
  private
  def set_question #@question = Question.find(params[:id])が重複しているので共通化する！
    @question = Question.find(params[:id])  #(params[:id])を取得して@questionに代入 
  end
  
  #ストロングパラメーター
  private #クラスの内部だけで使用する
  def question_params
    params.require(:question).permit(:name, :title, :content) #name,title,contentのみの値を@questionに渡す
  end
end

```

★paramsに入るデータをデバッグツールbyebugで確認する
formに値を入れて、ターミナルでparamsを入力するとフォームから送られてきた値を確認することができる
終了するには、quitを実行する

```ruby:question_controller.rb
private
def question_params
  byebug #byebugを入力
  params.require(:question).permit(:name, :title, :content) #paramsにはフォームから送られてきたデータが入る
end
```
```terminal:ターミナル
$ rails g controller answers edit  #answersコントローラーのeditアクションを作成
```

```ruby:answers_controller.rb
class AnswersController < ApplicationController

  #保存
  def create
    #question_idを元にデータベースから該当のquestionを取得して変数に代入する
    @question = Question.find(params[:question_id]) 

    #Answerモデルの空のインスタンスを作り、@answerインスタンス変数へ代入
    @answer = Answer.new 

    #content,name,question_idを保存できたら
    if @answer.update(answer_params) 

      #「Success!」と表示して詳細ページににリダイレクトする
      redirect_to question_path(@question), notice: 'Success!'

    #アップデートできない場合は
    else 
      #「Invalid!」と表示して詳細ページににリダイレクトする
      redirect_to question_path(@question), alert: 'Invalid!' 
    end
  end
  
  def edit
    #question_idを元にデータベースから該当のquestionを取得して変数に代入する
    @question = Question.find(params[:question_id]) 

    #取得した@questionから編集したいanswerのidを取得して@answerに代入する
    @answer = @question.answers.find(params[:id]) 
  end
  
  def update
    #question_idを元にデータベースから該当のquestionを取得して変数に代入する
    @question = Question.find(params[:question_id])

    #取得した@questionからアップデートしたいanswerのidを取得して@answerに代入する
    @answer = @question.answers.find(params[:id])

    #アップデートできたら
    if @answer.update(answer_params) 

      #「Success!」と表示して詳細ページににリダイレクトする
      redirect_to question_path(@question), notice: 'Success!'

    #アップデートできない場合は
    else
      #「Invalid!」と表示して
      flash[:alert] = 'Invalid!' 
      #編集ページに戻る
      render :edit 
    end
  end
  
  def destroy
    #question_idを元にデータベースから該当のquestionを取得して変数に代入する
    @question = Question.find(params[:question_id]) 
    #取得した@questionから編集したいanswerのidを取得して@answerに代入する
    @answer = @question.answers.find(params[:id])
    #削除する
    @answer.destroy 
    #「Deleted!」と表示して詳細ページににリダイレクトする
    redirect_to question_path(@question), notice: 'Deleted!' 
  end
  
  #ストロングパラメーター
  private
  def answer_params
    #content,name,question_idのみの値を@answerに渡す
    params.require(:answer).permit(:content, :name, :question_id) 
  end
end
```

##view

```erb:questions/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Qanda</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <div class="container">
      <% if flash[:notice] %>
        <p class="text-success"><%= flash[:notice] %></p>
      <% end %>
      <% if flash[:alert] %>
        <p class="text-danger"><%= flash[:alert] %></p>
      <% end %>
      <%= yield %>
    </div>
  </body>
</html>

```

#### 質問一覧ページの作成

```erb:questions/index.html.erb
<h2>Questions</h2>
<div class="row">
  <div class="col-md-12">
    <table class="table table-striped">
      <thead class="thead-light">
        <tr>
          <th>ID</th>
          <th>Title</th>
          <th>Menu</th>
        </tr>
      </thead>
      <tbody>
        <% @questions.each do |question| %>
          <tr>
            <td><%= question.id %></td>
　　　　　　　<!-- 質問詳細画面へのリンク -->
            <!-- prefixに_pathをつけている　-->
            <!-- (question)→:idとして読み込まれる -->
            <!-- question GET    /questions/:id(.:format)   questions#show -->
            <td><%= link_to question.title, question_path(question) %></td>

            <!-- edit_question GET    /questions/:id/edit(.:format)    questions#edit -->
            <td>[<%= link_to 'Edit', edit_question_path(question) %>]
            <!-- question DELETE /questions/:id(.:format)  questions#destroy -->
            [<%= link_to 'Delete', question_path(question), method: :delete, data:{ confirm: 'Are you sure?' }  %>]</td>
          </tr>
        <% end %>
      </tbody>
    </table>
    <div>
　　　 <!--  new_question GET  /questions/new(.:format) questions#new -->
      <%= link_to 'New question', new_question_path %>
    </div>
  </div>
</div>
```
####シードファイルを使った初期データの投入

```ruby:db/seeds.rb
Question.create(id: 1, name: 'Test name 1', title: 'Test question 1', content: 'Test content 1')
Question.create(id: 2, name: 'Test name 2', title: 'Test question 2', content: 'Test content 2')
Question.create(id: 3, name: 'Test name 3', title: 'Test question 3', content: 'Test content 3')
```

```
rails db:seed
```
#### 新規質問投稿ページの作成

```erb:questions/new.html.erb
<div>
  <div class="col-md-4 offset-md-4">
    <h2 class="text-center">New question</h2>
    <%= render 'form' %> <!--_form.html.erbを読み込む -->
  </div>
</div>
```

####質問編集ページの作成

```erb:questions/edit.html.erb
<div>
  <div class="col-md-4 offset-md-4">
    <h2 class="text-center">Edit question</h2>
    <%= render 'form' %> <!--_form.html.erbを読み込む -->
  </div>
</div>
```

####共通部分の作成
リファクタリング：プログラムを外部から見た時に動作を変えずに、ソースコードを整理すること
new.html.erbとedit.html.erbはほぼ同じ内容になっている
共通部分を_form.html.erbで作成し、new.html.erbとedit.html.erbで読み込む

```erb: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>
  
  <div class="text-center">
    <%= f.submit "Save", class: "btn btn-primary" %>
  </div>
    
<% end %>
```

####質問詳細ページの作成

```erb: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>
    
    <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_question_answer GET /questions/:question_id/answers/:id/edit(.:format)answers#edit -->
                [<%= link_to 'Edit', edit_question_answer_path(@question, answer) %>]

                <!-- question_answer DELETE /questions/:question_id/answers/:id(.:format) answers#destroy -->
                [<%= link_to 'Delete', question_answer_path(@question, answer), method: :delete, data: {confirm: 'Are you sure?'} %> ]
              </td>
            </tr>
            <% end %>
          </tbody>
        <% else %>
        <p>No answer yet.</p> <!-- 質問の答えがない場合に表示する -->
        <% end %>
      </table>
    </div>
    
    <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>
    
<% end %>
```

#### 回答編集ページの作成

```erb:answers/edit.html.erb
<div>
  <h2>Updates answer</h2>
  <%= form_with model:[@question, @answer], local: true do |f| %>
    <div class="form-group">
      <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>
    </div>
  <% end %>
</div>
```