Рабочее, но, вероятно, ужасное решение:
class Tournament < ApplicationRecord
has_many :teams
serialize :tiebreaker, Array
TIEBREAKER_WHITELIST = %w[score opponent_score possession].freeze
def sorted_teams
list = teams.shuffle
(TIEBREAKER_WHITELIST & tiebreaker).reverse.each do |metric|
list = list.sort_by { |team| [team.send(metric), list.find_index(team)] }
end
list.reverse
end
end
Каждый tournament
имеет множество teams
.Экземпляр tournament
имеет сериализованное поле с именем tiebreaker
.Он содержит массив строк, например, ["score", "possession"]
, где каждая строка соответствует имени метода открытого экземпляра в team
.Каждый из этих методов возвращает число.
Поле tiebreaker
находится в порядке убывания приоритета, поэтому для приведенного выше примера я бы ожидал, что possession
повлияет на сортировку только для команд с равным score
.
list = teams.shuffle
- это случайный список для начала, в случае если команды связаны для всех следующих тай-брейков.
(TIEBREAKER_WHITELIST & tiebreaker)
- это возвращает только строки, которые появляются как в поле tiebreaker
, так и в константе белого списка для защиты от конечных пользователей, использующих произвольные методы.
.reverse.each do |metric|
- это инвертирует массив метриктак что список сначала сортируется по метрике с наименьшим приоритетом.
[team.send(metric), list.find_index(team)]
- это сортировка для каждой метрики.send
превращает строку в вызов метода.Я обнаружил, что find_index
было необходимо для сохранения порядка сортировки из предыдущих сортировок.т. е. если бы я сначала отсортировал по possession
, это сохранило бы порядок для команд с одинаковым score
.
list.reverse
- переверните список и верните его.Это было потому, что я хотел, чтобы в моем списке сначала были команды с более высокой оценкой / владением и sort_by
сортировки по возрастанию.
Я хотел, чтобы некоторые метрики сортировались по возрастанию (opponent_score
), а другие - по убыванию (score
), поэтому я обработал этов соответствующих методах, например, возвращая отрицательные значения для opponent_score
.
Я не совсем доволен решением, как есть, но, похоже, оно работает!