【フリマアプリ】ancestryを用いたseedデーター作成について

   某スクールにおいて、チーム開発で、フリーマーケットアプリを作成中であり、使用した技術について公開しています。
※初学者のため、ミスや認識違いが多々あると思いますがご了承ください。

## railsのseed機能を用いて、カテゴリーをデーターベースにまとめて保存した。

　railsには、**「ancestry」**という非常に便利なgemがあります。
　ancestryの機能について、簡単に説明させていただきますと、ancestryとは日本語で**「先祖」**という意味で、その名の通り家系図のような繋がりを持つデーターベースを利用するときに真価を発揮します。
　※導入やモデル、マイグレーションの作成については割愛します。

　わかりやすく言うと、「波平」というデーターがあって、その配下には「サザエ」、「カツオ」、「ワカメ」とうデーターがあり、さらに「サザエ」の配下には、「タラオ」、 **（注：以下妄想です）**「マグ郎」「カクレクマノ美」という繋がりのデーターベースがあったとします。

　例えば、波平の子孫を全て引っ張ってくるといった記述を試みた際に、いその家と、いささか家ぐらいの小さな、データーベースであれば、親モデル、子モデル、孫モデルを作成し、多対多のアソシエーションを組んで、中間テーブルを作成して、所属側に外部キーを設定して、、、のように手作業でデーターベースを組んでいくのもありですが、数が多くなると結構大変です。

　しかし、ancestryを用いると一つのモデルで全て管理できます。先に取得方法を説明すると（モデル名はIsono、波平のidは１とする）

```
Isono.find(1).subtree
```
　これだけで、簡単に波平含む子供と孫が全て取得できます。他にも様々な取得方法があり、詳細はancestryの公式githubを参照ください。同じように初学者の方は、うげ、、公式、、、、と思った方もいるかもしれませんが
<img width="780" alt="99b66b40383f50ff79f2958f324ca36f.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/7c470d51-5816-d037-d2a8-2b61a00cd335.png">

のように図でも解説しており、英語が苦手な自分でも、なんとか理解できました！ちなみに、subtreeは右下の図ですね。青丸が自己を表します。

https://github.com/stefankroes/ancestry

https://qiita.com/Rubyist_SOTA/items/49383aa7f60c42141871
翻訳版
　
　さて、フリマアプリにどこに、使用するかというとカテゴリーは家系図ような繋がりをしています。「レディース」があり、そのレディースの配下には「上着」、「バッグ」、「靴」などの子カテゴリーがあり、さらにその配下にも孫カテゴリーが存在します。（靴カテゴリーの配下なら、「スニーカー」、「ハイヒール」、「パンプス」のような）
 どのようなカラクリで、このようなことが実現できているかというと

<img width="483" alt="212ad3a65d97c81d3cc7b51fcb0c1a8a.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/1cd93211-1076-ec81-b228-4b49f3477cc1.png">

　ancestryというカラムがあるのが、わかると思います。このように先祖を、ancestryカラムで管理することにより、血筋を結びつけています。どう言うことかといいますと**id:1からid:13**までは、**ancestryカラム**が**nil**になっているのがわかると思います。これは、そのレコードの**親がいない**、すなわち**id：1〜id:13**までのレコードは**全て親**だと言うことです。

　では、次に**id:13からid:32**までに目を向けると、ancestryカラムには、 **１** が格納されているのがわかります。これはすなわち、**親のidが１**である事を示しています。すなわち、**id:13からid:32までのレコードは全て親が「レディース」**であるということがわかると思います。

<img width="469" alt="9b6f47885e2b0f9be5776df2f6ab3fee.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/c6409f85-d030-f1de-b681-1f9399fedcc8.png">
　さらに孫カテゴリになると、分数のような表記になります。これは全然難しくなく、**/の左側が、祖父カテゴリー、/の右側が親カテゴリー**ということですね。**id:81の「Tシャツ・・・」**に注目するとancestryは **1 / 14** 　になっています。
これは、つまり**祖父カテゴリー**が**id:1の「レディース」**（祖父なのにレディースでややこしいですが、、、）、**親カテゴリーが id:14の「トップス」**であることを示しています。
　このように管理する事によって、血筋関係を一つのデーターベースで管理しているのですね。

　そこで、本題の**ancestryを用いてデーターを作成方法**を説明します。
　categoryというデーターベースに一つ一つ手作業で入力していく方法もあるんですが、膨大な量のためかなり、うんざりします。
　また、チーム開発初期では、データーベースがようわからん事になって、とりあえず**rails db:drop**で吹き飛ばそうとことが多々あったため、その度にまた入れ直していたら、それだけで開発期間は終わりそうです。

　そこで、seedファイルに予め、作成したいデーターを組んでおきます。**seedファイル**はdbファイルの配下にある**seed.rb**です。

