获取某个分类及其子分类的所有产品(Rails,awesome_nested_set)

8

我正在开发一款电子商务应用程序,我正在努力理解以下问题:

我通过awesome_nested_set插件实现了我的类别。如果我通过选择一个类别列出我的文章,一切都很好,但是对于某些链接,我想显示一个类别和其子类别的所有产品。

这里是控制器代码,仅适用于一个类别:

   # products_controller.rb
   def index
    if params[:category]
      @category = Category.find(params[:category])
      #@products = @category.product_list
      @products = @category.products
    else
      @category = false
      @products = Product.scoped
    end
    @products = @products.where("title like ?", "%" + params[:title] + "%") if params[:title]
    @products = @products.order("created_at).page(params[:page]).per( params[:per_page] ? params[:per_page] : 25)
    @categories = Category.all
  end

我注释掉的那行是我在类别模型中自己编写的帮助方法,它返回该类别及其子类别的所有产品的数组。

它的定义如下:

# app/models/category.rb
def product_list
  self_and_ancestors.to_a.collect! { |x| x.products }
end

现在,当我取消注释这行代码并尝试选择一个类别时,我的产品控制器代码就会出错,例如:
undefined method `order' for #<Array:0x1887c2c>

或者
undefined method `page' for #<Array:0x1887c2c>

因为我正在使用排序和分页,所以它无法再对数组进行排序。有什么办法可以在我的控制器中获取ActiveRecord Relation元素中的所有产品吗?谢谢。
更新:当我使用以下内容时:
class Category < ActiveRecord::Base
    acts_as_nested_set

    attr_accessible :name, :description, :lft, :rgt, :parent_id   

    has_many :categorizations
    has_many :products, :through => :categorizations

    attr_accessor :product_list

    def branch_ids
      self_and_descendants.map(&:id).uniq 
    end

    def all_products
       Product.find(:all, :conditions => { :category_id => branch_ids } )
    end


end

当我向控制器请求@category.all_products时,出现以下错误:
Mysql::Error: Unknown column 'products.category_id' in 'where clause': SELECT `products`.* FROM `products` WHERE `products`.`category_id` IN (6, 8, 9)

如何获取所有包含此星座的产品?
更新2:
好的,我要开始设置悬赏了。
如果我尝试:
def all_products Categorization.find(:all, :conditions => { :category_id => branch_ids } ) end
我会再次得到“undefined method 'order' for #”错误。
我需要知道如何将一个多对多关系中的所有产品作为ActiveRecord关系获取。
更新3:
我在gist中放置了相关代码https://gist.github.com/1211231
3个回答

5
使用awesome_nested_set的关键在于在lft列中使用范围。以下是我如何通过直接关联(category has_many articles)来实现它的代码示例。
  module Category
    extend ActiveSupport::Concern
    included do
      belongs_to :category
      scope :sorted, includes(:category).order("categories.lft, #{table_name}.position")
    end

    module ClassMethods
      def tree(category=nil) 
        return scoped unless category
        scoped.includes(:category).where([
          "categories.tree_id=1 AND categories.lft BETWEEN ? AND ?", 
          category.lft, category.rgt
        ])
      end
    end # ClassMethods
  end

然后在某个控制器中

@category = Category.find_by_name("fruits")
@articles = Article.tree(@category) 

这将会找到所有属于苹果、橙子、香蕉等类别的文章。你可以通过在分类上进行连接来适应这个想法(但你确定这里需要多对多关系吗?)

无论如何,我建议尝试以下方法:

class Product < AR
      has_many :categorizations

      def self.tree(category=nil) 
        return scoped unless category
        select("distinct(products.id), products.*").
        joins(:categorizations => :category).where([
          "categories.lft BETWEEN ? AND ?", category.lft, category.rgt
        ])
      end
end

如果有任何疑问,请告诉我。


这给我一个语法错误..."/Users/frankblizzard/Sites/rails_projekte/sotaca/app/models/product.rb:56: 语法错误,意外的 '}', 期望 tASSOC       scoped.joins(:categorizations => {:category}).where([" - tmaximini
等一下 - 我觉得现在它能工作了(移除了 :category 周围的花括号) - 现在会进行深入测试。 - tmaximini
目前看起来它可以工作了 - 唯一需要注意的是,如果某些文章有两个子类别关联,那么它们将会出现两次。我知道有一个方法叫做“uniq”,可以解决重复问题,但是“.order”和“paginate”将不起作用,因为它们会将ActiveRecord Relation转换为数组。你有什么想法如何处理这种情况? - tmaximini
啊!这就是多对多的问题。你试过在选择中注入distinct(products.id)吗?(请参见上面更新的内容)。 - charlysisto
快速提示:select、group、join(和其他操作)会禁用包含关系的急切加载...唯一的缺点是当你使用比标准关联更复杂的 SQL 时。 - charlysisto

3

有几种方法可以改进您的代码,但如果您正在寻找属于某个类别及其子类别(后代)的所有产品,则为什么不这样做:

def branch_ids
  self_and_descendants.map(&:id).uniq 
end

def all_products
  Product.find(:all, :conditions => { :category_id => branch_ids } )
end

我在这里做了一些假设,但是希望你能理解我的意思。


1
抱歉回复晚了,但我现在记得我尝试过类似的东西。问题是,在产品表中没有“category_id”列。我有一个has_many:通过“分类”关系,因为一个产品可以有多个类别。这就是为令这个查询无法工作的原因。我会更新问题。 - tmaximini
收集所有的categorization_ids而不是category_ids,然后执行与我上面描述类似的操作,或者编写一个命名作用域,在涉及的模型上执行连接... - socjopata
嘿-我不知道怎么让它工作,很抱歉。也许你可以编辑你的答案,使其能够正常工作,因为我现在无法理清头绪。我提供了赏金并更新了问题。谢谢。 - tmaximini
好奇问一下:Category.find(branch_ids).map(&:products) 返回的是什么?其中 branch_ids 当然包括所有分类树(当前+子类)... - socjopata
我将相关代码和控制台输出放在了 gist 中:https://gist.github.com/1211231 - tmaximini

0

扩展charlysisto的回答,当你在处理has_and_belongs_to_many分类时,很快就会发现粗暴的视角速度相当慢...你需要一些“中间件”来加速...

socjopata的回答提供了如何改善这种情况的好方法。所以,你可以使用定义

def branch_ids
  self_and_descendants.map(&:id).uniq 
end

在你的 category.rb 文件中(模型)。
然后,例如,在你的控制器中(带分页示例):
@category = Category.find... # whatever
branches  = @category.branch_ids
@prodcats = ProductCategory.select('distinct product_id')
                           .where('category_id IN (?)',branches)
                           .order(:product_id)
                           .paginate(page: params[:page], :per_page => 25)
ids       = @prodcats.map(&:product_id)
@products = Product.where('id IN (?)', ids)

最后,在您的视图中,您需要一点“技巧”才能进行分页。您将在@prodcats而不是@products上进行分页...
<%= will_paginate @prodcats %>
<% @products.each do |product| %>
    ....
<% end %>

这种方法在处理many_to_many时速度更快,虽然我必须承认它并不完全符合政治正确。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接