TECH CAMP 7,8週目

TECH CAMPは7週目より最終課題に入っています。最終課題はメルカリのクローンフリマアプリのチーム開発。ちなみに私がチームのスクラムマスターで、5名で開発を推進してます。ひとつひとつのタスクの工数がきちんと把握できていない中での調整は難しいですが、今のところバランス良く進められているかなと感じています。
進め方としては、全員でデータベース設計をし、完了後にまず全てのページの表示に必要なルーティング、コントローラー、ビューを用意、そこからそれぞれのページのマークアップを全員に振り分けます（1〜2ページ/人）。その後、サーバーサイドを全員に振り分けます。これにより全員が様々なページ/機能に触れられるかなと考えたんですが、タスクによっては時間がかかるものがあり、そのメンバーはひとつの機能実装の深堀ばっかりになってしまい、そこは少し申し訳なかったなと思います。

　【担当した実装】
・商品購入確認ページのマークアップ
・商品詳細ページのマークアップ
・ウィザード形式を用いたユーザー新規登録、ログイン、ログアウト機能
・カテゴリ表示、選択機能

スクラムマスターは指示出しをしつつ、もちろん開発も行います。この中でも難しく感じたカテゴリ機能実装を以下に残しておきます。

**・カテゴリ表示、選択機能**
まずは商品詳細ページへの表示です。
<img width="624" alt="スクリーンショット 2020-05-30 18.10.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620703/85e026b1-8852-8ff8-c5d6-690671556b7f.png">
このように、3階層のカテゴリの表示を行います。例えば、”レディース”の中の”トップス”の中の”Tシャツ”のような感じです。データベース設計時に3つのテーブルが必要では？との案もありましたが、調べてみると、以下のgemを使用する事でひとつのテーブルで可能ということがわかりました。

```
gem 'ancestry'
```
bundle installし、has_ancestryとモデルに記述。

```app/models/category.rb
class Category < ApplicationRecord
  has_many :items
  has_ancestry
end
```
さて、データベースに大量のカテゴリ情報をどう入力するのか‥。ということで調べるとCSVファイルを読み込ませる方法が。まずはヘッダーに全カテゴリが入力済みだったので（メンバーよ、ありがとう！）これをスプレットシートにコピペし、関数でカテゴリ名のみ抜き出しました。そして下記ファイルにこのように記述。

```db/seeds.rb
require "csv"

CSV.foreach('db/category.csv', headers: true) do |row|
  Category.create(
    name: row['name'],
    ancestry: row['ancestry']
  )
  
end
```
dbファイル下にCSVファイルを移動し、ターミナルで$rake db:seedを行うとデータベースに読み込まれます。
<img width="299" alt="スクリーンショット 2020-05-30 18.26.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620703/2e453ad8-ce08-cbe7-e78a-7fd03b288e58.png">
こんな感じです。親カテゴリのancestryカラムはnullで、その子カテゴリのancestryカラムには親カテゴリのidが入ります。その孫カテゴリのancestryカラムには親カテゴリのid/子カテゴリのidが入ります。
そして表示させるためにコントローラーのshowアクションにこのように記述。こうする事でカテゴリテーブルの親、子、孫を呼び出せるそう（parentやchildで）。なんて便利なgemなんだ。

```app/controllers/items_controller.rb
def show
  @items = Item.find(params[:id])
  @grandchild = Category.find(@items.category_id)
  @child = @grandchild.parent
  @parent = @child.parent
end
```
そしてビューを編集。これで表示は完了です。

```app/views/items/show.html.haml
%th カテゴリー
  %td
    = @parent.name
    %br
    = @child.name
    %br
    = @grandchild.name
```

次は商品出品時のカテゴリ選択機能です。（苦手な）ajaxを使った動的な実装です。
こんな感じ。
![12657385b75fb9fce59088cb8dc78ceb.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620703/10124184-d10b-ac55-e002-ae21c88c8b48.gif)

まずはルーティングでアクション先を指定。

```config/routes.rb
resources :items do 
  collection do
     get 'category/get_category_children', to: 'items#get_category_children', defaults: { format: 'json' }
     get 'category/get_category_grandchildren', to: 'items#get_category_grandchildren', defaults: { format: 'json' }
   end
 end
```
コントローラーへ記述。

```app/controllers/items_controller.rb
def new 
  @category = Category.where(ancestry: "").limit(13)
end

def get_category_children  
  @category_children = Category.find(params[:parent_id]).children 
end
 
def get_category_grandchildren
  @category_grandchildren = Category.find(params[:child_id]).children
end
```
jbuilderファイルを作成。

```app/views/items/get_category_children.json.jbuilder
json.array! @category_children do |child|
  json.id child.id
  json.name child.name
end
```
```app/views/items/get_category_grandchildren.json.jbuilder
json.array! @category_grandchildren do |grandchild|
  json.id grandchild.id
  json.name grandchild.name
end
```
ビューを編集。

