【问题标题】:Finding record where polymorphic association or nil查找多态关联或 nil 的记录
【发布时间】:2015-03-22 21:31:08
【问题描述】:

我有一个运行 ruby​​ 2.1.2 和 rails 4.1.1 的 rails 应用程序,其中我有一个像这样的多态关联:

class Picture < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
end

class Employee < ActiveRecord::Base
 has_many :pictures, as: :imageable
end

class Product < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

我希望能够找到属于给定员工或没有关联“可成像”记录的所有图片。

# These both work fine
Picture.where(imageable: Employee.first) # finds all associated with Employee.first
Picture.where(imageable: nil) # finds all un-associated Pictures

#This does not work
Picture.where(imageable: [Employee.first, nil])

一旦看到运行的 SQL,它不起作用的原因就很明显了

SELECT "pictures".* FROM "pictures"
WHERE "pictures"."imageable_type" = 'Employee' AND 
      (("pictures"."imageable_id" = 1 OR "pictures"."imageable_id" IS NULL))

当我需要时

SELECT "pictures".* FROM "pictures"
WHERE (("pictures"."imageable_type" = 'Employee' AND "pictures"."imageable_id" = 1)) OR 
      "pictures"."imageable_id" IS NULL

知道如何在不写出 SQL 的情况下在 rails 中运行查询吗?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 where polymorphic-associations null


    【解决方案1】:

    试试这个:

    from_employee = Picture.where(imageable: Employee.first).where_values.reduce(:and)
    from_nil = Picture.where(imageable: nil).where_values.reduce(:and)
    
    @from_employee_or_nil = Picture.where(from_employee.or(from_nil))
    

    编辑

    在 Rails 4.1 上,where_values 动态定义为 query_methods。它映射了与当前ActiveRecord::Relation 相关的所有条件的Arel::Nodes“条件”。该节点响应and(other),使用reduce(:and),您可以将它们全部放在一起。

    在 Rails 4.2 上这不起作用。你可以让它工作,但它变得很奇怪(见:OR operator in WHERE clause with Arel in Rails 4.2-问题的编辑-)。我们能做什么?

    使用普通查询(1):

    @employee = Employee.first
    Picture.where("(pictures.imageable_type = ? AND pictures.imageable_id = ?) OR pictures.imageable_id IS NULL", @employee.class, @employee.id)
    

    Hack with Arel(2):(正如@cristian 所说)

    @employee = Employee.first
    Picture.where( ( Picture.arel_table[:imageable_type].eq(@employee.class).and( Picture.arel_table[:imageable_id].eq(@employee.id) )).or(Picture.arel_table[:imageable_id].eq(nil) ))
    

    使用复杂的 where_values(3):

    employee_scope = Picture.where(imageable: Employee.first)
    nil_scope = Picture.where(imageable: nil)
    
    Picture.where( employee_scope.where_values.reduce(:and).or(nil_scope.where_values.reduce(:and)).to_sql, employee_scope.bind_values.map(&:last) + nil_scope.bind_values.map(&:last) )
    

    我的选择:对于这种情况,普通查询 (1)。我的第一个答案(我一直在使用),在 4.2 停止工作,需要重构(3)。 Arel 让您独立于数据库管理器(我建议学习 Arel)。而你发布的答案给了我一些奇怪的感觉,因为它工作正常但它使用了一个无效的条件:imageable_id IS NULL AND imageable_type = 'Employee' 理论上是由搜索返回的。

    【讨论】:

    • 谢谢。我会试试这个。您介意解释(或链接到更多信息)这是如何工作的吗?
    • 谢谢。如果我想保持简单,那么普通查询似乎是我最好的选择。即使这就是我所说的,我也不想在问题本身中使用。 Arel 似乎是我需要了解更多的东西。所以我可能只是为了练习......我会稍等一下,看看其他人怎么说。
    【解决方案2】:

    为了获得您正在寻找的确切查询,请使用 AREL:

    employee_condition = Picture.arel_table[:imageable_id]
      .eq( Employee.first )
      .and( Picture.arel_table[:imageable_type].eq('Employee') )
    
    nil_condition = Picture.arel_table[:imageable_id].eq(nil)
    
    Picture.where(
      Picture.arel_table.grouping(employee_condition).or(nil_condition)
    )
    

    这将生成:

    SELECT "pictures".* FROM "pictures" WHERE (
      ("pictures"."imageable_id" = 1 AND "pictures"."imageable_type" = 'Employee')
      OR "pictures"."imageable_id" IS NULL
    )
    

    为了更好地理解上面代码中发生的事情,请阅读 Using Arel to Compose SQL QueriesRails/Arel - precedence when combining AREL predicates

    【讨论】:

    • 谢谢!我见过有人提到 Arel,但认为它是第三方的宝石。我会试一试。它看起来不像我给出的答案那么干净,但它实际上做了我想要的查询。我想知道性能差异是什么。
    • 它很冗长,但如果你稍微重构一下它会更好看。 ActiveRecord 在下面使用 AREL,因此性能上的差异应该不存在。在幕后,ActiveRecord 使用 Arel 构建查询并最终调用它以获取最终 SQL,然后再将其传送到您选择的数据库。更多信息在这里:jpospisil.com/2014/06/16/… 和这里:slideshare.net/camerondutro/…
    • 感谢克里斯蒂安。我看到了 Jiří 的帖子,但 Cameron 的幻灯片真的很有帮助。对于这个相对简单的查询,我开始认为写出普通查询实际上是最干净的选择,但我很高兴能更多地了解 Arel 的强大功能。
    • 另外,我所说的性能是指查询 1 (A is 'X' OR A is Null) AND (B is 'Y' OR B is NULL) 和查询 2 (A is 'X' AND B is 'Y') AND (B is NULL) 之间的区别您可以在对我的回答(这是获取查询 1 的一种方式)的编辑中看到性能问题没有实际意义,因为 1 和 2 对我的应用程序不等效,因为有时 A 为“Z”且 Bis NULL 查询 1 无法捕获。
    【解决方案3】:

    所以以下方法有效,但我不确定它是否是最好的,甚至是唯一的方法:

    @empolyee = Employee.first
    Picture.where(imageable_type: ["Employee", nil], imageable_id: [@empolyee.id, nil])
    

    这运行以下,我不确定它是否更慢。

    SELECT "pictures".* FROM "pictures"
      WHERE (("pictures"."imageable_type" = 'Organization' OR "pictures"."imageable_type" IS NULL)) AND
            (("pictures"."imageable_id" = 1 OR "pictures"."imageable_id" IS NULL))
    

    编辑

    虽然这可行,但可能存在失败的边缘情况。

    我认为,如果图片属于产品并且该产品被删除(并且我的依赖策略设置为无效),那么图片将 null 作为“imageable_id”但仍然将“产品”作为“imageable_type” .

    我的上述回答找不到这样的图片,因为“imageable_type”既不是“Employee”也不是 NULL。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-21
      相关资源
      最近更新 更多