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

时间:2015-03-22 21:31:09

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

我有一个运行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

我希望能够找到属于给定员工的所有图片,或者没有关联的&#34; imageable&#34;记录。

# 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中运行查询吗?

3 个答案:

答案 0 :(得分:2)

试试这个:

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。它映射Arel::Nodes&#34;条件&#34;与当前ActiveRecord::Relation相关的所有条件。此节点会回复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'理论上由搜索返回。

答案 1 :(得分:1)

为了获得您正在寻找的确切查询,请使用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

答案 2 :(得分:0)

以下是有效的,但我不确定它是最好还是唯一的方式:

@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”既不是“员工”也不是NULL。