#ancestryを使用して多階層のカテゴリーを作成する
こんな感じの入力欄を作ります。
商品出品画面を作る①へ
#導入する
*先にカテゴリーテーブルを作成しておく必要があるので注意
Gemを導入する
gem 'ancestry'
bundle install
rails g migration add_ancestry_to_category
#カテゴリーテーブルにancestryを追加
rails db:migrate
#モデルを編集する
belongs_to :category
has_many :items
has_ancestry
#カテゴリーのデータを用意する
今回はseedファイルを使用します
# レディース
ladies_child_array = ['トップス','ジャケット/アウター','パンツ','スカート','ワンピース','靴','ルームウェア/パジャマ','レッグウェア','帽子','バッグ','アクセサリー','ヘアアクセサリー','小物','時計']
#2階層目(子)のカテゴーを準備
ladies_grandchild_array = [
#3階層目(孫)のカテゴーを2重配列で準備
['Tシャツ/カットソー(半袖/袖なし)','Tシャツ/カットソー(七分/長袖)','シャツ/ブラウス(半袖/袖なし)','シャツ/ブラウス(七分/長袖)','ポロシャツ','キャミソール','タンクトップ','ホルターネック','ニット/セーター','チュニック','カーディガン/ボレロ','アンサンブル','ベスト/ジレ','パーカー'],
['テーラードジャケット','ノーカラージャケット','Gジャン/デニムジャケット','レザージャケット','ダウンジャケット','ライダースジャケット','ミリタリージャケット','ダウンベスト','ジャンパー/ブルゾン','ポンチョ','ロングコート','トレンチコート','ダッフルコート','ピーコート'],
['デニム/ジーンズ','ショートパンツ','カジュアルパンツ','ハーフパンツ','チノパン','ワークパンツ/カーゴパンツ','クロップドパンツ','サロペット/オーバーオール','オールインワン','サルエルパンツ','ガウチョパンツ','その他'],
['ミニスカート','ひざ丈スカート','ロングスカート','キュロット','その他'],
['ミニワンピース','ひざ丈ワンピース','ロングワンピース','その他'],
['ハイヒール/パンプス','ブーツ','サンダル','スニーカー','ミュール','モカシン','ローファー/革靴','フラットシューズ/バレエシューズ','長靴/レインシューズ','その他'],
['パジャマ','ルームウェア'],
['ソックス','スパッツ/レギンス','ストッキング/タイツ','レッグウォーマー','その他'],
['ニットキャップ/ビーニー','ハット','ハンチング/ベレー帽','キャップ','キャスケット','麦わら帽子','その他'],
['ハンドバッグ','トートバッグ','エコバッグ','リュック/バックパック','ボストンバッグ','スポーツバッグ','ショルダーバッグ','クラッチバッグ','ポーチ/バニティ','ボディバッグ/ウェストバッグ','マザーズバッグ','メッセンジャーバッグ','ビジネスバッグ','旅行用バッグ/キャリーバッグ'],
['ネックレス','ブレスレット','バングル/リストバンド','リング','ピアス(片耳用)','ピアス(両耳用)','イヤリング','アンクレット','ブローチ/コサージュ','チャーム','その他'],
['ヘアゴム/シュシュ','ヘアバンド/カチューシャ','ヘアピン','その他'],
['長財布','折り財布','コインケース/小銭入れ','名刺入れ/定期入れ','キーケース','キーホルダー','手袋/アームカバー','ハンカチ','ベルト','マフラー/ショール','ストール/スヌード','バンダナ/スカーフ','ネックウォーマー','サスペンダー'],
['腕時計(アナログ)','腕時計(デジタル)','ラバーベルト','レザーベルト','金属ベルト','その他']
]
parent = Category.create(name: 'レディース')
# 1階層目(親)のカテゴリーを作成
ladies_child_array.each_with_index do |child, i|
# 2階層目(子)のカテゴリー配列から1つずつ取り出す
child = parent.children.create(name: child)
# 取りだりた2階層目(子)のカテゴリーを作成
ladies_grandchild_array[i].each do |grandchild|
# 3階層目(孫)のi番目のカテゴリー配列から1つずつ取り出す
child.children.create(name: grandchild)
# 取りだした3階層目(孫)のカテゴリーを作成
end
end
###ポイント
ladies_grandchild_array = [[ ]]
の中に3階層目のカテゴリーを入れる
ここで、2重の配列にしておくのがポイント。
ladies_child_array.each_with_index do |child, i|
で2階層目(子)のカテゴリー配列から1つずつ取り出す時に『 i 』を付けて番号をつける。
ladies_grandchild_array[i]
とすることで、2重配列のladies_grandchild_arrayから『 i 』番目の配列を取り出す事ができる。
さらに、後ろに*.each do |grandchild|*を付けて
ladies_grandchild_array[i].each do |grandchild|
とする事で、取り出した『 i 』番目の配列から1つずつ中身を取り出す事ができる。
つまり2階層目(子)のカテゴリーに紐ずく、3階層目(孫)のカテゴリーを選別している。という事。
###seedファイル実行
rails db:seed
これで、カテゴリーテーブルにancestryカラムが追加され、各カテゴリー名が入っているハズ
#入力フォームを作る
###haml
.sell-container__content
%h3.sell-sub-head 商品の詳細
.sell-container__content__details
.sell-title
%h3.sell-title__text
カテゴリー
%span.sell-title__require
必須
.sell-collection_select
= f.label :category_id, {class: 'sell-collection_select__label'} do
= f.collection_select :category_id, @category_parent, :id, :name, {prompt: "選択して下さい"},{ class: 'sell-collection_select__input', id: 'category-select-parent', required: "required"}
%i.fas.fa-chevron-down
.error-messages#error-category
###JS
$(function(){
// カテゴリーセレクトボックスのオプションを作成
function categoryOption(category){
var optionHtml = `<option value="${category.id}">${category.name}</option>`;
return optionHtml;
}
// 親カテゴリー選択後のイベント
$('#category-select-parent').on('change', function(){
let parentCategoryId = $(this).val();
//選択された親カテゴリーのIDを取得
if (parentCategoryId == ''){
//親カテゴリーが空(初期値)の時
$('#select-children-box').remove();
$('#select-grandchildren-box').remove();
//子と孫を削除するする
}else{
$.ajax({
url: '/items/category_children',
type: 'GET',
data: { parent_id: parentCategoryId },
dataType: 'json'
})
.done(function(category_children){
$('#select-children-box').remove();
$('#select-grandchildren-box').remove();
//親が変更された時、子と孫を削除するする
let optionHtml = '';
category_children.forEach(function(child){
optionHtml += categoryOption(child);
//option要素を作成する
});
$('#error-category').before(`<div class="sell-collection_select " id="select-children-box">
<label class="sell-collection_select__label" for="item_category_id">
<select class="sell-collection_select__input" id="category-select-children" required="required" name="item[category_id]">
<option value="">選択して下さい</option>
${optionHtml}
</select>
<i class="fas fa-chevron-down"></i>
</label>
</div>`
);
})
.fail(function(){
alert('カテゴリー取得に失敗しました');
});
}
});
// 子カテゴリー選択後のイベント
$('.sell-container__content__details').on('change', '#category-select-children', function(){
let childrenCategoryId = $(this).val();
//選択された子カテゴリーのIDを取得
if (childrenCategoryId == ''){
//子カテゴリーが空(初期値)の時
$('#select-grandchildren-box').remove();
//孫以下を削除する
}else{
$.ajax({
url: '/items/category_grandchildren',
type: 'GET',
data: { child_id: childrenCategoryId },
dataType: 'json'
})
.done(function(category_grandchildren){
$('#select-grandchildren-box').remove();
//子が変更された時、孫を削除するする
let optionHtml = '';
category_grandchildren.forEach(function(grandchildren){
optionHtml += categoryOption(grandchildren);
//option要素を作成する
});
$('#error-category').before(`<div class="sell-collection_select " id="select-grandchildren-box">
<label class="sell-collection_select__label" for="item_category_id">
<select class="sell-collection_select__input" id="category-select-grandchildren" required="required" name="item[category_id]">
<option value="">選択して下さい</option>
${optionHtml}
</select>
<i class="fas fa-chevron-down"></i>
</label>
</div>`
);
})
.fail(function(){
alert('カテゴリー取得に失敗しました');
});
}
});
});
流れ
- 1階層目のカテゴリーを選択
- 選択されたカテゴリー情報をJSで所得し、ajaxでコントローラに送る
- コントローラーでDBから1階層目のカテゴリーに紐付く2階層目のカテゴリーを全て取得し、jbuilderへ送る
- jbuilderでデータをHTTP型からJSON型に変更し、配列にしてJSへ送る
- JSのdone(function(category_children)で受け取り、処理をしていく
- optionを保持するからの変数を用意
- 受け取ったJSON型のデータの配列から1つずつ取り出し、JSの上で用意した
var optionHtml =<option value="${category.id}">${category.name}</option>
;
でセレクトボックスで表示する為のオプションを作成 - return optionHtml;で6で用意した変数に格納する
- HTMLを作成してnew.html.hamlにはめ込む
- 3階層目も同じ要領で
コントローラー
def new
@item = Item.new
@category_parent = Category.where("ancestry is null")
end
# 親カテゴリーが選択された後に動くアクション
def category_children
@category_children = Category.find("#{params[:parent_id]}").children
#親カテゴリーに紐付く子カテゴリーを取得
end
# 子カテゴリーが選択された後に動くアクション
def category_grandchildren
@category_grandchildren = Category.find("#{params[:child_id]}").children
#子カテゴリーに紐付く孫カテゴリーの配列を取得
end
jbuilder
json.array! @category_children do |child|
json.id child.id
json.name child.name
end
json.array! @category_grandchildren do |grandchild|
json.id grandchild.id
json.name grandchild.name
end
#ポイント
seedファイルでの作成方法がわかれば大体できる。
セレクトボックスで表示する為には、optionタグを作成する
name属性が同じフォームは基本的に上書きされる