0

私はおそらくここで何かばかげたことをしているでしょう-そして私は他の方法を受け入れています-しかし私は計算されたフィールドに基づいて結果セットを並べ替えようとしています:

Client.select{['clients.*',
               (cast((surname == matching_surname).as int) * 10 +
                cast((given_names == matching_given_names).as int) + 
                cast((date_of_birth == matching_date_of_birth).as int).as(ranking)]}.
       where{(surname =~ matching_surname) | 
             (given_names =~ matching_given_names) | 
             (date_of_birth == matching_date_of_birth)}.
       order{`ranking`.desc}

私の問題はそれdate_of_birthがゼロかもしれないということです。これにより、cast((...).as int)呼び出しは3つの異なる値を返します-式が;と1評価された場合。式が;と評価された場合; 基になる列の値がであった場合。true0falsenilnil

式のnil値により、ランキング全体がNIL-と評価されます。つまり、正確に一致するレコードがsurnameありgiven_namesdate_of_birth列がnil、のranking場合、レコードのforはですnil

castをチェックするで複雑な式を使用しようとしましたが、を使用if not nil or the matching_valueしてSqueel例外で失敗し、とを使用すると|rubyがそれを評価します。||or

また、エイリアス列の順序で述語を使用しようとしました。

order{[`ranking` != nil, `ranking`.desc]}

ただし、列が存在しないActiveRecordことを示す例外がスローされます。ranking

私は私のロープの終わりにいます...何かアイデアはありますか?

4

1 に答える 1

0

少し踊った後、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

Client.weigh_<attribute>(client)次のような別のスコープはどこにありますか。

def self.weigh_social_insurance_number (client)
  select{[:id]}.where{social_insurance_number == client[:social_insurance_number]}
end

これにより、nilのチェックから値の比較を行うことができ、ブール計算の3番目の値を削除しました(TRUE => 1、FALSE => 0)。

綺麗?効率的?エレガント?多分そうではありません...しかし働いています。:)

新しい情報に基づいて編集

Bigxiangの回答のおかげで、これをはるかに美しいものにリファクタリングしました 。これが私が思いついたものです:

まず、weigh_<attribute>(client)スコープをふるいに置き換えました。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

よりクリーンで、よりエレガントで、より良く機能します!

于 2013-05-24T15:07:52.563 に答える