在预先加载的关联上指定条件会返回ActiveRecord :: RecordNotFound

时间:2012-10-15 22:36:25

标签: ruby-on-rails ruby-on-rails-3.2

问题是当Restaurant没有任何符合条件的MenuItems时,ActiveRecord表示无法找到Restaurant。这是相关的代码:

class Restaurant < ActiveRecord::Base
  has_many :menu_items, dependent: :destroy
  has_many :meals, through: :menu_items

  def self.with_meals_of_the_week
    includes({menu_items: :meal}).where(:'menu_items.date' => Time.now.beginning_of_week..Time.now.end_of_week)
  end
end

生成的sql代码:

Restaurant Load (0.0ms)←[0m  ←[1mSELECT DISTINCT "restaurants".id FROM "restaurants"
LEFT OUTER JOIN "menu_items" ON "menu_items"."restaurant_id" = "restaurants"."id"
LEFT OUTER JOIN "meals" ON "meals"."id" = "menu_items"."meal_id" WHERE
"restaurants"."id" = ? AND ("menu_items"."date" BETWEEN '2012-10-14 23:00:00.000000'
AND '2012-10-21 22:59:59.999999') LIMIT 1←[0m  [["id", "1"]]

但是,根据Rails Guides的这一部分,这不应该发生:

Post.includes(:comments).where("comments.visible", true)

如果在此包含查询的情况下,任何帖子都没有评论,则仍会加载所有帖子。

3 个答案:

答案 0 :(得分:2)

生成的SQL是您查询的正确翻译。但看看它, 只是在SQL级别(我稍微缩短了一点):

SELECT *
FROM 
  "restaurants"
LEFT OUTER JOIN 
  "menu_items" ON "menu_items"."restaurant_id" = "restaurants"."id"
LEFT OUTER JOIN 
  "meals" ON "meals"."id" = "menu_items"."meal_id" 
WHERE 
  "restaurants"."id" = ? 
AND 
  ("menu_items"."date" BETWEEN '2012-10-14' AND '2012-10-21') 

左外连接执行您希望它们执行的工作:餐馆 与menu_items和饭菜相结合;如果没有menu_item 去餐厅,餐厅仍然保留在结果,与 所有缺失的部分(menu_items.id,menu_items.date,...)填入NULL

现在看看那里的第二部分:BETWEEN运营商要求, menu_items.date不为null!还有这个 是您在没有用餐的情况下过滤掉所有餐厅的地方。

因此我们需要以使null-date ok的方式更改查询。

回到红宝石,你可以写:

  def self.with_meals_of_the_week
    includes({menu_items: :meal})
       .where('menu_items.date is NULL or menu_items.date between ? and ?', 
            Time.now.beginning_of_week, 
            Time.now.end_of_week
        )
  end

生成的SQL现在是

.... WHERE (menu_items.date is NULL or menu_items.date between '2012-10-21' and '2012-10-28')

和没有用餐的餐馆留在。

答案 1 :(得分:0)

正如在Rails指南中所说的那样,只有当你不使用“where”子句和“includes”时才会返回查询中的所有帖子,因为使用“where”子句生成OUTER JOIN请求到带有WHERE的DB外表所以DB不会返回任何内容。

当你需要一些对象(全部或部分对象 - 使用基本模型的位置)并且如果有相关模型只获得所有对象时,这样的实现是非常有用的,但如果不是 - 那么只需获取基本模型列表

另一方面,如果您尝试在包含表格时使用条件,那么在大多数情况下,您只想选择具有此条件的对象,这意味着您只想选择具有meal_items的餐馆。

所以在你的情况下,如果你仍然只想使用2个查询(而不是N + 1),我可能会这样做:

class Restaurant < ActiveRecord::Base
  has_many :menu_items, dependent: :destroy
  has_many :meals, through: :menu_items

  cattr_accessor :meals_of_the_week

  def self.with_meals_of_the_week
    restaurants = Restaurant.all
    meals_of_the_week = {}
    MenuItems.includes(:meal).where(date: Time.now.beginning_of_week..Time.now.end_of_week, restaurant_id => restaurants).each do |menu_item|
      meals_of_the_week[menu_item.restaurant_id] = menu_item  
    end
    restaurants.each { |r| r.meals_of_the_week = meals_of_the_week[r.id] }

    restaurants
  end
end

更新:当您只是尝试在模型

上执行条件时,Rails 4将引发弃用警告

对不起可能的拼写错误。

答案 2 :(得分:0)

我认为对此有一些误解

  

如果没有where条件,这将生成两个查询的正常集合。

     

如果在此包含查询的情况下,则没有任何评论   帖子,所有帖子仍然会被加载。通过使用连接(INNER   JOIN),连接条件必须匹配,否则不会有记录   回。   [from guides]

我认为这些陈述并未引用示例Post.includes(:comments).where("comments.visible", true) 但请参阅没有where语句Post.includes(:comments)

的语句

所以一切正常!这是LEFT OUTER JOIN的工作方式。


所以...你写道:“如果在这包括查询的情况下,任何帖子都没有评论,那么所有的帖子都会被加载。”好!但只有当没有where条款时才会这样!你错过了这句话的背景。