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的主要内容,如果未能解决你的问题,请参考以下文章

我在哪里可以找到好的 AREL 文档? [关闭]

find_by_sql 并选择案例

Find_by_sql 作为 Rails 范围

指南 4.1 - 模仿者 [关闭]

试图制作一个模仿进程表的程序

模仿知乎首页代码