某スクールにおいて、チーム開発で、フリーマーケットアプリを作成中であり、使用した技術について公開しています。
※初学者のため、ミスや認識違いが多々あると思いますがご了承ください。
railsのseed機能を用いて、カテゴリーをデーターベースにまとめて保存した。
railsには、**「ancestry」という非常に便利なgemがあります。
ancestryの機能について、簡単に説明させていただきますと、ancestryとは日本語で「先祖」**という意味で、その名の通り家系図のような繋がりを持つデーターベースを利用するときに真価を発揮します。
※導入やモデル、マイグレーションの作成については割愛します。
わかりやすく言うと、「波平」というデーターがあって、その配下には「サザエ」、「カツオ」、「ワカメ」とうデーターがあり、さらに「サザエ」の配下には、「タラオ」、 (注:以下妄想です)「マグ郎」「カクレクマノ美」という繋がりのデーターベースがあったとします。
例えば、波平の子孫を全て引っ張ってくるといった記述を試みた際に、いその家と、いささか家ぐらいの小さな、データーベースであれば、親モデル、子モデル、孫モデルを作成し、多対多のアソシエーションを組んで、中間テーブルを作成して、所属側に外部キーを設定して、、、のように手作業でデーターベースを組んでいくのもありですが、数が多くなると結構大変です。
しかし、ancestryを用いると一つのモデルで全て管理できます。先に取得方法を説明すると(モデル名はIsono、波平のidは1とする)
Isono.find(1).subtree
これだけで、簡単に波平含む子供と孫が全て取得できます。他にも様々な取得方法があり、詳細はancestryの公式githubを参照ください。同じように初学者の方は、うげ、、公式、、、、と思った方もいるかもしれませんが
のように図でも解説しており、英語が苦手な自分でも、なんとか理解できました!ちなみに、subtreeは右下の図ですね。青丸が自己を表します。
https://qiita.com/Rubyist_SOTA/items/49383aa7f60c42141871
翻訳版
さて、フリマアプリにどこに、使用するかというとカテゴリーは家系図ような繋がりをしています。「レディース」があり、そのレディースの配下には「上着」、「バッグ」、「靴」などの子カテゴリーがあり、さらにその配下にも孫カテゴリーが存在します。(靴カテゴリーの配下なら、「スニーカー」、「ハイヒール」、「パンプス」のような)
どのようなカラクリで、このようなことが実現できているかというと
ancestryというカラムがあるのが、わかると思います。このように先祖を、ancestryカラムで管理することにより、血筋を結びつけています。どう言うことかといいますとid:1からid:13までは、ancestryカラムがnilになっているのがわかると思います。これは、そのレコードの親がいない、すなわちid:1〜id:13までのレコードは全て親だと言うことです。
では、次にid:13からid:32までに目を向けると、ancestryカラムには、 1 が格納されているのがわかります。これはすなわち、親のidが1である事を示しています。すなわち、**id:13からid:32までのレコードは全て親が「レディース」**であるということがわかると思います。
さらに孫カテゴリになると、分数のような表記になります。これは全然難しくなく、**/の左側が、祖父カテゴリー、/の右側が親カテゴリー**ということですね。**id:81の「Tシャツ・・・」**に注目するとancestryは **1 / 14** になっています。 これは、つまり**祖父カテゴリー**が**id:1の「レディース」**(祖父なのにレディースでややこしいですが、、、)、**親カテゴリーが id:14の「トップス」**であることを示しています。 このように管理する事によって、血筋関係を一つのデーターベースで管理しているのですね。 そこで、本題のancestryを用いてデーターを作成方法を説明します。
categoryというデーターベースに一つ一つ手作業で入力していく方法もあるんですが、膨大な量のためかなり、うんざりします。
また、チーム開発初期では、データーベースがようわからん事になって、とりあえずrails db:dropで吹き飛ばそうとことが多々あったため、その度にまた入れ直していたら、それだけで開発期間は終わりそうです。
そこで、seedファイルに予め、作成したいデーターを組んでおきます。seedファイルはdbファイルの配下にあるseed.rbです。
ここに
User.create(id:1, first_name:"かつお",family_name:"いその")
のように記載しておき
ターミナルで、
rails db:seed
を実行すると、データーベースに保存されます。
ただし、外部キー制約に注意しないといけません。
例えば、userモデルにitemモデルが所属し、user_idを外部キーで設定していたとすると、
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 が格納されます。
よって、まずは
lady = Category.create(name:"レディース")
mens = Category.create(name:"メンズ")
baby = Category.create(name:"ベビー")
# (以下略)
みたいな感じで、親を作成し、
tops = lady.children.create(name:"トップス")
jacket = lady.children.create(name:"ジャケット")
# (以下略)
みたいな感じで、子を作成し
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できそうな気がしますが、とりあえずはこれで行きたいと思います。
長々とありがとうございました。