Ruby on rails 使用Rails和Squeel对复杂条件下的结果进行排序

Ruby on rails 使用Rails和Squeel对复杂条件下的结果进行排序,ruby-on-rails,squeel,Ruby On Rails,Squeel,我可能在这里做了一些愚蠢的事情——我也愿意接受其他方式——但我正试图根据计算字段对结果集进行排序: 我的问题是出生日期可能是零。这会导致cast((…).as int)调用返回三个不同的值-1如果表达式的计算结果为true0如果表达式的计算结果为false;如果基础列值为nil,则为nil 表达式中的nil值导致整个排名评估为nil——这意味着,即使我有一个与姓氏和给定姓名完全匹配的记录,如果出生日期列为nil,记录的排名为nil 我曾尝试在cast中使用一个复杂表达式来检查是否为nil或匹配的

我可能在这里做了一些愚蠢的事情——我也愿意接受其他方式——但我正试图根据计算字段对结果集进行排序:

我的问题是出生日期可能是零。这会导致
cast((…).as int)
调用返回三个不同的值-
1
如果表达式的计算结果为
true
<代码>0如果表达式的计算结果为
false
;如果基础列值为
nil
,则为
nil

表达式中的
nil
值导致整个排名评估为
nil
——这意味着,即使我有一个与
姓氏和
给定姓名完全匹配的记录,如果
出生日期列为
nil
,记录的
排名
nil

我曾尝试在
cast
中使用一个复杂表达式来检查
是否为nil或匹配的
值,但使用
时出现了一个squel异常,ruby在使用
|
时对其进行求值

我还尝试按别名列的顺序使用谓词:

order{[`ranking` != nil, `ranking`.desc]}
但这会引发一个
ActiveRecord
异常,抱怨列
ranking
不存在


我已经筋疲力尽了。。。有什么想法吗?

在跳了一段舞之后,我能够使用一系列与其他范围的外部连接来计算
排名,如下所示:

def self.weighted_by_any (client)
  scope = 
    select{[`clients.*`,
            [
             ((cast((`not rank_A.id is null`).as int) * 100) if client[:social_insurance_number].present?),
             ((cast((`not rank_B.id is null`).as int) * 10) if client[:surname].present?),
             ((cast((`not rank_C.id is null`).as int) * 1) if client[:given_names].present?), 
             ((cast((`not rank_D.id is null`).as int) * 1) if client[:date_of_birth].present?)
            ].compact.reduce(:+).as(`ranking`)
          ]}.by_any(client)

  scope = scope.joins{"left join (" + Client.weigh_social_insurance_number(client).to_sql + ") AS rank_A ON rank_A.id = clients.id"} if client[:social_insurance_number].present?
  scope = scope.joins{"left join (" + Client.weigh_surname(client).to_sql + ") AS rank_B on rank_B.id = clients.id"} if client[:surname].present?
  scope = scope.joins{"left join (" + Client.weigh_given_names(client).to_sql + ") AS rank_C on rank_C.id = clients.id"} if client[:given_names].present?
  scope = scope.joins{"left join (" + Client.weigh_date_of_birth(client).to_sql + ") AS rank_D on rank_D.id = clients.id"} if client[:date_of_birth].present?
  scope.order{`ranking`.desc}
end
其中
客户端。称重。以下是我的想法:

首先,我用筛选器替换了
称重(客户端)
范围。我以前发现,您可以在范围的
select{}
部分使用筛选器,我们将在一分钟内使用它

sifter :weigh_social_insurance_number do |token|
  # check if the token is present - we don't want to match on nil, but we want the column in the results
  # cast the comparison of the token to the column to an integer -> nil = nil, true = 1, false = 0
  # use coalesce to replace the nil value with `0` (for no match)
  (token.present? ? coalesce(cast((social_insurance_number == token).as int), `0`) : `0`).as(weight_social_insurance_number)
end

sifter :weigh_surname do |token|
  (token.present? ? coalesce(cast((surname == token).as int), `0`) :`0`).as(weight_surname)
end

sifter :weigh_given_names do |token|
  (token.present? ? coalesce(cast((given_names == token).as int), `0`) : `0`).as(weight_given_names)
end

sifter :weigh_date_of_birth do |token|
  (token.present? ? coalesce(cast((date_of_birth == token).as int), `0`) : `0`).as(weight_date_of_birth)
end
因此,让我们使用筛选器创建一个范围来衡量我们的所有标准:

