31
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

多階層カテゴリの検索ウインドウを作る

Last updated at Posted at 2019-08-10

##作るもの
Image from Gyazo

カテゴリ検索のウインドウを作ります。
適切な呼び方がよくわからなかったのですが、ベロベロリストと個人的に呼んでいました。

調べたらあんまり情報がなかったので、少しでもお役に立てば...

今回はカテゴリの作成方法(扱う方法)、ウインドウの作成方法を概観していこうと思います。
使用するgemの導入方などについては、リンク先の資料を参照してください。

バージョン情報です!

ruby 2.5.1
Rails 5.2.3

多階層のカテゴリ

今回作成するウインドウでは、3段階のカテゴリがあります。
説明しやすいように、親、子、孫という風に名前をつけます。

この分類を扱いやすくするためにancestryというgemを使用しました。

ancestryのgithub
参考にさせていただいた記事

多階層カテゴリの仕組みがどのようなものか、順を追って説明したいと思います。

###ただの分類リスト
まず、分類するときのグループ名に使えそうな名前のリストを用意しました。
ただ無秩序に並べてみたものです。

id name
1 食べ物
2 飲み物
3 牛肉
4 お肉
5 ソフトドリンク
お酒
7 ビール

これを3段階のカテゴリーにするとしたらどうでしょう。

食べもの > お肉 > 牛肉
飲み物 > ソフトドリンク > (何かソフトドリンクの種類)
飲み物 > お酒 > ビール

このような形に分類できるのではないでしょうか。
そしてこれを以下のように、、、

・parentsテーブル

id name
1 食べ物
2 飲み物

・childrenテーブル

id name parent_id
1 お肉
2 ソフトドリンク
3 お酒

・grand_childrenテーブル

id name child_id
1 牛肉
2 ビール

 
3つのテーブルに分けてアソシエーションを組んで管理するのは非常にめんどくさそうです。

##ancestoryの仕組み

id name ancestry
1 食べ物
2 飲み物
お肉 1
ソフトドリンク 2
お酒 2
6 牛肉 1/3
7 ビール 2/5

こんな風に、ancestryは親も子も孫も、一つのテーブルの中で管理します。
ancestryというカラムの中にある数字が自分の属するカテゴリを示しています。
3のお肉であれば1の食べ物のカテゴリーに属している、7のビールであれば2の飲み物カテゴリーと、それに属する5のお酒というカテゴリに属している、ということです。

seed.rb
lady = Category.create(name:"レディース")

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

こんな風に、ancestryを導入しますとchildrenという記述で子カテゴリを作ることができますし、さらに子のchildrenで孫も作り出すことができます。非常に便利です。
(Categoryというモデルを作成しました)

上記のseedを入れるだけで以下のようになります。

id name ancestry
1 レディース   NULL
2 トップス   1
3 Tシャツ/カットソー(半袖/袖なし) 1/2
4 Tシャツ/カットソー(七分/長袖) 1/2
5 シャツ/ブラウス(半袖/袖なし) 1/2
6 シャツ/ブラウス(七分/長袖) 1/2
7 ポロシャツ 1/2
8 キャミソール 1/2
9 タンクトップ 1/2
10 ホルターネック 1/2
11 ニット/セーター 1/2
12 チュニック 1/2
13 カーディガン/ボレロ 1/2
14 アンサンブル 1/2
15 ベスト/ジレ 1/2
16 パーカー 1/2

ファッションに疎い私が手作業で分類していたら発狂していたことでしょう。

##親カテゴリを持ってこよう
全てのseedを入れ終わり、親カテゴリを取得する場合には、ancestryのカラムがnillのところを持ってくれば良いですし、なんと、childrenで子要素を取得することができるようになります。

application_controller.rb
def set_parents
    @parents = Category.where(ancestry: nil)
end

このような形で親要素を取得することができます。よく使うので、application_controllerに記述しておきました。before_actionで呼び出して、カテゴリを使いたい時にすぐ使えるようにセットしちゃいましょう。

window.haml
.category_list
  .parents_list
    - @parents.each do |parent|
      = link_to "#{parent.name}", category_path(parent), class: "parent_category",id: "#{parent.id}"
  .children_list
  .grand_children_list

このような形でウインドウのhamlを用意しておきまして、子要素のリスト、孫要素のリストにjsでhtmlを足していこうという発想です。(sassは別途あてています)

##非同期通信
どの親カテゴリにの上にマウスがいるのか、それに属する子カテゴリや孫カテゴリを取得して、追加します。
コメントで解説をつけておきましたので、ご覧ください!

search.js
$(function() {
// 子カテゴリーを追加するための処理です。
  function buildChildHTML(child){
    var html =`<a class="child_category" id="${child.id}" 
                href="/category/${child.id}">${child.name}</a>`;
    return html;
  }

  $(".parent_category").on("mouseover", function() {
    var id = this.id//どのリンクにマウスが乗ってるのか取得します
    $(".now-selected-red").removeClass("now-selected-red")//赤色のcssのためです
    $('#' + id).addClass("now-selected-red");//赤色のcssのためです
    $(".child_category").remove();//一旦出ている子カテゴリ消します!
    $(".grand_child_category").remove();//孫、てめえもだ!
    $.ajax({
      type: 'GET',
      url: '/category/new',//とりあえずここでは、newアクションに飛ばしてます
      data: {parent_id: id},//どの親の要素かを送ります params[:parent_id]で送られる
      dataType: 'json'
    }).done(function(children) {
      children.forEach(function (child) {//帰ってきた子カテゴリー(配列)
        var html = buildChildHTML(child);//HTMLにして
        $(".children_list").append(html);//リストに追加します
      })
    });
  });

  // 孫カテゴリを追加する処理です 基本的に子要素と同じです!
  function buildGrandChildHTML(child){
    var html =`<a class="grand_child_category" id="${child.id}"
               href="/category/${child.id}">${child.name}</a>`;
    return html;
  }

  $(document).on("mouseover", ".child_category", function () {//子カテゴリーのリストは動的に追加されたHTMLのため
    var id = this.id
    $(".now-selected-gray").removeClass("now-selected-gray");//灰色のcssのため
    $('#' + id).addClass("now-selected-gray");//灰色のcssのため
    $.ajax({
      type: 'GET',
      url: '/category/new',
      data: {parent_id: id},
      dataType: 'json'
    }).done(function(children) {
      children.forEach(function (child) {
        var html = buildGrandChildHTML(child);
        $(".grand_children_list").append(html);
      })
      $(document).on("mouseover", ".child_category", function () {
        $(".grand_child_category").remove();
      });
    });
  });  
});
category_controller.rb
 def new
    @children = Category.find(params[:parent_id]).children
    respond_to do |format|
      format.html
      format.json
    end
  end

childrenで子カテゴリを持ってこられます。(もし子の子なら孫カテゴリ)

公式のREAD MEにはたくさんのメソッドが乗っています!
ancestryのgithub

new.json.jbuilder
json.array! @children do |child|
  json.id child.id
  json.name child.name
end

複数子、孫、要素が帰ってきますので、このように処理する必要があります。

##まとめ
思った以上に簡単にカテゴリ検索の中と外を作ることができました。
カテゴリーテーブルの中身さえ変えれば良いので、項目の追加も楽チンですね!
何かのお役に立っていれば幸いです。ありがとうございました!

31
33
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
31
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?