2014-09-01 59 views
0

我有3个模型,类别,产品和项目。Rails关系问题(类别,产品和项目)

目标是获得所有具有产品(category.products.count> 0)的类别的列表,以及产品所属的项目未标记为专用的列表。

class Category < ActiveRecord::Base 
    has_many :products 
end 

class Product < ActiveRecord::Base 
    belongs_to :project 
    belongs_to :user 
    belongs_to :category 
end 

class Project < ActiveRecord::Base 
    belongs_to :user 
    has_many :products 
end 

class ApplicationController < ActionController::Base 
    protected 

    def set_categories 
     @categories = Category.joins(:products).where("products.is_product = true or  
         products.is_loving_this = true") 
     category = Category.find_by(title: "All") 
     @categories.unshift(category).uniq! 
    end 
end 

views/layouts/_title_bar.html.haml 
%nav.navbar-custom1.navbar.navbar-custom1.navbar-default.navbar-static-top{role: "navigation"} 
    .container 
     .col-xs-4 
      %ul.nav.navbar-nav 
      %ul.dropdown#dropdown_title_bar 
       %a.dropdown-toggle{"data-toggle" => "dropdown", href: "#", type: "button"} 
        Products 
        %b.caret 
      %ul.dropdown-menu{"aria-labelledby" => "dropdown-menu", role: "menu"} 
       - @categories.each do |c| 
        - if c.products.count > 0 || c.title == "All" 
         %li= link_to c.title, category_path(c) 

上面的代码给了我所有类别的下拉列表,其中category.products.count> 0,但我无法弄清楚如何只得到产品,该product.project.private ==假。

任何人都可以指导我如何添加? 我想过要做2个循环,但似乎很混乱。

- @categories.each do |c| 
    - if c.products.count > 0 || c.title == "All"   
     - c.products.each do |product| 
      - if product.project && product.project.private == false 
         %li= link_to c.title, category_path(c) 

最后美中不足的是,有时产品所不具备的一个项目,这就是为什么我添加的第一个if语句 - 如果product.project & & product.project.private ==假

先谢谢你们。

回答

2

我建议你拿出一些范围来帮助这个查询。

app/models/product.rb 
# BTW, can you come up with a better name for this? When is a product not a product? 
scope :is_product, -> { where(is_product: true)} 
scope :is_loving_this, -> { where(is_loving_this: true) } 
scope :is_public, -> { include(:project).where(projects: { private: false }) } 

然后你的查询可以是这个样子:

app/models/category.rb 
def self.active_categories 
    category_ids = Products.is_public.is_product.is_loving_this.map(&:category_id) 
    Category.find(category_ids)  
end 

注意,这是所有未经检验的,所以请写一些测试,以验证... :)

0

感谢杰森,

然而,使用你的建议工作,loving_this从不属于一个项目,所以我不能按照你的建议把所有的方法链接起来。

我必须将它们像这样分离:

def get_categories 
    category_ids = Product.is_public.product_type.map(&:category_id) 
    loving_this_ids = Product.loving_this_type.map(&:category_id) 
    @categories = Category.find(category_ids, loving_this_ids) 
end 

你认为这是确定?或者,还有更好的方法?

+1

小事,但你也可以用'pluck(:category_id)'替换'map(&:category_id)',这应该减少从数据库传输的数据量。你可以尝试的另一件事是(不知道是否会工作):'@categories = Category.scoped.merge(Product.is_public.product_type).merge(Product.loving_this_type)' – 2014-09-03 00:15:02

1

如果你想这样做在一个范围内,你可以用一个子查询做到这一点(Rails会就在2个独立的查询):

scope :wanted_categories, -> { Category.joins(:products).where(" 
        (products.is_product = true or  
        products.is_loving_this = true) and products.id in (?)", 
        Product.joins(:projects).where("projects.private 
        = false").pluck(:id) } 

的Rails首先运行子查询,并抓住产品ID列表谁的项目不是私有的,然后使用该列表为主查询填充SQL集(注意问号周围的圆括号)。

这种方法工程进展顺利时,你正在寻找那些IN一组值,但如果相反,您需要与NOT IN使用它,那么就会出现一个警告: 如果子查询不返回元素,轨道将填充sql设置为NULL,这意味着什么都不会匹​​配,products.id in (NULL)products.id not in (NULL)都不会返回任何内容。在这种情况下,您希望将子查询转换为范围,并使主范围成为条件并仅在子查询返回某些内容时才使其运行。 E.g:

class Product 
scope :of_public_project, -> { joins(:projects).where("projects.private = false") } 

class Category 
scope :wanted_categories, -> { joins(:products).where("(products.is_product = true 
        or products.is_loving_this = true) and products.id not in 
        (?)", Product.of_public_project.pluck(:id) if Product.of_public_project.any?}