[Rails]gem 'ancestry'による多階層カテゴリーの導入[備忘録]


はじめに

初めて記事を投稿します。
某プログラミングスクールの課題でフリマアプリを作っていて、
gem 'ancestry'を導入してカテゴリー機能を実装したのでメモしておきます。
以下のようなものを目指します

なお、この記事ではデータベースにカテゴリーを入れて表示するところまでを記述します。

前提

Productモデル(カテゴリーを追加したいモデル)とCategoryモデルは実装してある。
大丈夫だとは思いますが、'Category'の複数形は'Categories'なので、
使うときは注意しましょう。(周りにそれが原因でエラーを出していた人がいたため。)

導入

初めて使うgemのときは公式ドキュメントをまず参照。
https://github.com/stefankroes/ancestry

gem 'ancestry'

bundle installを実行

カテゴリーテーブルにデータを入れる

さて、早速 rails db:seed を実行してデータを入れようと思ったところ、上手いかない・・・

原因

1.DB設計の際、先にancestryカラムを追加していたが、データ型がintegerになっていた。
 ancestryカラムはstring型です
2.null: falseが設定されており、下記のエラーが出た。

Mysql2::Error: Field 'ancestry' doesn't have a default value

解消

1.データ型を変更

rails g migration ChangeDatatypeAncestryOfCategories
def change
  change_column :categories, :ancestry, :string, index: true
end

2.null:false を外す

rails g migration ChangeColumnToNotNull
def up
  change_column :categories, :ancestry, :string, null: true
end

def down
  change_column :categories, :ancestry, :string, null: false
end

これでエラーが出ずに通るようになったので、
データを記述してテーブルに入力

db/seeds.rb
lady = Category.create(name: "レディース")
lady_1 = lady.children.create(name: "トップス")
lady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}])

#以下省略

rails db:seed を実行!

詳しくは公式ドキュメントに書いてありますが、入力されたCategoriesテーブルの中身を説明すると、
- 親要素(id: 1)のancestryは、nullが入る
- 子要素(id: 2)ののancestryは、親要素のid(1)が入る
- 孫要素(id: 3~20)のancestryは、親と子のidが入る(1/2)
これにより、多階層のカテゴリーを表現できる。

例えば、他のカテゴリーだとこんな感じ。

一つのテーブル、一つのカラムで実装出来るのは管理もしやすくて使い勝手がいいですね。

 ビュー

考え方を記載します。

products_controller.rb
def index
  # ancestry:nil つまり、親要素だけを取得
  @parents = Category.where(ancestry: nil)
end
_category.html.haml
- @parents.each do |parent|
  = parent.name
- parent.children.each do |child|
  = child.name
- child.children.each do |grandchild|
  = grandchild.name
#細かい部分は省略しています
#インデント等が正しくないですが、親に子を、子に孫を入れ子にしていくと上手く表示できます。
category.scss
// 親、子、孫に対して初期状態では
display: none;
// カテゴリーから探すにホバーされたときに、親要素を
display: block;
//親要素にホバーされたときには子要素、子要素にホバーされたときは孫要素に
display: block;
//ホバーされた要素自体に対しては、背景色や文字色を変更するとわかりやすくなります。

このように連続してタブを出現させるには、
CSSを使う方法とJavaScriptを使う方法があると思いますが、
ホバーされたときに出現したり消えたりするだけならSCSSにまとめて書いた方が、
他の人が見たときに分かりやすいと思いこのような実装をしました。

学んだこと

・必要なときに必要なテーブルを作り、必要なカラムを追加する。
・gemの使い方はまず公式ドキュメントを読む。

何かの参考になれば幸いです。
ここまで記事を読んでくださりありがとうございました。