1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# frozen_string_literal: true
require_relative "../open_skill"
require_relative "db"
require_relative "sources"
Sequel::Model.plugin :timestamps, update_on_create: true
module RankKing
# Signed in sig/rank_king/models.rbs: the superclass is not a constant.
# @rbs skip
class User < Sequel::Model(DB[:users])
one_to_many :lists, class: "RankKing::List"
end
# @rbs skip
class List < Sequel::Model(DB[:lists])
many_to_one :user, class: "RankKing::User"
# Archived items are hidden everywhere for now.
one_to_many :items, class: "RankKing::Item", conditions: { archived_at: nil }
one_to_many :axes, class: "RankKing::Axis"
# Archived items too, which the items association leaves out.
def before_destroy
Result.where(axis_id: axes_dataset.select(:id)).delete
axes_dataset.delete
Item.where(list_id: id).delete
super
end
end
# @rbs skip
class Item < Sequel::Model(DB[:items])
many_to_one :list, class: "RankKing::List"
end
# @rbs skip
class Axis < Sequel::Model(DB[:axes])
many_to_one :list, class: "RankKing::List"
one_to_many :results, class: "RankKing::Result"
# The list's items in order on this axis, then the ones with no
# results on it yet. Ranked by OpenSkill ordinal, best first. Pass
# the axis's `ratings` when the caller already has them.
def ranking(ratings = self.ratings)
ranked, unranked = list.items.partition { |item| ratings.key?(item.id) }
model = CONFIG.open_skill
[ranked.sort_by { |item| [-model.ordinal(ratings.fetch(item.id)), item.id] }, unranked]
end
# Each compared item's rating, from replaying the axis's results in
# the order they were recorded. A tie is a draw: both placed first.
def ratings
model = CONFIG.open_skill
ratings = {} #: Hash[Integer, OpenSkill::Rating]
results_dataset.order(:id).each do |result|
winner = ratings.fetch(result.winner_id) { model.rating }
loser = ratings.fetch(result.loser_id) { model.rating }
game = result.tie ? OpenSkill::Game.new([[winner], [loser]]) : OpenSkill::Game.win_lose([winner], [loser])
(new_winner,), (new_loser,) = model.rate(game)
ratings[result.winner_id] = new_winner
ratings[result.loser_id] = new_loser
end
ratings
end
def before_destroy
results_dataset.delete
super
end
end
# @rbs skip
class Result < Sequel::Model(DB[:results])
many_to_one :axis, class: "RankKing::Axis"
many_to_one :winner, class: "RankKing::Item"
many_to_one :loser, class: "RankKing::Item"
end
end