我有一个fairly complex sql query,我很确定我无法用ARel完成(Rails 3.0.10)
查看链接,但它有一些连接和一个where exists
子句,而且我很确定ARel太复杂了。
我的问题是,在此查询过于复杂之前,使用ARel我可以使用includes
添加我需要避免n + 1问题的其他模型。现在我正在使用find_by_sql,includes
不起作用。我仍然希望能够获取这些记录并将它们附加到我的模型实例中,就像includes
那样,但我不确定如何实现这一点。
有人能指出我正确的方向吗?
我还没有尝试在同一个查询中加入它们。我只是不确定它们将如何映射到对象(即,如果ActiveRecord将它们正确映射到正确的类)
我知道当使用includes
ActiveRecord实际进行第二次查询时,会以某种方式将这些行附加到原始查询中的相应实例。有人可以告诉我如何做到这一点吗?或者我是否需要加入相同的查询?
答案 0 :(得分:22)
让我们假装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你可以随时应用eager-loading。因此,您可以获取控制器中的对象,然后在渲染之前,您可以检查格式并急切加载更多关联。这就是我发生的事情 - HTML不需要急切加载,但.json会这样做。
那有帮助吗?
答案 1 :(得分:1)
我很确定你甚至可以用Arel做最复杂的查询。也许你对此持怀疑态度。
检查这些:
答案 2 :(得分:1)
@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