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

时间:2013-01-03 13:10:36

标签: ruby-on-rails ruby squeel

首先,我使用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子句,有没有办法可以有条件地添加子句?

2 个答案:

答案 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_sinself.by_nameself.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。