2013-03-14 1 views
0

Я, вероятно, занимаюсь чем-то глупым здесь - и я открыт для других способов делать, но я пытаюсь упорядочить свой набор результатов на основе вычисленного поля:Результаты ранжирования в сложных условиях с использованием Rails и Squeel

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) для возврата трех разных значений: 1, если выражение оценивается как true; 0, если выражение оценивается как false; и nil, если базовое значение столбца было nil.

В nil значения из выражений вызывает весь рейтинг для оценки в NIL - это значит, что даже если у меня есть запись, которая соответствует именно на surname и given_names, если date_of_birth столбца nil, тем ranking для записи является nil ,

Я пытался использовать сложное выражение в cast, который проверяет if not nil or the matching_value, но она не с Squeel исключением использования | и рубин оценивает его при использовании || и or.

Я также пытался использовать предикаты в порядке для псевдонимов столбцов:

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

но бросает исключение ActiveRecord жалуясь, что столбец ranking не существует.

Я нахожусь в конце своей веревки ... любые идеи?

ответ

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 и удалить третье значение в моем булевом расчете (TRUE => 1, FALSE => 0).

Чистый? Эффективное? Элегантный? Может быть, нет ... но работает. :)

EDIT база новой информации

Я переработан это во что-то гораздо более красивой, благодаря Bigxiang's answer. Вот то, что я придумал:

Во-первых, я заменил области с просеивателями. Ранее я обнаружил, что вы можете использовать просеиватели в части области 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 

уборщик, более элегантный и работает лучше!