def self.weigh_criteria (client)
  select{[`*`, 
          sift(weigh_social_insurance_number, client[:social_insurance_number]),
          sift(weigh_surname, client[:surname]),
          sift(weigh_given_names, client[:given_names]),
          sift(weigh_date_of_birth, client[:date_of_birth])
        ]}
end
现在我们可以确定提供的标准是否与列值匹配,我们使用另一个筛选器计算排名:

sifter :ranking do
  (weight_social_insurance_number * 100 + weight_surname * 10 + weight_date_of_birth * 5 + weight_given_names).as(ranking)
end
并将其加在一起,使我们的范围包括所有模型属性和计算属性:

def self.weighted_by_any (client)
  # check if the date is valid 
  begin 
    client[:date_of_birth] = Date.parse(client[:date_of_birth])
  rescue => e
    client.delete(:date_of_birth)
  end

  select{[`*`, sift(ranking)]}.from("(#{weigh_criteria(client).by_any(client).to_sql}) clients").order{`ranking`.desc}
end
因此,我现在可以搜索一个客户,并根据其与提供的标准的匹配程度对结果进行排序:

irb(main): Client.weighted_by_any(client)
  Client Load (8.9ms)  SELECT *, 
                              "clients"."weight_social_insurance_number" * 100 + 
                              "clients"."weight_surname" * 10 + 
                              "clients"."weight_date_of_birth" * 5 + 
                              "clients"."weight_given_names" AS ranking 
                       FROM (
                             SELECT *, 
                                    coalesce(cast("clients"."social_insurance_number" = '<sin>' AS int), 0) AS weight_social_insurance_number, 
                                    coalesce(cast("clients"."surname" = '<surname>' AS int), 0) AS weight_surname, 
                                    coalesce(cast("clients"."given_names" = '<given_names>' AS int), 0) AS weight_given_names,         0 AS weight_date_of_birth 
                             FROM "clients" 
                             WHERE ((("clients"."social_insurance_number" = '<sin>' 
                                   OR "clients"."surname" ILIKE '<surname>%') 
                                   OR "clients"."given_names" ILIKE '<given_names>%'))
                            ) clients 
                       ORDER BY ranking DESC
irb(主):客户端。按任意(客户端)加权
客户端负载(8.9ms)选择*,
“客户”.“权重、社会保险号”*100+
“客户”.“体重姓氏”*10+
“客户”,“体重、出生日期”*5+
“客户”,“权重”作为排名
从(
选择*,
合并(强制转换(“客户”,“社会保险号”=''为整数),0)为权重(社会保险号),
合并(强制转换(“客户”。“姓氏”=”为int),0)为权重\姓氏,
合并(强制转换(“客户端”。“给定名称”=''为int),0)为权重\u给定名称,0为权重\u出生日期\u
来自“客户”
式中((“客户”,“社会保险号”=”
或“客户”。“姓氏”类似“%”)
或“客户端”。“给定名称”类似“%”)
)客户
按等级顺序描述
更干净,更优雅,工作更好

def self.weighted_by_any (client)
  # check if the date is valid 
  begin 
    client[:date_of_birth] = Date.parse(client[:date_of_birth])
  rescue => e
    client.delete(:date_of_birth)
  end

  select{[`*`, sift(ranking)]}.from("(#{weigh_criteria(client).by_any(client).to_sql}) clients").order{`ranking`.desc}
end
irb(main): Client.weighted_by_any(client)
  Client Load (8.9ms)  SELECT *, 
                              "clients"."weight_social_insurance_number" * 100 + 
                              "clients"."weight_surname" * 10 + 
                              "clients"."weight_date_of_birth" * 5 + 
                              "clients"."weight_given_names" AS ranking 
                       FROM (
                             SELECT *, 
                                    coalesce(cast("clients"."social_insurance_number" = '<sin>' AS int), 0) AS weight_social_insurance_number, 
                                    coalesce(cast("clients"."surname" = '<surname>' AS int), 0) AS weight_surname, 
                                    coalesce(cast("clients"."given_names" = '<given_names>' AS int), 0) AS weight_given_names,         0 AS weight_date_of_birth 
                             FROM "clients" 
                             WHERE ((("clients"."social_insurance_number" = '<sin>' 
                                   OR "clients"."surname" ILIKE '<surname>%') 
                                   OR "clients"."given_names" ILIKE '<given_names>%'))
                            ) clients 
                       ORDER BY ranking DESC