首先,我使用Rails v3.2.9和Squeel 1.0.13,这是我正在尝试做的事情:
我想使用三种识别信息中的任何一种搜索客户 - 姓名,出生日期(dob)和社会保险号码(罪)。结果集必须包含任何具有任何标识符的记录 - 条件的OR。我之前在Squeel做过这个,它看起来像是:
scope :by_any, ->(sin, name, dob){ where{(client.sin == "#{sin}") | (client.name =~ "%#{name}%") | (client.dob == "#{dob}")} }
只要我提供所有标识符,这都可以正常工作。但是,如果我只有一个名字怎么办?以上范围导致:
SELECT "clients".* FROM "clients" WHERE ((("clients"."sin" IS NULL OR "clients"."name" ILIKE '%John Doe%') OR "clients"."dob" IS NULL))
这包括sin为null的客户端集和dob为null的客户端集以及名为“John Doe”的请求客户端集。
因此,请尝试有条件地向where
块添加子句。起初,我尝试使用nil检查值?方法:
def self.by_any (sin, name, dob)
where do
(clients.sin == "#{sin}" unless sin.nil?) |
(clients.name =~ "%#{name}" unless name.nil?) |
(clients.dob == "#{dob}" unless dob.nil?)
end
导致:
SELECT "clients".* FROM "clients" WHERE ('t')
提出了许多其他问题,例如与't'有什么关系,但这是一个切线。
没有为每个排列编写where子句,有没有办法可以有条件地添加子句?
答案 0 :(得分:3)
所以,这不是最漂亮的东西,但它会照顾你所追求的。
def self.by_any(sin, name, dob)
where do
[
sin.presence && clients.sin == "#{sin}",
name.presence && clients.name =~ "%#{name}",
dob.presence && clients.dob == "#{dob}"
].compact.reduce(:|)
# compact to remove the nils, reduce to combine the cases with |
end
end
基本上,[a, b, c].reduce(:f)
会返回(a.f(b)).f(c)
。在这种情况下,调用的方法f
是管道,因此我们得到(a.|(b)).|(c)
,其中(a | b) | c
为不那么混乱的符号。
这很有效,因为在Squeel中,谓词运算符(==
,=~
等)会返回一个Predicate
节点,因此我们可以在将它们与{{{}}}连接之前独立构造它们。 {1}}。
如果所有三个都是|
,则返回所有记录。
答案 1 :(得分:0)
在最终找到this related post后,我蚕食@bradgonesurfing的备用模式来解决这个问题:
def self.by_any (sin, name, dob)
queries = Array.new
queries << self.by_sin(sin) unless sin.nil?
queries << self.by_name(name) unless name.nil?
queries << self.by_dob(dob) unless dob.nil?
self.where do
queries = queries.map { |q| id.in q.select{id} }
queries.inject { |s, i| s | i }
end
end
其中self.by_sin
,self.by_name
和self.by_dob
是带过滤器的简单范围。这产生了以下内容:
SELECT *
FROM clients
WHERE clients.id IN (<subquery for sin>)
OR clients.id IN (<subquery for name>)
OR clients.id IN (<subquery for dob>)
其中子查询仅包括其关联值不为nil的情况。
这实际上允许我将适当的范围组合在一起作为ActiveRecord :: Relation。