質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Q&A

解決済

3回答

7136閲覧

Rails 無限階層カテゴリの実装

退会済みユーザー

退会済みユーザー

総合スコア0

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

0グッド

0クリップ

投稿2014/12/03 08:12

新規でアイテムを登録しました。

item tableのカラム名:category_idには
category table カラム名:category_id 13(パンツ)が入っている状態です。

item controller

def men

@category = Category.find_by(name: "メンズ")
@items = @category.items.paginate(page: params[:page])

end

上記の実行結果だと0件となります。
メンズの部分をパンツにすれば先ほど登録したものが表示されます。

Category model

has_many :items # アイテムを保持する。
has_many :categories # サブカテゴリーを保持する
belongs_to: category # 親カテゴリーを保持する

Item model

has_many :categories # 所属するカテゴリーを保持する

category table
+----+-----------+----------------------+
| id | parent_id | name |
+----+-----------+----------------------+
| 1 | | root |
| 2 | 1 | メンズ |
| 3 | 1 | レディース |
| 4 | 1 | コスメ |
| 5 | 1 | インテリア |
| 6 | 1 | 家電製品 |
| 7 | 1 | 本 |
| 8 | 1 | キッズ |
| 9 | 1 | スポーツ |
| 10 | 1 | エンターテインメント |
| 11 | 1 | おもちゃ |
| 12 | 2 | トップス |
| 13 | 2 | アウター |
| 14 | 2 | パンツ |
| 15 | 2 | 靴 |
| 16 | 2 | 帽子 |
| 17 | 3 | トップス |
| 18 | 3 | アウター |
| 19 | 3 | パンツ |
| 20 | 3 | 靴 |
| 21 | 3 | 帽子 |
+----+-----------+----------------------

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

すいません、コメントだとMarkdownが有効ではなく修正もできないようなので、こちらに解答します。


例示してもらったカテゴリだと2段(メンズ→パンツ)なので以下のコードで動作するはずです。
(注: paginateがDB上は役に立っていません)

lang

1@category = Category.find_by(name: "メンズ") 2items = [] 3items += @category.items 4@category.categories.each do |cat| 5 items += cat.items 6end 7@items = items.paginate(page: params[:page])

しかし、これを4段、例えば 衣服→メンズ→パンツ→ジーンズのように作ってしまった場合、

lang

1@category = Category.find_by(name: "衣服") 2items = [] 3items += @category.items # ここで衣服のアイテムを追加 4@category.categories.each do |cat_lv2| 5 items += cat_lv2.items # ここでメンズのアイテムを追加 6 cat_lv2.categories.each do |cat_lv3| 7 items += cat_lv3.items # ここでパンツのアイテムを追加 8 cat_lv3.categories.each do |cat_lv4| 9 items += cat_lv4.items # ここでジーンズのアイテムを追加 10 end 11 end 12end 13@items = items.paginate(page: params[:page])

のようになっていきます。

ですのでまず、ツリー状のカテゴリをフラット化することを考え、
その後にアイテムを取得することを提案しました。


以下ののように再帰的な関数呼び出しを使えば全子孫に対するアイテムを取得できます。
しかしDB的には効率が悪いので、効率の良いコードのためには先に例示したサイトなどを参考にうまいことやってください。

lang

1def index 2 @category = Category.find_by(name: "衣服") 3 descendant = [] 4 add_self_and_descendant(@category, descendant) 5 items = [] 6 descendant.each do |category| 7 items += category.items 8 end 9 @items = items.paginate(page: params[:page]) 10end 11 12# 13# 渡された category とその子孫を順に list に加えます 14# 15def add_self_and_descendant(category, list) 16 list << category 17 category.categories.each do |child| 18 self_and_descendant(child, list) 19 end 20end

投稿2014/11/27 14:26

hello-world

総合スコア1342

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2014/11/27 15:44

いただいたコードだと no such column: categories.category_idとなりました。
退会済みユーザー

退会済みユーザー

2014/11/27 16:26

スキーマは下記であっているでしょうか class CreateCategories < ActiveRecord::Migration def change create_table :categories do |t| t.integer :parent_id t.string :name t.timestamps end end end
退会済みユーザー

退会済みユーザー

2014/11/27 17:57

