Ruby on Railsでのアプリ開発（初心者）

#はじめに
ruby on railsでアプリ開発を進めるが、コントローラー作成以降のコマンドや設定などを備忘録として残す。
今回、プロジェクト名は「qanda」としている。
また、環境構築や初期設定はこちらの記事に記載している。
[・AWS Cloud9にRuby on Railsの環境構築する](https://qiita.com/Moriyoshi_05/items/a9b020453d04a702e141)
完全に個人の備忘録ようなので、見た目や言葉足らずがあるので、ご了承ください。

#コントローラーの作成
questionsというコントローラーを作って、保有するアクションはindex,show,new,editとする。

```bash
rails g controller questions index show new edit
```

#モデルの作成
Questionモデルを作る。データベースのカラム情報は記載の通り

```bash
rails g model question name:string title:string content:text
```

db>migrateフォルダのタイムスタンプファイルを確認して問題なければmigrateする。

```bash
rails db:migrate
```

反映されているか確認する。dbconsoleから抜ける方法は「.q」を打つ。

```bash
rails dbconsole
sqlite> .schema
```

###indexアクションを確認
railsを起動後、URLの語尾に/questions/indexをつけて確認

#ルーティングの設定確認
```bash
rails routes
```

なお、現在はこのようなルーティング設定になっている。

```rb:ルーティング初期
Rails.application.routes.draw do
  get 'questions/index'
  get 'questions/show'
  get 'questions/new'
  get 'questions/edit'
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
```

なお、下記のように書き直すと、一般的に必要なるルーティングは自動で設定する。

```ルーティング変更
Rails.application.routes.draw do
  resources :questions
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
```

これで、railsを起動後、URLの語尾に/questionsをつけると、
indexの内容が確認できる。

#rootメソッドの設定
URLの語尾に何もつけないでも、indexアクションに行けるようにしたい。
（rootのURLでindexアクションを動かす）

```rb
Rails.application.routes.draw do
  root 'questions#index'
  resources :questions
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
```

#質問一覧ページの作成
questions_controllerでindexアクションを修正する。

```rb
  def index
    @questions = Question.all
  end
```

viewでindex.html.erbを編集して、見た目を作る。
例えば、このように画面を作ってみる。（質問データがまだないのでデータ表示なし）

```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>
                        <td><%= question.title %></td>
                        <td>[Edit] [Delete]</td>
                    </tr>
                <% end %>
            </tbody>
        </table>
    </div>
</div>
```
![スクリーンショット 2021-01-30 2.42.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/aebae80e-06fb-8bc0-82b7-19a2fb536609.png)

###シードファイルを使った初期データの挿入
dbフォルダのseeds.rbを編集する。データを３つ作る準備をする。

```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')
```

その後、ターミナルでコマンドを実行

```bash
rails db:seed
```

railsを起動して、動作確認をすると無事データを確認できる。
![スクリーンショット 2021-01-30 2.51.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/b922137a-871c-28f9-3683-15e868aebde9.png)

###bootstrapの導入
Gemfileを使って導入する。下記をファイルに追加する。
なお、bootstrapバージョンは4.1.1以上で固定しておく。
また、jqueryは３系をインストールする。

```
gem 'bootstrap', '~> 4.1.1'
gem 'jquery-rails', '~> 4.3.1'
```

gemのインストールを実施する

```bash
bundle install
```

application.cssの拡張子をscssに変換する。

```bash
mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss
```

application.scssの中身を以下に変更する。

```scss
@import "bootstrap";
```

applivation.jsに以下を追加する。
（require_treeの手前）
参考：https://github.com/twbs/bootstrap-rubygem

```js
//= require jquery3
//= require popper
//= require bootstrap-sprockets
```

railsを起動するとテーブルの表示が変わる。
![スクリーンショット 2021-01-30 16.33.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/629a33fd-42c4-2209-de28-267d0d4eb1ee.png)

#各ページの共通で使われるHTML
views＞layouts＞application.html.erbがそれに当たる

#新規質問の作成
###viewの作成
new.html.erbを下記のように記述する。

```erb

<div>
    <div class="col-md-4 offset-md-4">
        <h2 class="text-center">New question</h2>
        <%= 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_field :content, class: "form-control" %>
            </div>
            
            <div class="text-center">
                <%= f.submit "Save", class: "btn btn-primary" %>
            </div>
        <% end %>
    </div>
</div>
```

またコントローラーでnewアクションを変更する。

```rb:questions_controller.rb
  def new
    @question = Question.new
  end
```

画面を確認すると、下記のようになった。
![スクリーンショット 2021-01-30 17.00.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/955833/55ab6bcd-7a52-0030-0b50-c61a7bdfc577.png)

###投稿データの保存
コントローラーにcreateアクションを追加する。
新規作成でsaveを押すと呼び出されるアクションのこと。

+ question_paramsメソッドの戻り値を利用
+ redirect_to root_pathでrootのURLへ遷移
+ notice: 'Success!'で、Success!のメッセージを表示
+ flash[:alert]でアラートメッセージの表示
+ render :newで、新規投稿ページにいたままになる

```rb:questions_controller.rb
 def create
    @question = Question.new(question_params)
    if @question.save
      redirect_to root_path, notice: 'Success!'
    else
      flash[:alert] = 'Save error!'
      render :new
  end
```

また、privateで以下も定義する。
question_paramsメソッドによって、ストロングパラメータという仕組みを使って、
フォームから送られてきた特定のデータのみを受け付ける。

```rb:
  private
    def question_params
      params.require(:question).permit(:name, :title, :content)
    end
```

###バリデートの追加
+ nameは必須とする。
+ titleは必須とする。
+ contentは必須とする。
+ エラーのときにメッセージを出す。

```rb：models>question.rb
class Question < ApplicationRecord
    validates :name, presence: true
    validates :title, presence: true
    validates :content, presence: true
end
```

以下で、noticeで正常系のメッセージ・alertで異常系のメッセージを表示する。

```rb:views>layouts>application.html.erb(body内部）
  <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>
```

###リンクの貼り付け
下記で新規投稿画面へのリンクを貼り付けれる
link_toはビューヘルパーで、第一引数が名称・第二引数はURLとなる。
また、URLはroutesのprefixから確認できる。

```erb
<div>
  <%= link_to 'New question', new_question_path %>
</div>
```

#質問詳細画面
###リンクの作成
こちらでeditにリンクを貼ることができる

```erb
[<%= link_to 'Edit', edit_question_path(question) %>]
```

###controllerの実装
URLでIDが渡されるので、それを使う。

```rb
  def edit
    @question = Question.find(params[:id])
  end
```

また、updateコントローラーを作成する。

```rb
  def update
    @question = Question.find(params[:id])
    if @question.update(question_params)
      redirect_to root_path, notice: 'Success!'
    else
      flash[:alert] = 'Save error!'
      render :new
    end      
  end
```

#質問削除機能の追加
index.html.erbにこちらを追加する

```erb
<td>[<%= link_to 'Edit', edit_question_path(question) %>] 
    [<%= link_to 'Delete', question_path(question), method: :delete, data:{ confirm: 'Are you sure?'}  %>]</td>
```

#質問詳細画面
index.html.erbにこちらを追加する

```erb
<td><%= link_to question.title, question_path(question) %></td>
```

#回答投稿機能
これらのような機能を追加してみる

+ ユーザが質問に対して、回答投稿できる
+ ユーザが回答一覧を閲覧できる
+ ユーザが回答を編集できる
+ ユーザが回答を削除できる

###showコントローラーの実装
```rb
  def show
    @question = Question.find(params[:id])
  end
```

##answersコントローラーを作成する
こちらのコマンドでコントローラーを作成

```bash
rails g controller answers edit
```

##answerモデルを作成する
questionsテーブルのidとanswersテーブルのquestion_idを紐づける（１対多）

```bash
rails g model answer question:references name:string content:text
```

また、questionモデルに下記を追加して、questionは複数のanswerを持っていることを示す。
dependent: : destroyでquestionが削除されたら紐づくanswersを全て削除することを示す。

```rb
has_many :answers, dependent: :destroy
```

次にこちらのコマンドを実行して、DBを作成する

```bash
rails db:migrate
```

##answer関連のルーティング設定
routes.rbの中身をこちらに書き換える

```rb
Rails.application.routes.draw do
  get 'answers/edit'
  root 'questions#index'
  resources :questions do
    resources :answers
  end
end
```

##Questionsコントローラーに回答投稿機能の連携
question_controller.erbを変更する。

```erb
  def show
    @question = Question.find(params[:id])
    @answer = Answer.new
  end
```

#回答投稿画面
show.html.erbを編集して、このように記載する

+ questionモデルに紐づくanswerモデルをformで送信する時は[@question, @answer]と記載
+ f.hidden_fieldでページには描画されない値を設定

```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>
```

###質問が投稿されたときの保存処理
answers_controller.rbを編集して、下記を追加する。

```rb
  def create
    @question = Question.find(params[:question_id])
    @answer = Answer.new
    if @answer.update(answer_params)
      redirect_to question_path(@question), notice: 'Success!'
    else
      redirect_to question_path(@question), alert: 'Invalid!'
    end
  end
```

###バリデーション
models>answer.rbに追加する。

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

###回答一覧の表示
show.html.erbに表示を追加する。
場所はh3タグの上に配置する。

```erb
        <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>
        </div>
```

###解答の編集機能
上記で追記したviewのコードで[Edit]となっている箇所を次に書き換える

```erb
[<%= link_to 'Edit', edit_question_answer_path(@question, answer) %>] 
```

answers_controller.ebを編集して、下記を追加する。

```rb
  def edit
    @question = Question.find(params[:question_id])
    @answer = Answer.answers.find(params[:id])
  end
```

また、answersのedit.html.erbを編集する

```erb
<div>
    <h2>Update 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>
```

###回答の編集
answers_controller.rbに追加する

```rb
  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
```

###回答の削除
answersのshow.html.erbに記載された[Delete]を下記のように修正する。

```erb
[<%= link_to 'Delete', question_answer_path(@question, answer),
method: :delete, data:{ confirm: 'Are you sure?'} %>]
```

また、answers_controller.rbに追加する

```rb
  def destroy
    @question = Question.find(params[:question_id])
    @answer = @question.answers.find(params[:id])  
    @answer.destroy
    redirect_to question_path(@question), notice: 'Deleted!'
  end
```

#さいごに
これで、ひととりのruby on railsをつかったWEBアプリができた。
次は公開に向けた手順を残していく