使用Squeel时,是否可以有条件地在where块中添加子句?

首先,我使用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子句添加到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') 

提出了许多其他问题,比如那个问题的处理方式,但这是一个切线。

如果没有为每个排列编写where子句,有没有办法可以有条件地添加子句?

所以,这不是最漂亮的东西,但它会做你想要的。

 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)返回(af(b)).f(c) 。 在这种情况下, f ,被调用的方法是管道,所以我们得到(a.|(b)).|(c) ,在较少混淆的表示法中,是(a | b) | c (a | b) | c

它的工作原理是,在Squeel中,谓词运算符( ===~等)返回一个Predicate节点,因此我们可以在连接之前独立构造它们|

在所有三个都nil的情况下,它返回所有记录。

在最终找到这篇相关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_sinself.by_nameself.by_dob是带filter的简单范围。 这会产生以下内容:

 SELECT * FROM clients WHERE clients.id IN () OR clients.id IN () OR clients.id IN () 

子查询仅包括其关联值不为零的情况。

这有效地允许我将适当的作用域组合在一起作为ActiveRecord :: Relation。