```app/views/items/new.html.haml
.status_register
  = form_with(model: @item, local: true) do |form|
    .status_register__status_category_group
      .status_register__status_category_group__category
        .status_register__status_category_group__category__register_title
          カテゴリー
        .status_register__status_category_group__category__choose
          = form.collection_select :category_id, @category, :id, :name,{prompt: '---'}, {id: 'parent_category'}
```
最後にjsファイルを作成します。親カテゴリ選択後に子カテゴリのセレクトボックスが出現、がなかなかうまくいかず時間がかかりました。コンソールで見るとイベント発火が確認出来ていたので、単純にhtmlのところかなと思いますが、かなりいじったのできちんとした原因がわからず‥。しまった‥。
他のメンバー＆自分のためにコメントアウト残してますが、そのまま貼ります。

```app/assets/javascripts/category.js
//この1行目の記述でリロード時に動作。カリキュラムでは削除していたturbolinks関連の記述を削除しないよう注意
$(document).on('turbolinks:load', function(){
  $(function(){
    //オプション設定
    function appendOption(category){
      var html = `<option value="${category.id}" data-category="${category.id}">${category.name}</option>`;
      return html;
    }
    //子カテゴリー表示（items/new.html.hamlのカテゴリー選択部分を編集した場合は要確認）
    function appendChidrenBox(insertHTML){
      var childSelectHtml = '';
      childSelectHtml = `<div class='status_register__status_category_groupl__category__choose__added' id= 'children_wrapper'>
                          <div class='status_register__status_category_group__category__choose1'>
                            <i class='fas fa-chevron-down status_register__status_category_group__category__choose--arrow-down'></i>
                            <select class="status_register__status_category_group__category__choose--select" id="child_category" name="item[category_id]">
                              <option value="---" data-category="---">---</option>
                              ${insertHTML}
                            <select>
                          </div>
                        </div>`;
      $('.status_register__status_category_group__category__choose').append(childSelectHtml);
    }
    //孫カテゴリー表示（items/new.html.hamlのカテゴリー選択部分を編集した場合は要確認）
    function appendGrandchidrenBox(insertHTML){
      var grandchildSelectHtml = '';
      grandchildSelectHtml = `<div class='status_register__status_category_group__category__choose__added' id= 'grandchildren_wrapper'>
                                <div class='status_register__status_category_group__category__choose2'>
                                  <i class='fas fa-chevron-down status_register__status_category_group__category__choose--arrow-down'></i>
                                  <select class="status_register__status_category_group__category__choose__box--select" id="grandchild_category" name="item[category_id]">
                                    <option value="---" data-category="---">---</option>
                                    ${insertHTML}
                                  </select>
                                </div>
                              </div>`;
      $('.status_register__status_category_group__category__choose').append(grandchildSelectHtml);
    }
    //親カテゴリー選択後イベント発火
    $('#parent_category').on('change', function(){
      //選択された親カテゴリーのidを取得
      var parent_category_id = document.getElementById
      ('parent_category').value;
      $.ajax({
        url: '/items/category/get_category_children',
        type: 'GET',
        data: { parent_id: parent_category_id },
        dataType: 'json'
      })
      .done(function(children){
        //親カテゴリが変更された時に子・孫カテゴリを削除する
        $('#children_wrapper').remove();
        $('#grandchildren_wrapper').remove();
        var insertHTML = '';
        children.forEach(function(child){
          insertHTML += appendOption(child);
        });
        appendChidrenBox(insertHTML);
      })
      //エラー警告
      .fail(function(){
        alert('再度カテゴリーを選択してください');
      })
    });
    //子カテゴリー選択後イベント発火
    $('.status_register__status_category_group__category').on('change','#child_category', function(){
      //選択された子カテゴリーのidを取得
      var child_category_id = $('#child_category option:selected').data('category');
      $.ajax({
        url: '/items/category/get_category_grandchildren',
        type: 'GET',
        data: { child_id: child_category_id },
        dataType: 'json'
      })
      .done(function(grandchildren){
        if (grandchildren.length != 0) {
          //子カテゴリが変更された時に孫カテゴリを削除する
          $('#grandchildren_wrapper').remove();
          var insertHTML = '';
          grandchildren.forEach(function(grandchild){
            insertHTML += appendOption(grandchild);
          });
          appendGrandchidrenBox(insertHTML);
      })
      //エラー警告
      .fail(function(){
        alert('再度カテゴリーを選択してください');
      })
    });
  });
});
```

これにて完了です。
参考記事があり大変助かりました。
データベース設計時にこのancestryを使用する方法に辿り着いて良かったです。みんなで検索して見つけたのかな？最初に親テーブル、子テーブル、孫テーブルがそれぞれ必要だ！と主張していたのは自分でしたが（笑）

# 参考
・[Rails5でjqueryを動かす方法](https://qiita.com/hiroyayamamo/items/b258acbaa089d9482c8ALLAN HOLDSWORTH)
・[多階層カテゴリでancestryを使ったら便利すぎた](https://qiita.com/Sotq_17/items/120256209993fb05ebac)
・[多階層セレクトボックスの実装](https://shura.design/2019/09/10/archives/690)
・[f.collection_selectについて](https://qiita.com/yokke0059/items/9508150c47b8ALLAN HOLDSWORTH130EL&P3df)
・[【Rails】rake seedコマンドでCSVファイルからDBに読み込ませる方法](https://qiita.com/kumasuke/items/545afaf5876d3dc52670)