6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-04-24

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

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

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

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

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

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

Isono.find(1).subtree

 これだけで、簡単に波平含む子供と孫が全て取得できます。他にも様々な取得方法があり、詳細はancestryの公式githubを参照ください。同じように初学者の方は、うげ、、公式、、、、と思った方もいるかもしれませんが
99b66b40383f50ff79f2958f324ca36f.png

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

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

212ad3a65d97c81d3cc7b51fcb0c1a8a.png

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

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

9b6f47885e2b0f9be5776df2f6ab3fee.png  さらに孫カテゴリになると、分数のような表記になります。これは全然難しくなく、**/の左側が、祖父カテゴリー、/の右側が親カテゴリー**ということですね。**id:81の「Tシャツ・・・」**に注目するとancestryは **1 / 14**  になっています。 これは、つまり**祖父カテゴリー**が**id:1の「レディース」**(祖父なのにレディースでややこしいですが、、、)、**親カテゴリーが id:14の「トップス」**であることを示しています。  このように管理する事によって、血筋関係を一つのデーターベースで管理しているのですね。

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

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

f7e54ec810819ca2c43455ffef3019f2.png

ここに

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 が格納されます。

よって、まずは

seeds.rb
lady = Category.create(name:"レディース")
mens = Category.create(name:"メンズ")
baby = Category.create(name:"ベビー")
     # (以下略)

みたいな感じで、親を作成し、

seeds.rb
tops = lady.children.create(name:"トップス") 
jacket = lady.children.create(name:"ジャケット") 
     # (以下略)

みたいな感じで、子を作成し

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できそうな気がしますが、とりあえずはこれで行きたいと思います。

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?