Rails ActiveRecord使用INNER JOIN进行eager_load

3

在一个庞大的Rails应用程序中,我注意到我们有一段代码会生成一个很大的ActiveRecord::Relation。它在.joins()语句中使用自定义的SQL代码块,就像这样:

def foos
  Foo.
    joins("INNER JOIN bars ON foos.bar_id = bars.id").
    joins("INNER JOIN baz ON bars.baz_id = baz.id").
    where(<some condition on bars>)
end

(请注意,此示例中的JOIN比所显示的更复杂;否则我显然会只执行 Foo.joins(bar: :baz)。) 现在,在使用 ActiveRecord::Relation 的某些地方,这是可以的。 在其他情况下,我们希望将bars关联预加载到Foo结果集上。是否有任何方法可以做到这一点:
def scope_with_bars_eager_loaded
  foos.eager_load(:bars, using_existing_join_in_query: true)
end

我能想到的最接近的事情是:
def array_with_bars_eager_loaded
  foos.pluck(<fields we need>).map do |fields|
    bar = Bar.new(<get bar data from fields>)

    # This of course doesn't behave as well as a Foo
    # that we've loaded normally in regards to callbacks,
    # fields we didn't SELECT, etc. But it's probably
    # fine enough for this use-case (we're using this
    # data to render a page).
    Foo.new(<get foo data from fields>, bar: bar)
  end
end

这个问题更加复杂,而且也无法享受到作为ActiveRecord::Relation的好处。任何帮助都将不胜感激!

--

注:

特别需要避免 Rails 的默认行为“加载数据库中的每一列,有时一次查询中会多次加载”,任何建议都将不胜感激(这就是我使用.pluck而不是.select的原因,因为.select构造的查询即使你明确告诉它不要这样做,它仍会加载Foo中的所有内容)。例如:Foo.includes(:bar).where(bars: { condition: true }).select(:id) 会选择foos中的每一列,并选择foos.id两次。


这要看你的使用方式。如果你使用 Foo.new(hash_of_stuff_i_plucked_from_the_db),那么这条记录会像新记录一样运作,而不是从数据库中获取的记录。这会导致在回调和将其传递给表单时出现意外行为。 - max
代码joins(“INNER JOIN bars ON foos.bar_id = bars.id”)也很奇怪,因为.joins创建了一个LEFT INNER JOIN,你可以简单地将其写为.joins(:bars).joins.eager_load的主要区别在于.joins使用INNER.eager_load使用OUTER - max
此外,.select.pluck 的区别在于,select 返回一个 ActiveRecord::Relation 对象,而 .pluck 返回一个数组(由多个数组组成)。您可以使用 .select 来告诉 AR 确切要选择哪些列。例如,@foo = Foo.select('foos.id, foos.baz, bars.id, bars.baz').joins(:bars) 只会加载所选的列。 - max
感谢您的评论@max。我知道您提到的事情,并对我的问题进行了一些澄清。 - JacobEvelyn
1个回答

1

好的,我最终重构了我的foos方法,这样它就可以简单地执行includes。对于所有被SELECT的字段,我仍然不是很满意,但我想这就是使用ActiveRecord而不是像Sequel这样的东西得到的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接