Drop item metadata; source-specific item details get their own tables
Items keep only what any source has: name, external_id, url and
archived_at. With no JSON columns left, the hand-rolled parsing and the
json Steep library go too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG4iASZnHb1yEM1JAASiWn
diff --git a/Steepfile b/Steepfile
index 5f3fdfb..ae41aec 100644
--- a/Steepfile
+++ b/Steepfile
@@ -11,5 +11,4 @@ target :lib do
signature "sig"
library "securerandom"
- library "json"
end
diff --git a/db/migrate/004_add_list_sources.rb b/db/migrate/004_add_list_sources.rb
index 3857643..11280b4 100644
--- a/db/migrate/004_add_list_sources.rb
+++ b/db/migrate/004_add_list_sources.rb
@@ -14,7 +14,6 @@ Sequel.migration do
alter_table(:items) do
add_column :external_id, String
add_column :url, String
- add_column :metadata, String, null: false, default: "{}"
add_column :archived_at, DateTime
add_index [:list_id, :external_id], unique: true
end
@@ -30,7 +29,6 @@ Sequel.migration do
drop_index [:list_id, :external_id]
drop_column :external_id
drop_column :url
- drop_column :metadata
drop_column :archived_at
end
diff --git a/lib/rank_king/models.rb b/lib/rank_king/models.rb
index 04537a6..6a23561 100644
--- a/lib/rank_king/models.rb
+++ b/lib/rank_king/models.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "json"
-
require_relative "db"
require_relative "sources"
@@ -26,13 +24,6 @@ module RankKing
# @rbs skip
class Item < Sequel::Model(DB[:items])
- # JSON by hand rather than Sequel's serialization plugin, which passes
- # json 3.1 an option it no longer takes.
- def metadata = JSON.parse(super)
- def metadata=(value)
- super(JSON.generate(value))
- end
-
many_to_one :list, class: "RankKing::List"
end
diff --git a/lib/rank_king/sources.rb b/lib/rank_king/sources.rb
index 179827b..20c49b3 100644
--- a/lib/rank_king/sources.rb
+++ b/lib/rank_king/sources.rb
@@ -4,9 +4,11 @@ module RankKing
module Sources
# What every source produces: one entry per item currently in the source.
# external_id is how sync recognizes the same item across snapshots.
+ # Only what any source has goes here; a kind's own item details belong
+ # in its own table.
# Signed in sig/rank_king/sources.rbs: a Data class.
# @rbs skip
- Entry = Data.define(:external_id, :name, :url, :metadata)
+ Entry = Data.define(:external_id, :name, :url)
end
end
diff --git a/lib/rank_king/sources/text.rb b/lib/rank_king/sources/text.rb
index a784c5f..8caa4c9 100644
--- a/lib/rank_king/sources/text.rb
+++ b/lib/rank_king/sources/text.rb
@@ -14,7 +14,7 @@ module RankKing
#: (untyped snapshot) -> Array[Entry]
def entries(snapshot)
snapshot.to_s.lines.map(&:strip).reject(&:empty?).uniq.map {|line|
- Entry.new(external_id: line, name: line, url: nil, metadata: {})
+ Entry.new(external_id: line, name: line, url: nil)
}
end
end
diff --git a/lib/rank_king/sync.rb b/lib/rank_king/sync.rb
index 344fb8c..e660bcf 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, metadata: entry.metadata, archived_at: nil }
+ attrs = { name: entry.name, url: entry.url, archived_at: nil }
if (item = existing.delete(entry.external_id))
restored += 1 if item.archived_at
item.update(**attrs)
diff --git a/sig/rank_king/models.rbs b/sig/rank_king/models.rbs
index a136203..37e0877 100644
--- a/sig/rank_king/models.rbs
+++ b/sig/rank_king/models.rbs
@@ -27,8 +27,6 @@ module RankKing
def list: () -> List
def external_id: () -> String?
def url: () -> String?
- def metadata: () -> Hash[String, untyped]
- def metadata=: (Hash[String, untyped] value) -> void
def archived_at: () -> Time?
end
diff --git a/sig/rank_king/sources.rbs b/sig/rank_king/sources.rbs
index de9485d..aaef0ad 100644
--- a/sig/rank_king/sources.rbs
+++ b/sig/rank_king/sources.rbs
@@ -7,9 +7,8 @@ module RankKing
attr_reader external_id: String
attr_reader name: String
attr_reader url: String?
- attr_reader metadata: Hash[String, untyped]
- def self.new: (external_id: String, name: String, url: String?, metadata: Hash[String, untyped]) -> instance
+ def self.new: (external_id: String, name: String, url: String?) -> instance
end
# A source instance: turns a snapshot (the pasted text for a push
diff --git a/test/test_sync.rb b/test/test_sync.rb
index fbeb68a..da88ef6 100644
--- a/test/test_sync.rb
+++ b/test/test_sync.rb
@@ -48,14 +48,13 @@ class TestSync < Minitest::Test
assert_equal %w[A B], names
end
- def test_refreshes_name_url_and_metadata
- Sync.call(@list, [Sources::Entry.new(external_id: "v1", name: "Old", url: nil, metadata: {})])
- Sync.call(@list, [Sources::Entry.new(external_id: "v1", name: "New", url: "https://example.com/v1", metadata: { "thumb" => "t.jpg" })])
+ def test_refreshes_name_and_url
+ Sync.call(@list, [Sources::Entry.new(external_id: "v1", name: "Old", url: nil)])
+ Sync.call(@list, [Sources::Entry.new(external_id: "v1", name: "New", url: "https://example.com/v1")])
item = Item.first(external_id: "v1")
assert_equal "New", item.name
assert_equal "https://example.com/v1", item.url
- assert_equal({ "thumb" => "t.jpg" }, item.metadata)
end
def test_text_source_strips_blank_and_duplicate_lines