config: build the OpenSkill model once
Axis#ranking, Axis#ratings, and the list view each built their own
model; they now read CONFIG.open_skill so the sort order and the
displayed ordinal cannot drift apart.

Assisted-by: Claude Sonnet 5.5 via Claude Code
change rslurpmkylnrmnqnskrrtkuxlwyqyxoy
commit ad44d4d383ac604d3762eaca5904f1dc905df562
author Alpha Chen <alpha@kejadlen.dev>
date
parent mluuzlwv
diff --git a/lib/rank_king/config.rb b/lib/rank_king/config.rb
index efbcda8..07e3bf3 100644
--- a/lib/rank_king/config.rb
+++ b/lib/rank_king/config.rb
@@ -2,11 +2,14 @@
 
 require "securerandom"
 
+require_relative "../open_skill"
+
 module RankKing
   # @rbs skip
   Config = Data.define(
     :database_url,
     :session_secret,
+    :open_skill,
   )
 
   class Config
@@ -20,9 +23,10 @@ module RankKing
       new(
         database_url: env.fetch("DATABASE_URL", ":memory:"),
         session_secret: env.fetch("SESSION_SECRET") { SecureRandom.hex(64) },
+        open_skill: OpenSkill::Model.new,
       )
     end
   end
 
-  CONFIG = Config.from_env
+  CONFIG = Config.from_env #: Config
 end
diff --git a/lib/rank_king/models.rb b/lib/rank_king/models.rb
index 5f9e7b6..5f613b8 100644
--- a/lib/rank_king/models.rb
+++ b/lib/rank_king/models.rb
@@ -46,14 +46,14 @@ module RankKing
     # 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
+      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.
     def ratings
-      model = OpenSkill::Model.new
+      model = CONFIG.open_skill
       ratings = {} #: Hash[Integer, OpenSkill::Rating]
       results_dataset.order(:id).each do |result|
         winner = ratings.fetch(result.winner_id) { model.rating }
diff --git a/lib/rank_king/views/list/show.rb b/lib/rank_king/views/list/show.rb
index 56c1909..f8b8629 100644
--- a/lib/rank_king/views/list/show.rb
+++ b/lib/rank_king/views/list/show.rb
@@ -86,7 +86,7 @@ module RankKing
         def render_ranking(axis)
           ratings = axis.ratings
           ranked, unranked = axis.ranking(ratings)
-          model = OpenSkill::Model.new
+          model = CONFIG.open_skill
 
           ol(class: "card") do
             ranked.each.with_index(1) do |item, rank|
diff --git a/sig/rank_king/config.rbs b/sig/rank_king/config.rbs
index f359a38..0a5bcad 100644
--- a/sig/rank_king/config.rbs
+++ b/sig/rank_king/config.rbs
@@ -5,7 +5,8 @@ module RankKing
   class Config
     attr_reader database_url: String
     attr_reader session_secret: String
+    attr_reader open_skill: OpenSkill::Model
 
-    def self.new: (database_url: String, session_secret: String) -> instance
+    def self.new: (database_url: String, session_secret: String, open_skill: OpenSkill::Model) -> instance
   end
 end
diff --git a/test/test_web.rb b/test/test_web.rb
index e469bf8..0d38c8d 100644
--- a/test/test_web.rb
+++ b/test/test_web.rb
@@ -248,7 +248,7 @@ class TestWeb < Minitest::Test
     get "/lists/#{list.id}"
     body = last_response.body[/role="tabpanel".*/m]
     assert_equal 2, body.scan(/<span[^>]*data-rating/).size
-    model = OpenSkill::Model.new
+    model = RankKing::CONFIG.open_skill
     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>})