今回はタグ機能を実装するために、 Railsのgem"ancestry"を利用していきます。
これに関しては今回初めてなので、学習しながらメモしていきたいと思います
仕組みを理解する。
タグの階層とどうやって紐づけるか理解していきましょう!!
親コテゴリー - 中間カテゴリ-(親_子) - 子カテゴリー - 中間カテゴリー(子_孫) - 孫カテゴリー - 中間テービル(孫_商品) - 商品
gemがないと上記のような仕組みのため、非常にめんどくさいです。
商品 - 中間テーブル - カテゴリー
上記だけで完結します。だから非常に楽ですね
ではどうして、カテゴリーテーブル一つで3階層可能なのか?疑問になりますよね
一緒に学習しましょう!
タグの3階層の仕組み
3階層までタグが作成可能で、
一番上の親カテゴリは祖先がないので、
ancestryカラム: NULL
ID:14番目のレコードですが、ancestry: 1になってます。
祖先はID:1のレコードという意味ですね。つまりレディースが親カテゴリということです。
では、さらにトップスよりも下の階層(3階層目)はどうなのか?
つまり、祖先はID:1番目の レディースになるということです。ancestry: 1/14となっています。
つまり、ID:1の子である,ID:14番目の子という表示
1階層目: null
2階層目: 1階層目のID
3階層目: 1階層目のID/2階層目のID
3階層目のancestryカラムを取得すれば、2階層目、1階層目と辿れる仕組みです。3階層目までしか構造上できないようです(もしもできる場合は、コメントで教えていただければ幸いです。)4階層目を実装するには、別のテーブルが必要となります。
どうやって商品と紐づけるのか?
タグIDと商品IDを紐づける中間テーブルをおきます。
product_categoriesテーブル
Column | Type | Options |
---|---|---|
product_id | references | null: false |
category_id | references | null: false |
実際にDBを見て理解していきましょう!!
この例だと
productのID:1番目とcategory_id:7番目が紐づいてます。
categoryの7番目は、コスメ・香水・美容ですから、
商品は「コスメ・香水・美容」カテゴリに紐づいているとわかります。
このように、中間テーブルを利用して商品とカテゴリーを紐づけていきます。
商品 - 中間テーブル - カテゴリー
まとめ
1階層目: null
2階層目: 1階層目のID
3階層目: 1階層目のID/2階層目のID
商品 - 中間テーブル - カテゴリー
1. htmlより、親カテゴリを並べる
2. 親カテゴリを選択されたら、コントローラーで子カテゴリを取得、JS(ajax)で追加表示
3. 子カテゴリが選択されたら、コントローラーで孫kてゴリを取得、JS(ajax)で追加表示
親 > 子 > 孫
親 > 子: 親.children > 孫: 子.children #ここは後述します。
実装をしていく。
それでは実装していきましょう!!
インストール
gem 'ancestry'
を追加して
$ bundle instal
インストールをしたら、再起動させたいので、
$ rails s
モデルの作成
商品モデルはすでに作成してる前提で、作成方法を記述しません。
カテゴリーモデルを作成します。
$ rails g model category
モデルを作成したら、migrateファイルを記述していきましょう!!
categoriesテーブル
Column | Type | Options |
---|---|---|
name | string | null: false, index: true |
ancestry | string | index: true |
Association
- has_many :products
- has_ancestry
class CreateCategories < ActiveRecord::Migration[5.2]
def change
create_table :categories do |t|
t.string :name, index: true, null: false
t.string :ancestry, index: true
t.timestamps
end
end
end
$ rake db:migrate
中間テーブル(product_categories)を作成
$ rails g model product_category
product_categoriesテーブル
Column | Type | Options |
---|---|---|
product_id | references | null: false |
category_id | references | null: false |
アソシエーション
- belongs_to :product
- belongs_to :category
上記のテーブルになるようにmigrateファイルを記述していきます。
class CreateProductCategories < ActiveRecord::Migration[5.2]
def change
create_table :product_categories do |t|
t.references :product, null:false
t.references :category, null:false
t.timestamps
end
end
end
rake db:migrate
アソシエーション
カテゴリーモデルは下記になります
class Category < ApplicationRecord
has_ancestry
has_many :product_categories, dependent: :destroy
has_many :products, through: :product_categories
end
中間テーブルは下記になります。
class ProductCategory < ApplicationRecord
belongs_to :product
belongs_to :category
end
商品モデルは下記になります
class Product < ApplicationRecord
has_many :product_categories, dependent: :destroy
has_many :categories, through: :product_categories
end
はい!これでモデルは完了ですね。
では次に進みましょう!!!
DB → モデル → コントローラー
seeds.rbでカテゴリを生成
今回はモデル別にseedファイルを作成するやり方をします
[通常方法](https://www.sejuku.net/blog/28395)
require './db/seeds/category.rb'
カテゴリー用のseedファイルを作成しましょう
db > seedsのフォルダを作成 > category.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
コントローラーの作成
DB → モデル → コントローラーの処理で進むので、コントローラーを作成します。
1. htmlより、親カテゴリを並べる
2. 親カテゴリを選択されたら、コントローラーで子カテゴリを取得、JSで追加表示
3. 子カテゴリが選択されたら、コントローラーで孫kてゴリを取得、JSで追加表示
親 > 子 > 孫
この仕組みを動かすための独自メソッドを作成します。
class ProductsController < ApplicationController
def get_category_children
@children = Category.find(params[:parent_id]).children
end
def get_category_grandchildren
@grandchildren = Category.find("#{params[:child_id]}").children
end
end
と上記のアクションを作成します。
@children = Category.find(params[:parent_id]).children
上記の.childenって何?ってなると思いますが、
親カテゴリから子カテゴリを取得するためのメソッドです。
先ほど親>子>孫の順番で取得すると説明しましたが、.childrenを使うので下記のようになります。
親 > 子:親カテゴリ.chidren > 孫: 子カテゴリ.children
他にもいろんなメソッドが用意されているので、一度Githubを見ていただければと思います。
Github:ancestroy
Ajaxを導入する(JQuery)
route.rb
Ajaxに対応したルーティングにします。
resources :products do
collection do # 新規用(new) usr:products/newのため
get 'get_category_children', defaults: { format: 'json' }
get 'get_category_grandchildren', defaults: { format: 'json' }
end
member do # 編集(edit用) usl: products/id/editのため
get 'get_category_children', defaults: { format: 'json' }
get 'get_category_grandchildren', defaults: { format: 'json' }
end
end
Ajax対応のコントローラーにする。
先ほどコントローラーを作成していましたが、改良します
def get_category_children
respond_to do |format|
format.html
format.json do
@children = Category.find(params[:parent_id]).children
end
end
end
def get_category_grandchildren
respond_to do |format|
format.html
format.json do
@grandchildren = Category.find("#{params[:child_id]}").children
end
end
end
これでAjax対応のコントローラーになりました。
jbuilder作成
コントローラーとAjax用jsとで情報の架け橋となるjbuilderを作成
子カテゴリを追加するために、idと名前をjsに持っていきたい。
json.array! @grandchildren do |child|
json.id child.id
json.name child.name
end
json.array! @grandchildren do |grandchild|
json.id grandchild.id
json.name grandchild.name
end
Ajax用のjs
Ajaxで要素を追加するjsを記述します。
$(document).on('turbolinks:load', function(){
// カテゴリーの選択肢が入ったdiv
var categoryBox = $('.form-details__form-box__category')
// 親カテゴリー
function appendOption(category) {
var html = `<option value="${category.id}" data-category="${category.id}">${category.name}</option>`
}
// 子カテゴリー
function appendChildBox(insertHTML) {
var childSelectHtml = '';
childSelectHtml = `<div class='form-select' id="child-category">
<select class= 'select-default' name="product[category_ids][]">
<option value>---</option>
${insertHTML}
</select>
<i class='fa fa-angle-down icon-angle-down'></i>
</div>`
categoryBox.append(childSelectHtml);
}
// カテゴリーボックスで親カテゴリが変わった場合
categoryBox.on("change", "#parent-category", function(){
var parentCategory = $("parent-category").value;
if(parentCategory !== "") {
$.ajax ({
url: '/products/get_category_children',
type: "GET",
data: {
parent_id: parentCategory
},
dataType: 'json'
})
.done(function(children){
$('#child-category').remove();
$('#grandchild-category').remove();
var insertHTML = '';
children.forEach(function(grandchild){
insertHTML += appendOption(grandchild);
});
appendGrandchildrenBox(insertHTML);
})
.fail(function(){
alert('カテゴリー取得に失敗しました');
})
} else {
//親カテゴリーが初期値(---)の場合、子カテゴリー以下は非表示にする
//親カテゴリが未選択の場合、子、孫カテゴリの選択欄は非表示にしたいので、そのように変更
$('#child-category').remove();
$('#grandchild-category').remove();
$('#size').remove();
}
})
})
学習して作成中
参考になるサイト
Github:ancestroy
Railsでタグ機能をgemを使わずに実装した際のメモ
Railsのgem"ancestry"による多階層構造の実現