LoginSignup
2
3

More than 3 years have passed since last update.

商品出品画面を作る③ancestry(多階層カテゴリー)

Last updated at Posted at 2020-04-12

ancestryを使用して多階層のカテゴリーを作成する

image.png
こんな感じの入力欄を作ります。
商品出品画面を作る①へ

導入する

*先にカテゴリーテーブルを作成しておく必要があるので注意
Gemを導入する

Gemfile
gem 'ancestry'
ターミナル
bundle install
rails g migration add_ancestry_to_category
#カテゴリーテーブルにancestryを追加
rails db:migrate

モデルを編集する

models/item.rb
belongs_to :category
models/category.rb
has_many :items
has_ancestry

カテゴリーのデータを用意する

今回はseedファイルを使用します

db/seed.rb
# レディース
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カラムが追加され、各カテゴリー名が入っているハズ

スクリーンショット 2020-04-12 17.00.00.png

入力フォームを作る

haml

new.html.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

items_new.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. 1階層目のカテゴリーを選択
2. 選択されたカテゴリー情報をJSで所得し、ajaxでコントローラに送る
3. コントローラーでDBから1階層目のカテゴリーに紐付く2階層目のカテゴリーを全て取得し、jbuilderへ送る
4. jbuilderでデータをHTTP型からJSON型に変更し、配列にしてJSへ送る
5. JSのdone(function(category_children)で受け取り、処理をしていく
6. optionを保持するからの変数を用意
7. 受け取ったJSON型のデータの配列から1つずつ取り出し、JSの上で用意した
 var optionHtml = <option value="${category.id}">${category.name}</option>;
 でセレクトボックスで表示する為のオプションを作成
8. return optionHtml;で6で用意した変数に格納する
9. HTMLを作成してnew.html.hamlにはめ込む
10. 3階層目も同じ要領で

コントローラー

items.controller.rb
  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

category_children.json.jbuilder
json.array! @category_children do |child|
  json.id   child.id
  json.name child.name
end
category_grandchildren.json.jbuilder
json.array! @category_grandchildren do |grandchild|
  json.id   grandchild.id
  json.name grandchild.name
end

ポイント

seedファイルでの作成方法がわかれば大体できる。
セレクトボックスで表示する為には、optionタグを作成する
name属性が同じフォームは基本的に上書きされる

スクリーンショット 2020-04-12 18.02.06.png

2
3
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
2
3