我如何告诉 ActiveRecord 在 where 子句之后丢弃关联缓存

How can I tell ActiveRecord to discard an association cache AFTER a where clause

我正在尝试使用 where 子句刷新 ActiveRecord has_many 关联。我发现有两件事让我感到困惑:

  1. ActiveRecord 不使用缓存的关联,如果你在关联上调用 where
  2. 通过将 true 传递给关联方法来强制 ActiveRecord 查询数据库将在考虑 where 子句之前获取所有关联,导致许多对象低效且不必要地加载到内存中。

(1) 是预期的行为吗? [更新——参见下面 Frederick Cheung 的回答;每个调用都会产生一个新的关系,所以我相信答案是肯定的] 我在文档中找不到这个,所以我觉得依赖我的观察是不确定的。如果是,则 (2) 没有实际意义,因为 AR 无论如何都会每次查询数据库而不告诉它强制重新加载。但由于我不知道,所以我有后续问题,以防情况 (1) 无法保证:How can I force reload without immediate query execution?

为了说明(输出被抑制,因为它们不相关,只显示 AR 日志输出):

MediaFilehas_many :assets

缓存和强制刷新按预期工作:

m = MediaFile.delivered.offset(20).last
### log suppressed
1.9.3-p448 :078 > m.assets.map(&:id); nil
  MediaAsset Load (0.3ms)  SELECT `media_assets`.* FROM `media_assets` WHERE (`media_assets`.media_file_id = 533849)
1.9.3-p448 :079 > m.assets.map(&:id); nil
1.9.3-p448 :080 > m.assets(true).map(&:id); nil
  MediaAsset Load (0.3ms)  SELECT `media_assets`.* FROM `media_assets` WHERE (`media_assets`.media_file_id = 533849)

where 子句强制数据库提取:

1.9.3-p448 :081 > m.assets.where(type: "Source").map(&:id); nil
  MediaAsset Load (0.3ms)  SELECT `media_assets`.* FROM `media_assets` WHERE (`media_assets`.media_file_id = 533849) AND (`media_assets`.`type` = 'Source')
1.9.3-p448 :082 > m.assets.where(type: "Source").map(&:id); nil
  MediaAsset Load (0.3ms)  SELECT `media_assets`.* FROM `media_assets` WHERE (`media_assets`.media_file_id = 533849) AND (`media_assets`.`type` = 'Source')

将 true 传递给关联方法会强制立即加载:

1.9.3-p448 :083 > m.assets(true).where(type: "Source").map(&:id); nil
  MediaAsset Load (0.3ms)  SELECT `media_assets`.* FROM `media_assets` WHERE (`media_assets`.media_file_id = 533849)
  MediaAsset Load (0.2ms)  SELECT `media_assets`.* FROM `media_assets` WHERE (`media_assets`.media_file_id = 533849) AND (`media_assets`.`type` = 'Source')

请注意,最后一个示例中的第一个查询在没有 where 子句的情况下首先执行。这似乎不是最理想的。

首先,说 where 子句强制刷新是不正确的 - 对于活动记录,这两者只是独立的关系,一个被加载与另一个没有关联。如果你坚持这种关系,它只会被加载一次,例如

rel = m.assets.where(type: 'Source'); nil
rel.map(&:id)
rel.map(&:id)

只访问数据库一次。

您可以通过调用重置来清除关联缓存:

m.assets.reset

(但是由于这个 returns 关联,在控制台中这将触发重新加载,除非你附加 ;false 或类似的)