多階層カテゴリ作成でgem 'ancestry'を使用する際、seed.rbの記述量を大幅に減らす

# 概要
某フリマサイトの商品カテゴリーのクローンを作成するときにお役立てください。
多段階カテゴリを実装する際、gem 'ancestry'というものがあることを知り、実装してみようと思ったら、レコードを投入する'seed.rb'に大量の記述が必要になりました。
記述ミスを防ぐためにも自分なりに、なんとか記述量を減らしたいなぁと方法を考えてみました。
答えだけ知りたい方は本記事STEP3をコピペすればOKです。
[![Image from Gyazo](https://i.gyazo.com/6f948e52360ec9527376e33ac50f8d16.png)](https://gyazo.com/6f948e52360ec9527376e33ac50f8d16)

# gem 'ancestry'を使うための準備
こちらは[公式のGitHub](https://github.com/stefankroes/ancestry)を参考にしました。
## Gemfile
Gemfileに下記を記載

```gemfile
gem 'ancestry'
```
Categoriesテーブルにanccestryを適用させるため
ターミナルで下記コマンドを実行

```terminal
$ bundle install
$ rails g migration add_ancestry_to_category ancestry:string
```
マイグレーションファイルに下記を記載

```migration.rb
class AddAncestryToCategory < ActiveRecord::Migration[5.2]
  def change
    add_column :categories, :ancestry, :string
    add_index :categories, :ancestry
  end
end
```
仕上げにmigrateをしてください

```terminal
$ rake db:migrate
```

## Model

```category.rb
class Category < ApplicationRecord
　# 中略
　has_ancestry
end
```

# レコードを作成 （本記事の趣旨）
比較対象として
**1. 一般的な書き方**
**2. 今回の実装した書き方**
の2パターンを記載します

## レコードの階層想定
3階層まであると想定して

- (第1階層) カテゴリ 1
   - (第2階層) カテゴリ 1-1
      - (第3階層) カテゴリ 1-1-1
      - (第3階層) カテゴリ 1-1-2
      - (第3階層) カテゴリ 1-1-3
   - (第2階層) カテゴリ 1-2
      - (第3階層) カテゴリ 1-2-1
      - (第3階層) カテゴリ 1-2-2
- (第1階層) カテゴリ 2
   - (第2階層) カテゴリ 2-1
      - (第3階層) カテゴリ 2-1-1
      - (第3階層) カテゴリ 2-1-2
      - (第3階層) カテゴリ 2-1-3
   - (第2階層) カテゴリ 2-2
      - (第3階層) カテゴリ 2-2-1
      - (第3階層) カテゴリ 2-2-2

とします

## 1. 一般的な書き方

```seed.rb
category1 = Category.create(name:"カテゴリ 1")

category1_1 = category1.children.create(name:"カテゴリ 1-1")
category1_2 = category1.children.create(name:"カテゴリ 1-2")

category1_1.children.create([{name:"カテゴリ 1-1-1"},{name:"カテゴリ 1-1-2"},{name:"カテゴリ 1-1-3"}])
category1_2.children.create([{name:"カテゴリ 1-2-1"},{name:"カテゴリ 1-2-2"}])

#以後省略
```
想定ぐらいのレコードの数であれば、なんとかなりそうです。
しかし、今回のフリマアプリでは総レコードが1000以上。ムムム

## 2. 今回の実装した書き方
STEPを踏んで考えていきます。
### STEP1 ~第2階層のみを考える~
まずは、第1階層の"カテゴリ 1"の直下に第2階層を作る方法を考えていきます

```seed.rb

@category1 = Category.create(name:"カテゴリ 1")

category1s = ["カテゴリ 1-1","カテゴリ 1-2"]

category1s.each do |category-item|
 @category1.children.create(name:"#{category-item}")
end
```
配列を使って、作成してました。
お気づきの方もいらっしゃるかもしれませんが、
 category1ではなく**@category1**と第1階層を定義してます。
変数名に @ を付けるとスコープが広がり、その後のeachのブロック内でも@変数が使えます。
each内で@category1 = Category.create(name:"カテゴリ 1")を定義すればいいじゃんと思う方もいるかもしれませんが、のちのちメリットがありますので、今はこのまま進めます。
### STEP2 ~第3階層が増えた場合を対応させる~
流れとしては2階層を作って3階層を作ります。

```seed.rb

@category1 = Category.create(name:"カテゴリ 1")

category1s = [
              {level2:"カテゴリ 1-1",level2_children:["1-1-1","1-1-2","1-1-3"]},
              {level2:"カテゴリ 1-2",level2_children:["1-2-1","1-2-2","1-2-3"]}
             ]

category1s.each.with_index(1) do |category1,i|
  level2_var="@category1_#{i}"
  level2_val= @category1.children.create(name:"#{category1[:level2]}")
  eval("#{level2_var} = level2_val")
  category1[:level2_children].each do |level2_children_val|
    eval("#{level2_var}.children.create(name:level2_children_val)")
  end
end
```
一気にややこしくなりました。
説明します。

```seed.rb
#中略
category1s = [
              {level2:"カテゴリ 1-1",level2_children:["1-1-1","1-1-2","1-1-3"]},
              {level2:"カテゴリ 1-2",level2_children:["1-2-1","1-2-2","1-2-3"]}
             ]
#中略
```
まずは、配列category1sに第2階層と第3階層をハッシュ定義しました。

```seed.rb
#中略
category1s.each.with_index(1) do |category1,i|
  level2_var="@category1_#{i}"
  level2_val= @category1.children.create(name:"#{category1[:level2]}")
#中略
```
最初のeach.with_index部分を説明します。
第3階層を作成するために第2階層作成時に変数も作らねばなりません。
**変数**を'level2_var'とし、例によって、第3階層作成時にスコープを超える必要があるため**@**をつけました。
i=1のとき、level2_var = @category1_1
i=2のとき、level2_var = @category1_2
が作成されるわけです。
**値**を'level2_val'とし、第1階層作成時1の**@category1**の子供である**category1s**のそれぞれの**変数category1**のハッシュのキーである'level2'を取り出しました。
i=1のとき、level2_val = @category1.children.create(name:"カテゴリ 1-1")
i=2のとき、level2_val = @category1.children.create(name:"カテゴリ 1-2")
が作成されるわけです。

```seed.rb
#中略
  eval("#{level2_var} = level2_val")
#中略
```
evalメソッドを使っています。**今回の肝**です。
eval メソッドは、Ruby の組み込みカーネルメソッドで与えられた文字列をそのままRubyのコードとして解釈して実行します。
つまり、動的な変数**'level2_var'**,**'level2_val'**の**値**を文字列として認識しRubyのコードとして実行してます。こうすることで、右辺左辺の変数が動的でも実行できるわけですね。

```seed.rb
#中略
  category1[:level2_children].each do |level2_children_val|
    eval("#{level2_var}.children.create(name:level2_children_val)")
  end
end
```
最後です。
第1階層作成時の**@category1**の子供である**category1s**のそれぞれの**変数category1**のハッシュのキーである'level2_children'を取り出しました。
そして、第2階層作成時の**'level2_var'**の子供として取り出したキーの名前でレコードを作成してます。
ここはeach.with_indexを使う必要はないので
１番最初処理の時のとき、
 @category1_1.children.create(name:"カテゴリ 1-1-1")
2番最初処理の時のとき、
 @category1_1.children.create(name:"カテゴリ 1-1-2")
が作成されるわけです。
そして、第2階層作成にもどり、i=2となります。
その後またこの処理が動き、
 @category1_2.children.create(name:"カテゴリ 1-2-1")
 @category1_2.children.create(name:"カテゴリ 1-2-2")
が作成されます。
ここでもevalを使用して動的変数をRubyとして実行してます。
### STEP3 ~第1階層が複数ある場合を考える~

```seed.rb
categories=[
            {level1:"カテゴリ 1",level1_children:[
                                                {level2:"カテゴリ 1-1",level2_children:["カテゴリ 1-1-1","カテゴリ 1-1-2","カテゴリ 1-1-3"]},
                                                {level2:"カテゴリ 1-2",level2_children:["カテゴリ 1-2-1","カテゴリ 1-2-2"]}
                                               ]
            },
            {level1:"カテゴリ 2",level1_children:[
                                                {level2:"カテゴリ 2-1",level2_children:["カテゴリ 2-1-1","カテゴリ 2-1-2","カテゴリ 2-1-3"]},
                                                {level2:"カテゴリ 2-2",level2_children:["カテゴリ 2-2-1","カテゴリ 2-2-2"]}
                                               ]
            }
          ]

categories.each.with_index(1) do |category,i|
  level1_var="@category#{i}"                                                        #1階層の変数("@category1"等)
  level1_val= Category.create(name:"#{category[:level1]}")                          #1階層の値作成("カテゴリ　1"等)
  eval("#{level1_var} = level1_val")                                                #1階層の変数=1階層の値
    category[:level1_children].each.with_index(1) do |level1_child,j|
      level2_var="#{level1_var}_#{j}"                                               #2階層の変数("@category1-1"等)
      level2_val= eval("#{level1_var}.children.create(name:level1_child[:level2])") #2階層の値作成("カテゴリ　1-1"等)
      eval("#{level2_var} = level2_val")                                            #2階層の変数=2階層の値
        level1_child[:level2_children].each do |level2_children_val|
          eval("#{level2_var}.children.create(name:level2_children_val)")           #3階層の値作成("カテゴリ　1-1-1"等)
        end
    end
end
```
STEP2と考え方は同じです。
categoriesのバリューを変更すれば、応用できます。

# 最後に
ターミナルで仕上げをしてください！

```terminal
$ rake db:seed
```

##参考文献
[Rubyで連番の変数を動的に作成](https://easyramble.com/ruby-dynamic-variables.html)