我可以在不实例化相关模型的情况下从一对一关系中急切加载属性以进行委托吗?

Can I eager load attributes from one-to-one relationship for delegation without instantiating the related model?

想象一下:

class House < ActiveRecord::Base

  belongs_to :ground

  delegate :elevation_in_meters, to: :ground

  # attributes: stories, roof_type
end

class Ground < ActiveRecord::Base
  has_one :house

  # attributes: elevation_in_meters, geo_data
end

然后到 eager load ground 以便 house.elevation_in_meters 可以在不加载的情况下被调用 Ground 我可以这样做:

houses=House.includes(:ground).first(3)

这个问题是,整个 Ground 对象实际上是用所有属性实例化的,包括 geo_data 属性——在这种情况下我不需要它。我关心的原因是,查询需要非常高效,并且 geo_data 是一个非常大的文本字段。我只需要读取委托属性,而不是写入它们。

我可以采用什么方法来预先加载 Ground 中的 elevation_in_meters 属性而不加载 Ground 中的所有内容?

我在 rails 4.1 顺便说一句

注意:我希望 House 默认具有这种预加载行为,这样我就不需要每次都指定它。

首先为您想要部分获取的模型写一个范围,select您喜欢的字段。请注意,我使用了全名(table 名称)和 select 的字符串。我不确定您是否可以 select(:elevation_in_meters,:geo_data) 因为我已经从我们的生产示例中复制了它,并且我们使用了一些与此范围的连接,如果没有 table 名称将无法工作。自己试试吧。

class Ground < ActiveRecord::Base
  has_one :house

  attributes: elevation_in_meters, geo_data
  scope :reduced, -> {
     select('grounds.elevation_in_meters, grounds.geo_data')
  }

end

有了作用域,您可以创建第二个 belongs_to 关系(不要害怕它会弄乱您的第一个关系,因为 rails 关系基本上只是为您创建的方法),调用 Ground 模型上的范围。

class House < ActiveRecord::Base

  belongs_to :ground
  belongs_to :ground_reduced, ->(_o) { reduced },
    class_name: 'Ground', foreign_key: 'ground_id'

  delegate :elevation_in_meters, to: :ground_reduced 
  # go for an additional delegation if 
  # you also need this with the full object sometimes

end

最后你可以像这样调用你的查询:

houses = House.includes(:ground_reduced).first(3)

从技术上讲,这不是您问题的正确答案,因为 Ground 对象仍处于实例化状态。但是该实例只会包含您想要的数据,而其他字段将为零,因此它应该可以解决问题。

更新: 正如我刚刚看到的那样,您希望最好将此行为作为默认行为,只需为您的房屋添加一个范围:

scope :reduced, -> { includes(:ground_reduced) }

然后您可以将其添加为您的默认范围,因为您的原始关系不会受此影响。

我知道已经有一段时间了,但我偶然发现了这个。

如果您只对单个属性感兴趣,您也可以将 joinsselect 结合使用,该属性将神奇地添加到您的 House 实例中。

res = House.joins(:ground).select('houses.*, grounds.elevation_in_meters').first

res.elevation_in_meters # attribute is available on the object

要始终显示此属性,请将其设置为 Housedefault_scope,如下所示:

default_scope { joins(:ground).select('houses.*, grounds.elevation_in_meters') }

根据您要加入的表格的性质,您可能还需要 distinct