#背景
カリキュラムの過程でご存知メルカリのクローンサイトを作りました。
その際に、ヘッダーメニューにあるドロップダウンリスト(ハンバーガーメニュー?)を実装したのでその記録です。
css編を書きました。
こちらです。
【Rails5】ドロップダウンリストの実装(haml,scss編)〜メルカリクローンサイトの作成
#リストに呼び出したいDB構造など
今回、メルカリのサイトをクローンするにあたり、カテゴリーは、単一テーブル継承にすることで実装しています。
例えば、以下のような構造をしています。
id | category | parent_id |
---|---|---|
1 | レディース | |
2 | メンズ | |
13 | トップス | 1 |
14 | ジャケット | 2 |
115 | Tシャツ | 13 |
116 | ダウンジャケット | 14 |
この例だと、レディース>トップス>Tシャツ、 メンズ>ジャケット>ダウンジャケットという階層になります。
ちなみにビューで値を呼び出すときは、@category.childrenや@category.parentで子要素や親要素を呼び出すことが可能。
#ドロップダウンリストの要素が多すぎ
さて、実装に入りましょう。
色々な記事を参考にさせていただくと、ドロップダウンはCSSだけでいけることが判明。~~(後日別記事書きます)~~→こちら
じゃぁhtmlでカテゴリー名を全部入れればOKでしょ?
いや、待て待て。
親、子、孫合わせて
1,325!!
うん、多いね。
クラス名とかも一緒に書くから全部書くと4,000行の大作になっちゃうじゃない。
スマートじゃないね。
これ地道にやるならプログラミングやってる意味ないじゃん。
DBにデータはあるんだし、なんとかそこから引っ張ってこれないんか??
#リストの数が多いのでeachを使う
じゃぁ該当するカテゴリー名を変数に入れてrenderをeachさせればいいんじゃね?
まずは親カテゴリ。
親カテゴリはDBから呼び出さずにビューに記載。そして子カテゴリの呼び出し(@category_children1)
%ul.header__menu-box--left__parents
%li.header__menu-box--left__parents__parent
= link_to "レディース", category_path(id: 1)
%ul.header__menu-box--left__children
= render partial: "header_child", collection: @category_children1, as: "category"
続いて子カテゴリ。そして孫カテゴリーの呼び出し(category.children)
%li.header__menu-box--left__children__child
= link_to "#{category.category}", category_path(id: category.id)
%ul.header__menu-box--left__grand-children
= render partial: "header_grand-child", collection: category.children, as: "children"
さらに孫カテゴリ
%li.header__menu-box--left__grand-children__grand-child
= link_to "#{children.category}", category_path(id: children)
また、コントローラー側ではこのような処理がされています。
def set_categories
@categories = Category.where(params[:id])
@category_children1 = Category.where(parent_id: 1)
@category_children2 = Category.where(parent_id: 2)
@category_children3 = Category.where(parent_id: 3)
@category_children4 = Category.where(parent_id: 4)
@category_children5 = Category.where(parent_id: 5)
@category_children6 = Category.where(parent_id: 6)
@category_children7 = Category.where(parent_id: 7)
@category_children8 = Category.where(parent_id: 8)
@category_children9 = Category.where(parent_id: 9)
@category_children10 = Category.where(parent_id: 10)
@category_children11 = Category.where(parent_id: 11)
@category_children12 = Category.where(parent_id: 12)
@category_children13 = Category.where(parent_id: 13)
end
よし、早速動かしてみよう!!!
おやおや、、、
確かに動くんだけど、リンクをクリックしたらエラーが。。。
読み込むテンプレートないじゃん!って怒られている
部分テンプレートはちゃんとあるし、Missingな訳ないんだけどな
あれ、よーく見ると、ビューを読み込もうとしてるのってcategory_controllerじゃん
product_controllerじゃないのね。。。
そうか、部分テンプレートが同じcategoryフォルダにないから読めないのね。
次のように変更しましょ。
= render partial: "products/header_child"
こうすれば違うフォルダの部分テンプレートも読めるようになるね。
じゃ更新して、、、
あれれ? あれれ?
今度は子と孫のカテゴリーが出なくなったぞ。。。
#ページごとで読み込むコントローラーが違う
ページごとに読み込むコントローラーが違うため、それぞれのコントローラーで変数の設定が必要になるのか。。。
全部のコントローラーに上のあれを書くのは流石に可読性が悪くなるし、
DRYの法則に逆らうね
だるいわ〜
なんとか共通化したりできないの??
#共通のメソッドConsernsにしてしまう
ありました、ちゃんとそういう機能。
参考記事
Ruby on RailsでConcernsを利用してControllerの処理を共通化する
早速、以下のように書きます。
module CommonActions
extend ActiveSupport::Concern
def set_categories
@categories = Category.where(params[:id])
@category_children1 = Category.where(parent_id: 1)
@category_children2 = Category.where(parent_id: 2)
#中略
@category_children12 = Category.where(parent_id: 12)
@category_children13 = Category.where(parent_id: 13)
end
end
include CommonActions
before_action :set_categories, only: [必要なアクション]
#最初に読み込む必要があるため、最初に追記します。
でもこれ、コントローラー全部に書き込むのもどうかと思うのでこうしてみました。
class ApplicationController < ActionController::Base
include CommonActions
before_action :set_categories
#中略
end
こうすることで、全部のコントローラーに記述する必要がなくなりました!!
少しはいいの作れたかな〜
もっといい方法があればご教授くださいまし。