ARel 模仿包含 find_by_sql
Posted
技术标签:
【中文标题】ARel 模仿包含 find_by_sql【英文标题】:ARel mimic includes with find_by_sql 【发布时间】:2011-10-30 06:34:12 【问题描述】:我有一个 fairly complex sql query,我很确定我无法使用 ARel (Rails 3.0.10) 完成
查看链接,但它有一些连接和 where exists
子句,我很确定这对于 ARel 来说太复杂了。
然而,我的问题是,在这个查询如此复杂之前,我可以使用 Arel 使用 includes
添加其他我需要避免 n+1 问题的模型。现在我正在使用 find_by_sql,includes
不起作用。我仍然希望能够获取这些记录并将它们附加到我的模型实例,就像includes
所做的那样,但我不太确定如何实现这一点。
有人能指出我正确的方向吗?
我还没有尝试在同一个查询中加入它们。我只是不确定它们将如何映射到对象(即 ActiveRecord 是否可以正确地将它们映射到正确的类)
我知道在使用includes
ActiveRecord 时实际上会进行第二次查询,然后以某种方式将这些行附加到原始查询中的相应实例。有人可以指导我如何做到这一点吗?还是我需要加入同一个查询?
【问题讨论】:
【参考方案1】:我很确定您甚至可以使用 Arel 进行最复杂的查询。也许你对此持怀疑态度。
检查这些:
Rails 3: Arel for NOT EXISTS?
How to do "where exists" in Arel
【讨论】:
where exists
帖子的答案不是 where exists,而是 where in。但是另一个确实使用 exists,所以我会检查一下。谢谢!
仅供参考,我实际上已经使用第二篇文章来调整我的查询,所以我目前正在做一个where in
,所以我想我现在不需要回答我原来的问题。
实际上,我已经使用 Arel3 几个月了,在这个当前的项目中,我有一些 SQL 查询来执行几页长的事情。你不能在 ActiveRecord 中真正做到这些,因为 ActiveRecord 不支持 UNION(它仍然在某个补丁中)和“WITH”子句之类的东西。有一些解决方法——比如用 Arel 构建它,然后在上面调用“to_sql”,但是你会遇到这个海报问题——如何使用 .includes 来减少往返的疯狂。【参考方案2】:
@pedrorolo 感谢not exists
arel 查询的提醒,帮助我实现了我所需要的。这是最终的解决方案(它们的关键是 GroupChallenge 查询中的最终.exists
:
class GroupChallenge < ActiveRecord::Base
belongs_to :group
belongs_to :challenge
def self.challenges_for_contact(contact_id, group_id=nil)
group_challenges = GroupChallenge.arel_table
group_contacts = GroupContact.arel_table
challenges = Challenge.arel_table
groups = Group.arel_table
query = group_challenges.project(1).
join(group_contacts).on(group_contacts[:group_id].eq(group_challenges[:group_id])).
where(group_challenges[:challenge_id].eq(challenges[:id])).
where(group_challenges[:restrict_participants].eq(true)).
where(group_contacts[:contact_id].eq(contact_id))
query = query.join(groups).on(groups[:id].eq(group_challenges[:group_id])).where(groups[:id].eq(group_id)) if group_id
query
end
end
class Challenge < ActiveRecord::Base
def self.open_for_participant(contact_id, group_id = nil)
open.
joins("LEFT OUTER JOIN challenge_participants as cp ON challenges.id = cp.challenge_id AND cp.contact_id = #contact_id.to_i").
where(['cp.accepted != ? or cp.accepted IS NULL', false]).
where(GroupChallenge.challenges_for_contact(contact_id, group_id).exists.or(table[:open_to_all].eq(true)))
end
end
【讨论】:
open_for_participant 方法可以用 AREL 编写。使用includes
代替joins
并且where(and or) 可以写成arel_table[:attribute].eq(arel_table[:arel_attribute]).and().or 等等...
问题是在这种情况下我必须别名challenge_participants
,因为出于不同的原因我还包括了challenge_participants(此处未显示)。我知道我也可以在 ARel 中使用别名,但我可以使用别名 ARel 表调用 includes
吗?还是只需要符号?
我想你应该研究一下如何使用运算符“include”。这当然是可以做到的。
您可以使用运算符“别名”来获取某个表的别名。 ;)(在本页末尾:github.com/rails/arel)
看起来 Rails includes 方法不接受 ARel::Table
对象。我尝试了Model.includes(arel_table)
之类的方法,但得到了ActiveRecord::ConfigurationError:
我在ARel 中也看不到任何include
之类的方法。有什么想法吗?【参考方案3】:
让我们假设 SQL 真的不能简化为 Arel。并非一切都可以,我们碰巧真的很想保留我们的自定义 find_by_sql 但我们也想使用包含。
那么 preload_associations 就是你的朋友: (针对 Rails 3.1 更新)
class Person
def self.custom_query
friends_and_family = find_by_sql("SELECT * FROM people")
# Rails 3.0 and lower use this:
# preload_associations(friends_and_family, [:car, :kids])
# Rails 3.1 and higher use this:
ActiveRecord::Associations::Preloader.new(friends_and_family, [:car, :kids]).run
friends_and_family
end
end
请注意,3.1 方法要好得多,b/c 您可以随时应用急切加载。因此,您可以在控制器中获取对象,然后在渲染之前,您可以检查格式并预先加载更多关联。这就是我发生的事情 - html 不需要预先加载,但 .json 需要。
这有帮助吗?
【讨论】:
谢谢!我实际上也发现了 preload_associations ,这正是我所需要的!忘记在这里添加了。谢谢你提醒我。以上是关于ARel 模仿包含 find_by_sql的主要内容,如果未能解决你的问题,请参考以下文章