ARel模仿包含find_by_sql

时间:2011-08-19 01:41:03

标签: sql ruby-on-rails-3 join arel

我有一个fairly complex sql query,我很确定我无法用ARel完成(Rails 3.0.10)

查看链接,但它有一些连接和一个where exists子句,而且我很确定ARel太复杂了。

我的问题是,在此查询过于复杂之前,使用ARel我可以使用includes添加我需要避免n + 1问题的其他模型。现在我正在使用find_by_sql,includes不起作用。我仍然希望能够获取这些记录并将它们附加到我的模型实例中,就像includes那样,但我不确定如何实现这一点。

有人能指出我正确的方向吗?

我还没有尝试在同一个查询中加入它们。我只是不确定它们将如何映射到对象(即,如果ActiveRecord将它们正确映射到正确的类)

我知道当使用includes ActiveRecord实际进行第二次查询时,会以某种方式将这些行附加到原始查询中的相应实例。有人可以告诉我如何做到这一点吗?或者我是否需要加入相同的查询?

3 个答案:

答案 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