Drop item url and use change for the sources migration
Nothing sets or shows an item's url; a synced kind can keep a link in its
own item table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG4iASZnHb1yEM1JAASiWn
change
commit 0b8d61af389fb40b88f36214fe851def026e97c4
author Claude <noreply@anthropic.com>
date
parent 006fd470
diff --git a/db/migrate/004_add_list_sources.rb b/db/migrate/004_add_list_sources.rb
index 4984a96..2a09109 100644
--- a/db/migrate/004_add_list_sources.rb
+++ b/db/migrate/004_add_list_sources.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 Sequel.migration do
-  up do
+  change do
     # Every list gets its items from exactly one source. Lists before this
     # were made from the textarea, so they default to the text source. A
     # kind that needs configuring gets its own table keyed by list_id; Text
@@ -11,24 +11,11 @@ Sequel.migration do
       add_column :synced_at, DateTime
     end
 
-    # Items keep only what any source has. How a kind recognizes its items
-    # across syncs, and anything else it knows about them, lives in its own
-    # table keyed by item_id; Text recognizes items by name.
+    # Items keep only what any source has. How a synced kind recognizes its
+    # items across syncs, and anything else it knows about them, lives in its
+    # own table keyed by item_id.
     alter_table(:items) do
-      add_column :url, String
       add_column :archived_at, DateTime
     end
   end
-
-  down do
-    alter_table(:items) do
-      drop_column :url
-      drop_column :archived_at
-    end
-
-    alter_table(:lists) do
-      drop_column :source_kind
-      drop_column :synced_at
-    end
-  end
 end
diff --git a/lib/rank_king/sources.rb b/lib/rank_king/sources.rb
index cd08a41..9909ef2 100644
--- a/lib/rank_king/sources.rb
+++ b/lib/rank_king/sources.rb
@@ -4,10 +4,10 @@ module RankKing
   module Sources
     # What every source produces: one entry per item currently in the source.
     # key is how the source recognizes the same item across syncs (see
-    # items_by_key); name and url are the item fields any source has.
+    # items_by_key); name is the item field any source has.
     # Signed in sig/rank_king/sources.rbs: a Data class.
     # @rbs skip
-    Entry = Data.define(:key, :name, :url)
+    Entry = Data.define(:key, :name)
   end
 end
 
diff --git a/lib/rank_king/sync.rb b/lib/rank_king/sync.rb
index 3c73739..ea6b9e5 100644
--- a/lib/rank_king/sync.rb
+++ b/lib/rank_king/sync.rb
@@ -19,7 +19,7 @@ module RankKing
         added = restored = 0
 
         entries.each do |entry|
-          attrs = { name: entry.name, url: entry.url, archived_at: nil }
+          attrs = { name: entry.name, archived_at: nil }
           if (item = existing.delete(entry.key))
             restored += 1 if item.archived_at
             item.update(**attrs)
diff --git a/sig/rank_king/models.rbs b/sig/rank_king/models.rbs
index 94c9ef4..d203e49 100644
--- a/sig/rank_king/models.rbs
+++ b/sig/rank_king/models.rbs
@@ -25,7 +25,6 @@ module RankKing
     def name: () -> String
     def list_id: () -> Integer
     def list: () -> List
-    def url: () -> String?
     def archived_at: () -> Time?
   end
 
diff --git a/sig/rank_king/sources.rbs b/sig/rank_king/sources.rbs
index 1f3a94a..02f720e 100644
--- a/sig/rank_king/sources.rbs
+++ b/sig/rank_king/sources.rbs
@@ -6,9 +6,8 @@ module RankKing
     class Entry
       attr_reader key: String
       attr_reader name: String
-      attr_reader url: String?
 
-      def self.new: (key: String, name: String, url: String?) -> instance
+      def self.new: (key: String, name: String) -> instance
     end
 
     # A synced source instance, for one list.
diff --git a/test/test_sync.rb b/test/test_sync.rb
index a9df977..20acff9 100644
--- a/test/test_sync.rb
+++ b/test/test_sync.rb
@@ -9,19 +9,20 @@ require_relative "../lib/rank_king/sync"
 class TestSync < Minitest::Test
   include RankKing
 
-  # A synced source that recognizes its items by URL, standing in for a
-  # kind that keeps its keys in its own table.
+  # A synced source that keeps each item's key in memory, standing in for a
+  # kind that keeps its keys in its own table keyed by item_id.
   class FakeSource
     attr_reader :calls
 
-    def initialize(list)
-      @list = list
+    def initialize
+      @keys = {}
       @calls = []
     end
 
-    def items_by_key = RankKing::Item.where(list_id: @list.id).exclude(url: nil).to_hash(:url)
+    def items_by_key = @keys.transform_values {|id| RankKing::Item[id] }
 
     def saved(item, entry)
+      @keys[entry.key] = item.id
       @calls << [item.id, entry.key]
     end
   end
@@ -35,7 +36,7 @@ class TestSync < Minitest::Test
 
     user = User.create(login: "alpha@example.com")
     @list = List.create(name: "Videos", user_id: user.id, source_kind: "Fake")
-    @source = FakeSource.new(@list)
+    @source = FakeSource.new
   end
 
   def test_adds_new_entries
@@ -52,17 +53,17 @@ class TestSync < Minitest::Test
 
     assert_equal 1, summary.archived
     assert_equal %w[a], names
-    refute_nil Item.first(url: "https://example.com/b").archived_at
+    refute_nil Item.first(name: "b").archived_at
   end
 
   def test_restores_entries_that_come_back
     sync("a", "b")
-    b = Item.first(url: "https://example.com/b")
+    b = Item.first(name: "b")
     sync("a")
     summary = sync("a", "b")
 
     assert_equal 1, summary.restored
-    assert_equal b.id, Item.first(url: "https://example.com/b").id
+    assert_equal [b.id], Item.where(name: "b").map(:id)
     assert_equal %w[a b], names
   end
 
@@ -72,14 +73,13 @@ class TestSync < Minitest::Test
     Sync.call(@list, [entry("a", name: "Renamed")], source: @source)
 
     assert_equal "Renamed", item.reload.name
-    assert_equal [[item.id, "https://example.com/a"]] * 2, @source.calls
+    assert_equal [[item.id, "a"]] * 2, @source.calls
   end
 
   private
 
   def entry(key, name: key)
-    url = "https://example.com/#{key}"
-    Sources::Entry.new(key: url, name:, url:)
+    Sources::Entry.new(key:, name:)
   end
 
   def sync(*keys) = Sync.call(@list, keys.map {|key| entry(key) }, source: @source)