<img width="312" alt="f7e54ec810819ca2c43455ffef3019f2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/d38090ce-c4f7-4eed-9dc8-e06f7adeb5dc.png">

ここに


```ruby:seeds.rb
User.create(id:1, first_name:"かつお",family_name:"いその")
```
のように記載しておき

ターミナルで、

```:ターミナル
rails db:seed
```
を実行すると、データーベースに保存されます。

ただし、**外部キー制約**に注意しないといけません。

例えば、**userモデル**に**itemモデル**が所属し、**user_id**を**外部キー**で設定していたとすると、

```seeds.rb
Item.create(id:1,name:"バット",user_id:1)
User.create(id:1, first_name:"かつお",family_name:"いその")
```
では、エラーになります。なぜかというとプログラムは上から実行されるため、Itemが作られようとした時、**まだid:1を持つuserが作成されていない**ため、参照先の**user_id:1**が見つからずバリデーションエラーとなり弾かれてしまいます。

この場合は、記載を上下逆にしなければいけません。

　さて本題にもどりancestryにおけるデーターの作成方法といいますと、今、**「レディース」レコード**が**lady**という変数に格納されていた場合

```
lady.children.create(name:"トップス") 
```

の記載で、**「レディース」の子供として「トップス」レコード**を作成することができます。当然トップスのancestryには **1** が格納されます。

よって、まずは

```ruby:seeds.rb
lady = Category.create(name:"レディース")
mens = Category.create(name:"メンズ")
baby = Category.create(name:"ベビー")
     #　（以下略）
```
みたいな感じで、親を作成し、

```ruby:seeds.rb
tops = lady.children.create(name:"トップス") 
jacket = lady.children.create(name:"ジャケット") 
     #　（以下略）
```
みたいな感じで、子を作成し

``````ruby:seeds.rb
 tshirt = tops.children.create(name:"Tシャツ")
 poroshirt =  tops.children.create(name:"ポロシャツ")
```
みたいな感じで、孫を作るといった感じで、延々と一個一個作っていく方法もあることはあります。

ただし、変数が多くなることや、可読性、データー追加時の記載の手間等を考えるとあまりよろしくないと思います。

そこで、名前データーは、配列として保管しておき、それをプログラム部に突っ込んで作成してしまおうと考えました。

## 実装方針
1. 親、子、孫の名前を配列でそれぞれ管理しておく。

2. それぞれをeach doで回して一括でデーターを作成する。

## コード
　結果以下のようなコードに辿りつきました。

```ruby:seeds.rb 
parents = ["レディース", "メンズ", "ベビー・キッズ", "インテリア・住まい・小物", "本・音楽・ゲーム",
            "おもちゃ・ホビー・グッズ", "コスメ・香水・美容", "家電・スマホ・カメラ", "スポーツ・レジャー",
            "ハンドメイド" ,"チケット" ,"自動車・オートバイ" ,"その他"]

children = [
            # レディースの子
            ["トップス","ジャケット","パンツ","スカート","ワンピース","靴","ルームウェア/パジャマ","レッグウェア",
              "帽子","バッグ","アクセサリー","ヘアアクセサリー","小物","時計","ウィッグ/エクステ","浴衣/水着",
              "スーツ/フォーマル/ドレス","マタニティ","その他"],
              # メンズの子
            ["トップス","ジャケット/アウター","パンツ","靴","バッグ","スーツ","帽子","アクセサリー","小物","時計","水着","レッグウェア","その他"],

            # （略）

          # ハンドメイドの子要素
            ["アクセサリー(女性用)","ファッション/小物"],
            # チケットの子要素
            ["音楽","スポーツ"],
            # 自動車・オートバイの子要素
            ["自転車本体","自転車タイヤ/ホイール"],
            # その他の子要素
            ["ペット用品","食品"]
          ]


