list: show each ranked item's rating
Assisted-by: Claude Sonnet 5.5 via Claude Code
diff --git a/lib/rank_king/models.rb b/lib/rank_king/models.rb
index 1a1f498..5f9e7b6 100644
--- a/lib/rank_king/models.rb
+++ b/lib/rank_king/models.rb
@@ -42,9 +42,9 @@ module RankKing
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.
- def ranking
- ratings = self.ratings
+ # 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 = OpenSkill::Model.new
[ranked.sort_by { |item| [-model.ordinal(ratings.fetch(item.id)), item.id] }, unranked]
@@ -52,10 +52,9 @@ module RankKing
# Each compared item's rating, from replaying the axis's results in
# the order they were recorded.
- #: () -> Hash[Integer, OpenSkill::Rating]
def ratings
model = OpenSkill::Model.new
- ratings = {}
+ 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 }
diff --git a/lib/rank_king/views/list/show.rb b/lib/rank_king/views/list/show.rb
index 5b57ff7..56c1909 100644
--- a/lib/rank_king/views/list/show.rb
+++ b/lib/rank_king/views/list/show.rb
@@ -84,13 +84,20 @@ module RankKing
end
def render_ranking(axis)
- ranked, unranked = axis.ranking
+ ratings = axis.ratings
+ ranked, unranked = axis.ranking(ratings)
+ model = OpenSkill::Model.new
ol(class: "card") do
ranked.each.with_index(1) do |item, rank|
li do
span(class: "type-label") { format("%02d", rank) }
plain item.name
+ rating = ratings.fetch(item.id)
+ span(class: "type-label", data_rating: true, title: format("μ %.1f σ %.1f", rating.mu, rating.sigma)) do
+ # Adding 0.0 turns a rounded -0.0 into 0.0.
+ format("%.1f", model.ordinal(rating).round(1) + 0.0)
+ end
end
end
unranked.each do |item|
diff --git a/public/css/app.css b/public/css/app.css
index e5c8867..e9d751b 100644
--- a/public/css/app.css
+++ b/public/css/app.css
@@ -174,7 +174,7 @@ main > header [popovertarget] { anchor-name: --rk-list-actions; }
gap: var(--gl-space-s);
}
-/* The rank sits in a fixed gutter; "Unranked" sits at the far end. */
+/* The rank sits in a fixed gutter; the rating (or "Unranked") sits at the far end. */
.card > li:has(> .type-label:first-child) { align-items: baseline; }
.card > li > .type-label:first-child {
@@ -187,6 +187,16 @@ main > header [popovertarget] { anchor-name: --rk-list-actions; }
color: var(--gl-color-text-tertiary);
}
+/* The rating never shrinks or wraps, so a long name wraps in its own space. */
+[data-rating] {
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+/* Lets a name with no break points (a URL) break mid-word instead of
+ overflowing the row, where the card's overflow: hidden clips the rating. */
+.card > li:has(> [data-rating]) { overflow-wrap: anywhere; }
+
/* -------------------- */
/* Compare */
/* -------------------- */
diff --git a/sig/rank_king/models.rbs b/sig/rank_king/models.rbs
index 62373e2..9f60b86 100644
--- a/sig/rank_king/models.rbs
+++ b/sig/rank_king/models.rbs
@@ -34,7 +34,7 @@ module RankKing
def list: () -> List
def results: () -> Array[Result]
def results_dataset: () -> Sequel::Dataset[Result]
- def ranking: () -> [Array[Item], Array[Item]]
+ def ranking: (?Hash[Integer, OpenSkill::Rating]) -> [Array[Item], Array[Item]]
def ratings: () -> Hash[Integer, OpenSkill::Rating]
end
diff --git a/test/test_web.rb b/test/test_web.rb
index 4b3ed9d..e469bf8 100644
--- a/test/test_web.rb
+++ b/test/test_web.rb
@@ -240,6 +240,24 @@ class TestWeb < Minitest::Test
assert_includes body, "Compare on Taste"
end
+ def test_show_list_shows_ratings_on_ranked_rows_only
+ list, axis = create_list_with_axis(items: "Pizza\nSushi\nTacos")
+ pizza, sushi, = list.items
+ record_result(list, axis, winner: sushi, loser: pizza)
+
+ get "/lists/#{list.id}"
+ body = last_response.body[/role="tabpanel".*/m]
+ assert_equal 2, body.scan(/<span[^>]*data-rating/).size
+ model = OpenSkill::Model.new
+ winner, loser = [sushi, pizza].map { |i| axis.ratings.fetch(i.id) }
+ assert_operator model.ordinal(loser), :<, 0
+ shown = body.scan(%r{<span[^>]*data-rating[^>]*title="([^"]*)"[^>]*>(-?\d+\.\d)</span>})
+ assert_equal [
+ [format("μ %.1f σ %.1f", winner.mu, winner.sigma), format("%.1f", model.ordinal(winner))],
+ [format("μ %.1f σ %.1f", loser.mu, loser.sigma), format("%.1f", model.ordinal(loser))]
+ ], shown
+ end
+
def test_ranking_orders_by_rating_not_win_count
list, axis = create_list_with_axis(items: "A\nB\nC")
a, b, c = list.items.sort_by(&:id)