先ほどのエラーは治りました。 ご指摘の通り下記のエラーで止まっています。 undefined method `paginate' for #<Array:0x007fcab8021568>
hello-world

2014/11/28 14:38

失礼しました。おそらく will_paginate gem を利用しているのですね。 これは AR::Relationにしか対応していないため、仰るとおり動作しません。 代わりに kaminari gem を利用することを提案します。 このgemであれば、AR::Relation の他Arrayなどに対してもページネーションが行えます。 https://github.com/amatsuda/kaminari
退会済みユーザー

退会済みユーザー

2014/11/28 16:21

kaminari でためしたところ、下記のエラーがでます。 undefined method `page' for #<Array:0x007fefe0dc6518> @items = items.page(params[:page]).per(10).order("created_at DESC")
退会済みユーザー

退会済みユーザー

2014/11/29 06:34

ツリー状のカテゴリをフラット化するとは具体的に何をどうすればいいのでしょうか 先ほどのエラーは.page(params[:page]).per(10).order("created_at DESC")を削除ししたので直りました。
hello-world

2014/11/30 00:48

フラット化する関数 add_self_and_descendant を追記しました。
guest

0

ベストアンサー

次のページを参考にして、 ActiveRecord で ツリー構造を構築してみてください。

投稿2014/11/27 13:51

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2014/11/27 17:43

下記のように書きました。 class Category < ActiveRecord::Base has_many :items # アイテムを保持する。 has_many :categories # サブカテゴリーを保持する belongs_to :category # 親カテゴリーを保持する has_many :children, class_name: "Category", foreign_key: "parent_id" belongs_to :parent, class_name: "Category", foreign_key: "parent_id" end class CreateCategories < ActiveRecord::Migration def change create_table :categories do |t| t.integer :parent_id t.string :name t.timestamps end end end
退会済みユーザー

退会済みユーザー

2014/11/27 17:58

先ほどの投稿は間違いです。すいません 現在は下記の通りです。 class Category < ActiveRecord::Base has_many :items # アイテムを保持する。 has_many :categories, class_name: "Category", foreign_key: "parent_id"# サブカテゴリーを保持する belongs_to :category, class_name: "Category", foreign_key: "parent_id"# 親カテゴリーを保持する end class CreateCategories < ActiveRecord::Migration def change create_table :categories do |t| t.integer :parent_id t.string :name t.timestamps end end end
guest

0

こちらを参考に、カテゴリをツリー構造にしてみてください。
http://www.geocities.jp/mickindex/database/db_tree_ns.html

その後に、複数のカテゴリから複数のアイテムを取得するコードを書いてください。

lang

1root = Category.find_by(name: 'メンズ') 2categories = [root] + root.children # ここのコードは適当です。 3@items = categories.flat_map{ |c| c.items } # 単純なコードです。クエリのチューニングが必要かもしれません

投稿2014/11/27 12:24

hello-world

総合スコア1342

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2014/11/27 13:35

頂いたURLを参考にしてみます。ただ理解するのにかなり時間がかかると思いますが・・ 現在のカテゴリーテーブルでは親と子のリレーションができていないという認識でよろしいでしょうか?parent_idにcategory_idが入ることで親と子が入れ替わり何階層にも対応できると思ったのですが
hello-world

2014/11/27 14:18

いいえ、親子関係は出来ています。 現状のテーブルですと2段なので、 ```lang-ruby @category = Category.find_by(name: "メンズ") items = [] items += @category.items @category.categories.each do |cat| items += cat.items end @items = items.paginate(page: params[:page]) ``` のようにすれば(paginationがDB上は役に立ってないものの)動くはずです。 しかし、これが3段、例えば 衣服→メンズ→パンツのように作ってしまった場合、 ```lang-ruby @category = Category.find_by(name: "衣服") items = [] items += @category.items # ここで衣服のアイテムを追加 @category.categories.each do |cat_lv2| items += cat_lv2.items # ここでメンズのアイテムを追加 cat_lv2.categories.each do |cat_lv3| items += cat_lv3.items # ここでパンツのアイテムを追加 end end @items = items.paginate(page: params[:page]) ``` のようになっていきます。 ですのでまず、カテゴリをツリー状にすることを考え、 その後にアイテムを取得することを提案しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問