我可以让你接近 ARel。 注意:我的代码最终会在后台调用两个查询,我将对此进行解释。
最近,我不得不自己在 ARel 中解决 LEFT JOIN。玩 ARel 时最好的办法是启动 Rails 控制台或 IRB 会话并在 ARel 对象上运行 #to_sql 方法,看看它们代表什么类型的 SQL。尽早并经常这样做!
这是您的 SQL,为了保持一致性而稍作修改:
SELECT *
FROM records
LEFT OUTER JOIN other_records ON other_records.record_id = records.id
AND other_records.some_date > now()
WHERE other_records.something = 'another'
我假设您的 records 模型是 Record,other_records 是 OtherRecord。翻译成 ARel 和 ActiveRecord:
class Record < ActiveRecord::Base
# Named scope that LEFT JOINs OtherRecords with some_date in the future
def left_join_other_in_future
# Handy ARel aliases
records = Record.arel_table
other = OtherRecord.arel_table
# Join criteria
record_owns_other = other[:record_id].eq(records[:id])
other_in_future = other[:some_date].gt(Time.now)
# ARel's #join method lets you specify the join node type. Defaults to InnerJoin.
# The #join_sources method extracts the ARel join node. You can plug that node
# into ActiveRecord's #joins method. If you call #to_sql on the join node,
# you'll get 'LEFT OUTER JOIN other_records ...'
left_join_other = records.join(other, Arel::Nodes::OuterJoin).
on(record_owns_other.and(other_in_future)).
join_sources
# Pull it together back in regular ActiveRecord and eager-load OtherRecords.
joins(left_join_other).includes(:other_records)
end
end
# MEANWHILE...
# Elsewhere in your app
Record.left_join_other_in_future.where(other_records: {something: 'another'})
我将联接装瓶在一个命名范围内,因此您无需将所有 ARel 与您的应用程序逻辑混在一起。
我的 ARel 最终在后台调用了两个查询:第一个使用您的 JOIN 和 WHERE 条件获取记录,第二个使用所有记录的大列表获取所有 OtherRecords "WHERE other_records.record_id IN (...)"第一个查询的 ID。
Record.includes() 肯定会为您提供所需的 LEFT JOIN,但我不知道如何将您自己的标准注入到联接中。如果您想自己编写 SQL,可以使用 Record.joins() 而不是 ARel:
Record.joins('LEFT OUTER JOIN other_records' +
' ON other_records.record_id = records.id' +
' AND other_records.some_date > NOW()')
我真的非常喜欢让数据库适配器编写我的 SQL,所以我使用了 ARel。
如果是我,我会考虑将附加连接条件放在 WHERE 子句中。我假设您之所以问,是因为将附加条件放在连接上会使查询的 EXPLAIN 看起来更好,或者是因为当没有任何相关的 other_records 时,您不想处理 other_records.some_date 列中的 NULL。