grand_children = [
                  #レディースの孫
                     #トップス
                     ["Tシャツ/カットソー女(半袖/袖なし)","Tシャツ/カットソー(七分/長袖)","シャツ/ブラウス(半袖/袖なし)","シャツ/ブラウス(七分/長袖)","ポロシャツ","その他"],
                     #ジャケット
                     ["テーラードジャケット女","ノーカラージャケット","デニムジャケット","レザージャケット","ダウン 
                      ジャケット","ライダースジャケット","その他"],
                      #パンツ
                     ["デニム/ジーンズ","ショートパンツ","カジュアルパンツ","ハーフパンツ","その他"],
                     #スカート
                     ["ミニスカート","ひざ丈スカート","ロングスカート","キュロット","その他"],
     
                                    #(略)

                     #ウィッグ
                     ["前髪ウィッグ","ロングストレート","ロングカール","ショートストレート","その他"],
                     #浴衣
                     ["浴衣","着物","振袖","長襦袢/半襦袢","水着","その他"],
                     #スーツ
                     ["スカートスーツ","パンツスーツ","ドレス","パーティーバッグ","シューズ","その他"],
                     #マタニティ
                     ["トップス","アウター","インナー","ワンピース","授乳服","その他"],
                     #その他
                     ["コスプレ","下着","その他"],

                                    #(略)
                  # チケットの孫要素
                     ["男性アイドル","女性アイドル", "韓流","国内アーティスト", "海外アーティスト","その他"],
                     ["サッカー", "野球","テニス", "格闘技/プロレス", "相撲/武道","その他"],
                  # 自動車・オートバイの孫要素
                     ["国内自動車本体", "外国自動車本体","その他"],
                     ["タイヤ/ホイールセット", "タイヤ","ホイール","その他"],
                  # その他の孫要素
                     ["ペットフード", "犬用品", "猫用品","魚用品/水草", "デグー用品","その他"],
                     [ "菓子", "米", "野菜", "果物", "加工食品","その他"]
                   ]
```
少し、わかりにくいかもしれませんが、親は**一元配列**、子と孫は**二次元配列**となっています。
わかりやすいように簡略して書くと

```ruby
親配列　= ["A","B","C"]

子配列　= [ ["a1","a2","a3"],["b1","b2","b3"],["c1","c2","c3"] ]

孫配列　= [ 
           ["a1ア","a1イ","a1ウ"],["a2ア","a2イ","a2ウ"],["a3ア","a3イ","a3ウ"],
           ["b1ア","b1イ","b1ウ"],["b2ア","b2イ","b2ウ"],["b3ア","b3イ","b3ウ"],
           ["c1ア","c1イ","b1ウ"],["c2ア","c2イ","c2ウ"],["c3ア","c3イ","c3ウ"]
         ]
```
こんな感じですね。

続いて実際にデーターを作成するプログラム部です。

```ruby:seeds.rb
parents.each do |parent|
  Category.create(name: parent)
end

@parents = Category.where(ancestry: nil)

c = 0
@parents.each do |parent|
  children[c].each do |child|
    parent.children.create(name: child)
    end
  c += 1
end

g =0
@parents.each do |parent|# 親をひとつづつとる
  index = parent.id  #その親のidをindexに格納

  @children = Category.where(ancestry: index)# 一つの親に対する子供を全てとる

    @children.each do |child|   # 子供を回す
      grand_children[g].each do |grand_child|# 配列の中の配列を回す
        child.children.create(name: grand_child)# 子供の子供を作る
      end
    g += 1
    end
end
```
解説します。

```ruby
parents.each do |parent|
  Category.create(name: parent)
end

@parents = Category.where(ancestry: nil)
```
まずは、親をparents配列から名前をとってきて作成し、@parentsに格納します。

```ruby
c = 0
@parents.each do |parent|
  children[c].each do |child|
    parent.children.create(name: child)
    end
  c += 1
end
```
続いて二重eachを用いて、親に対する子供を作成します。
外側のeachで親をブロック変数|parant|に格納し、内側のeachで、その親に紐づく子供を作成します。初回はc=0　であるためchildren[c]にはchildren配列の0番目の要素、つまり

```ruby
#　レディースの子
 ["トップス","ジャケット","パンツ","スカート","ワンピース","靴","ルームウェア/パジャマ","レッグウェア",
  "帽子","バッグ","アクセサリー","ヘアアクセサリー","小物","時計","ウィッグ/エクステ","浴衣/水着",
  "スーツ/フォーマル/ドレス","マタニティ","その他"],
```
が格納されます。これをeachで回すことによって、レディースの子供が全て作成されます。
子供が全て作り終えて、内側のeachを抜けると、c=1となります。

外側のeachで、@parentsの次の親が、つまり「メンズ」がブロック変数の|parent|に格納されます。
そして、内側のeachで、さらにchildren[c]（c = 1のため、children配列の1番目の要素、つまり

```ruby
 # メンズの子
["トップス","ジャケット/アウター","パンツ","靴","バッグ","スーツ","帽子","アクセサリー","小物","時計","水着","レッグウェア","その他"],
```
が格納されますね。）これを内側のeachで回す。この作業を繰り返すことによって、それぞれの親に紐づく子供を作っちゃいます。

続いて孫制作です。

```ruby:seeds.rb
g =0
@parents.each do |parent|# 親をひとつづつとる
  index = parent.id  #その親のidをindexに格納

  @children = Category.where(ancestry: index)# 一つの親に対する子供を全てとる
```
発想は同じなんですが、今回はまず、**親に紐づく子供を外側のeach**で取得し、さらにそこから、子供作成と同じ方法で孫を作るということですね。外側のeachが増えただけで仕組み自体は同じです。

これを全て記載し、あとは、ターミナルで

```:ターミナル
rails db:seed
```

を実行することにより、（いい歳こいて独身の自分でも）一気に子孫が作れちゃうわけですね。

もっとDRYできそうな気がしますが、とりあえずはこれで行きたいと思います。

長々とありがとうございました。