找到多态关联或无差异的记录

我有一个运行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中运行查询吗?

试试这个:

 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中动态定义了where_values 。 它映射与当前ActiveRecord::Relation相关的所有条件的Arel::Nodes “条件”。 这个节点响应and(other)reduce(:and)你可以将它们全部放在一起。

在Rails 4.2上,这不起作用。 你可以使它工作,但它变得奇怪(参见: WHERE子句中的OR运算符与Rails 4.2中的Arel – 问题的编辑 – )。 我们能做什么?

使用普通查询(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:

 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 ) 

要更好地理解上面代码中发生的事情,请阅读使用Arel撰写SQL查询和Rails / Arel – 组合AREL谓词时的优先级

所以下面的工作,但我不确定它是最好的,甚至是唯一的方式:

 @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’但仍然具有“Product”作为’imageable_type’。

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