【问题标题】:Specifying conditions on eager loaded associations returns ActiveRecord::RecordNotFound在急切加载的关联上指定条件返回 ActiveRecord::RecordNotFound
【发布时间】:2012-10-05 23:31:43
【问题描述】:

问题是当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)

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

【问题讨论】:

  • 这行得通吗?包括(:menu_items).where('menu_items.date' => Time.now.beginning_of_week..Time.now.end_of_week)
  • 不,完全相同的问题。这样我仍然需要以某种方式为每个menu_item 包含meal

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


【解决方案1】:

生成的 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 填充

现在看看 where 的第二部分:BETWEEN 运算符要求, 那 menu_items.date 不为空!和这个 是您过滤掉所有没有用餐的餐馆的地方。

所以我们需要更改查询以使空日期正常。

回到 ruby​​,你可以这样写:

  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')

没有餐点的餐馆留在里面。

【讨论】:

  • 你知道,这整件事是一个了解更多关于 Rails 的机会。我最初的问题涉及在我的餐厅表演动作中执行Restaurant.with_meals_of_the_week.find(params[:id]),但我最终发现这样做@restaurant = Restaurant.find(params[:id]) 然后@menu_items = @restaurant.menu_items.of_the_week 尽可能高效。但是,在索引操作中,您提供的查询类型将非常有用。你让我意识到我应该更多地关注生成的 SQL。谢谢你。
【解决方案2】:

正如 Rails 指南中所说,只有当您不将“where”子句与“includes”一起使用时,才会返回查询中的所有帖子,因为使用“where”子句会生成对带有 WHERE 的 DB 的 OUTER JOIN 请求外部表,因此 DB 不会返回任何内容。

当您需要一些对象(全部或其中一些对象 - 使用基础模型的 where)并且如果有相关模型只需获取所有对象时,这种实现非常有用,但如果没有 - 好的,只需获取基础模型列表.

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

所以在你的情况下,如果你仍然只想使用 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 将引发弃用警告

抱歉可能有错别字。

【讨论】:

  • 不,不是这样。我正在使用这样的方法:Restaurant.with_meals_of_the_week.find(params[:id]) 所以我正在寻找一家我知道存在的餐厅,包括符合该条件的餐点。问题是,当没有与之匹配的餐食时,则找不到记录。
【解决方案3】:

我觉得这里有些误解

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

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

我认为这个陈述没有提到示例Post.includes(:comments).where("comments.visible", true) 但参考一个没有where声明Post.includes(:comments)

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


所以...您写道:“如果在包含查询的情况下,没有任何帖子的 cmets,则仍会加载所有帖子。”好的!但这仅在没有 where 子句的情况下才是正确的!您错过了短语的上下文。

【讨论】:

  • 我明白你在说什么,但这并不是它所暗示的。您会注意到,在您引用的最后两句话中,includesjoins 有一个控制台字体,因此这意味着它们引用了 2 个方法/函数。而且,在我看来,这些句子意味着如果你使用includes,无论条件如何,父模型仍然会被加载,但如果你使用joins,那么条件必须匹配。
  • 不,这不是真的。句子暗示'如果没有where条件'和'没有任何帖子的cmets',所以Post.includes(:comments)将生成LEFT JOIN并返回所有帖子,但Posts.joins(:comments)将生成INNER JOIN并返回空集。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 2010-12-01
  • 1970-01-01
  • 2017-08-28
相关资源
最近更新 更多