Model seed photos and assets as Data classes
Assisted-by: Claude Opus 4.8 via Claude Code
diff --git a/lib/seeds.rb b/lib/seeds.rb
index 9f816f7..c2fa000 100644
--- a/lib/seeds.rb
+++ b/lib/seeds.rb
@@ -13,44 +13,55 @@ module Domus
module Seeds
USER_AGENT = "domus-seed/1.0 (https://github.com/kejadlen/domus)"
- # CC0 cat photos keyed by a short name. CC0 needs no attribution, but the
- # Wikimedia Commons file pages document the license and provenance:
+ # A seed photo. +key+ names the cache file; +url+ is the source download.
+ Photo = Data.define(
+ :key, #: Symbol
+ :url, #: String
+ )
+
+ # An asset to seed, with zero or more attached photos.
+ Asset = Data.define(
+ :name, #: String
+ :description, #: String
+ :photos, #: Array[Photo]
+ )
+
+ # CC0 cat photos. CC0 needs no attribution, but the Wikimedia Commons file
+ # pages document the license and provenance:
# https://creativecommons.org/publicdomain/zero/1.0/
- # grass: commons.wikimedia.org/wiki/File:Domestic_shorthair_cat_portrait_in_grass.jpg
- # tabby: commons.wikimedia.org/wiki/File:Tabby_cat_with_blue_eyes-3336579.jpg
- # paw: commons.wikimedia.org/wiki/File:Domestic_cat_paw.jpg
- # kitten: commons.wikimedia.org/wiki/File:A_mother_cat_of_Meitei_domestic_cat_breed_(Meitei_house_cat_variety)_suckling_her_only_little_newborn_baby_kitten_05.jpg
- PHOTOS = {
- grass: "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Domestic_shorthair_cat_portrait_in_grass.jpg/960px-Domestic_shorthair_cat_portrait_in_grass.jpg",
- tabby: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Tabby_cat_with_blue_eyes-3336579.jpg/960px-Tabby_cat_with_blue_eyes-3336579.jpg",
- paw: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cc/Domestic_cat_paw.jpg/960px-Domestic_cat_paw.jpg",
- kitten: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/A_mother_cat_of_Meitei_domestic_cat_breed_%28Meitei_house_cat_variety%29_suckling_her_only_little_newborn_baby_kitten_05.jpg/960px-A_mother_cat_of_Meitei_domestic_cat_breed_%28Meitei_house_cat_variety%29_suckling_her_only_little_newborn_baby_kitten_05.jpg",
- } #: Hash[Symbol, String]
+ # commons.wikimedia.org/wiki/File:Domestic_shorthair_cat_portrait_in_grass.jpg
+ # commons.wikimedia.org/wiki/File:Tabby_cat_with_blue_eyes-3336579.jpg
+ # commons.wikimedia.org/wiki/File:Domestic_cat_paw.jpg
+ # commons.wikimedia.org/wiki/File:A_mother_cat_of_Meitei_domestic_cat_breed_(Meitei_house_cat_variety)_suckling_her_only_little_newborn_baby_kitten_05.jpg
+ GRASS = Photo.new(:grass, "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Domestic_shorthair_cat_portrait_in_grass.jpg/960px-Domestic_shorthair_cat_portrait_in_grass.jpg")
+ TABBY = Photo.new(:tabby, "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Tabby_cat_with_blue_eyes-3336579.jpg/960px-Tabby_cat_with_blue_eyes-3336579.jpg")
+ PAW = Photo.new(:paw, "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cc/Domestic_cat_paw.jpg/960px-Domestic_cat_paw.jpg")
+ KITTEN = Photo.new(:kitten, "https://upload.wikimedia.org/wikipedia/commons/thumb/6/62/A_mother_cat_of_Meitei_domestic_cat_breed_%28Meitei_house_cat_variety%29_suckling_her_only_little_newborn_baby_kitten_05.jpg/960px-A_mother_cat_of_Meitei_domestic_cat_breed_%28Meitei_house_cat_variety%29_suckling_her_only_little_newborn_baby_kitten_05.jpg")
# Sample household assets. The drill is intentionally photoless so the
# asset page's add-photo affordance shows up in dev.
ASSETS = [
- {
+ Asset.new(
name: "Bosch 800 dishwasher",
description: "Stainless interior, third rack. Replaces the GE that flooded.",
- photos: %i[grass],
- },
- {
+ photos: [GRASS],
+ ),
+ Asset.new(
name: "LG French-door refrigerator",
description: "Counter-depth, craft-ice maker. Bought refurbished in 2023.",
- photos: %i[tabby kitten],
- },
- {
+ photos: [TABBY, KITTEN],
+ ),
+ Asset.new(
name: "Weber Genesis grill",
description: "Three-burner propane. Cover lives in the shed over winter.",
- photos: %i[paw],
- },
- {
+ photos: [PAW],
+ ),
+ Asset.new(
name: "Ryobi cordless drill",
description: "18V ONE+. Two batteries, charger in the garage drawer.",
photos: [],
- },
- ] #: Array[Hash[Symbol, untyped]]
+ ),
+ ] #: Array[Asset]
# Seeds the database when it's empty, returning true when it inserted data
# and false when assets already exist. The empty check keeps repeated dev
@@ -61,18 +72,18 @@ module Domus
db = app.db
return false unless db[:assets].empty?
- keys = ASSETS.flat_map { |spec| spec[:photos] }.uniq #: Array[Symbol]
- sources = keys.to_h { |key| [key, fetch(key)] } #: Hash[Symbol, Pathname]
+ photos = ASSETS.flat_map(&:photos).uniq #: Array[Photo]
+ sources = photos.to_h { |photo| [photo, fetch(photo)] } #: Hash[Photo, Pathname]
db.transaction do
now = Time.now
- ASSETS.each do |spec|
- asset_id = db[:assets].insert(name: spec[:name], description: spec[:description], created_at: now)
- spec[:photos].each do |key|
+ ASSETS.each do |asset|
+ asset_id = db[:assets].insert(name: asset.name, description: asset.description, created_at: now)
+ asset.photos.each do |photo|
file_id = db[:files].insert(extension: ".jpg", created_at: now)
dest = app.file_path(id: file_id, extension: ".jpg")
FileUtils.mkdir_p(dest.dirname)
- FileUtils.cp(sources.fetch(key), dest)
+ FileUtils.cp(sources.fetch(photo), dest)
db[:asset_attachments].insert(asset_id:, file_id:, created_at: now)
end
end
@@ -81,13 +92,13 @@ module Domus
end
# Returns the cached path to a seed photo, downloading it on first use.
- # : (Symbol) -> Pathname
- def self.fetch(key)
- path = cache_dir / "#{key}.jpg"
+ # : (Photo) -> Pathname
+ def self.fetch(photo)
+ path = cache_dir / "#{photo.key}.jpg"
return path if path.exist?
cache_dir.mkpath
- data = URI.parse(PHOTOS.fetch(key)).open("User-Agent" => USER_AGENT, &:read) #: String
+ data = URI.parse(photo.url).open("User-Agent" => USER_AGENT, &:read) #: String
path.binwrite(data)
path
end
diff --git a/sig/generated/seeds.rbs b/sig/generated/seeds.rbs
index a399e60..f83014c 100644
--- a/sig/generated/seeds.rbs
+++ b/sig/generated/seeds.rbs
@@ -9,18 +9,54 @@ module Domus
module Seeds
USER_AGENT: ::String
- # CC0 cat photos keyed by a short name. CC0 needs no attribution, but the
- # Wikimedia Commons file pages document the license and provenance:
+ # A seed photo. +key+ names the cache file; +url+ is the source download.
+ class Photo < Data
+ attr_reader key(): Symbol
+
+ attr_reader url(): String
+
+ def self.new: (Symbol key, String url) -> instance
+ | (key: Symbol, url: String) -> instance
+
+ def self.members: () -> [ :key, :url ]
+
+ def members: () -> [ :key, :url ]
+ end
+
+ # An asset to seed, with zero or more attached photos.
+ class Asset < Data
+ attr_reader name(): String
+
+ attr_reader description(): String
+
+ attr_reader photos(): Array[Photo]
+
+ def self.new: (String name, String description, Array[Photo] photos) -> instance
+ | (name: String, description: String, photos: Array[Photo]) -> instance
+
+ def self.members: () -> [ :name, :description, :photos ]
+
+ def members: () -> [ :name, :description, :photos ]
+ end
+
+ # CC0 cat photos. CC0 needs no attribution, but the Wikimedia Commons file
+ # pages document the license and provenance:
# https://creativecommons.org/publicdomain/zero/1.0/
- # grass: commons.wikimedia.org/wiki/File:Domestic_shorthair_cat_portrait_in_grass.jpg
- # tabby: commons.wikimedia.org/wiki/File:Tabby_cat_with_blue_eyes-3336579.jpg
- # paw: commons.wikimedia.org/wiki/File:Domestic_cat_paw.jpg
- # kitten: commons.wikimedia.org/wiki/File:A_mother_cat_of_Meitei_domestic_cat_breed_(Meitei_house_cat_variety)_suckling_her_only_little_newborn_baby_kitten_05.jpg
- PHOTOS: Hash[Symbol, String]
+ # commons.wikimedia.org/wiki/File:Domestic_shorthair_cat_portrait_in_grass.jpg
+ # commons.wikimedia.org/wiki/File:Tabby_cat_with_blue_eyes-3336579.jpg
+ # commons.wikimedia.org/wiki/File:Domestic_cat_paw.jpg
+ # commons.wikimedia.org/wiki/File:A_mother_cat_of_Meitei_domestic_cat_breed_(Meitei_house_cat_variety)_suckling_her_only_little_newborn_baby_kitten_05.jpg
+ GRASS: untyped
+
+ TABBY: untyped
+
+ PAW: untyped
+
+ KITTEN: untyped
# Sample household assets. The drill is intentionally photoless so the
# asset page's add-photo affordance shows up in dev.
- ASSETS: Array[Hash[Symbol, untyped]]
+ ASSETS: Array[Asset]
# Seeds the database when it's empty, returning true when it inserted data
# and false when assets already exist. The empty check keeps repeated dev
@@ -30,8 +66,8 @@ module Domus
def self.call: (untyped app) -> untyped
# Returns the cached path to a seed photo, downloading it on first use.
- # : (Symbol) -> Pathname
- def self.fetch: (untyped key) -> untyped
+ # : (Photo) -> Pathname
+ def self.fetch: (untyped photo) -> untyped
# The XDG cache directory for downloaded seed photos.
# : () -> Pathname