2014-09-01 60 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?}