From 472e851b8076abf19b5fbab31c350d144435a70b Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Fri, 13 Feb 2026 15:15:07 +0000 Subject: [PATCH] refactor: standardize view model conversions on From trait Convert single-argument from_domain() methods to impl From for consistency with the existing CafeView, RoasterView, and option view patterns. Affected: BagView, BrewView, CupView, GearView, GearDetailView, CafeDetailView, RoasterDetailView, TimelineEventView. RoastView retains from_domain() since it takes extra parameters. --- src/application/routes/api/coffee/bags.rs | 2 +- src/application/routes/api/coffee/brews.rs | 2 +- src/application/routes/api/coffee/cups.rs | 2 +- src/application/routes/api/coffee/gear.rs | 2 +- src/application/routes/app/cafes.rs | 2 +- src/application/routes/app/gear.rs | 2 +- src/application/routes/app/home.rs | 6 +++--- src/application/routes/app/roasters.rs | 2 +- src/application/routes/app/timeline.rs | 2 +- src/presentation/web/views/bags.rs | 4 ++-- src/presentation/web/views/brews.rs | 6 ++++-- src/presentation/web/views/cafes.rs | 4 ++-- src/presentation/web/views/cups.rs | 4 ++-- src/presentation/web/views/gear.rs | 8 ++++---- src/presentation/web/views/roasters.rs | 4 ++-- src/presentation/web/views/timeline.rs | 6 ++++-- 16 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/application/routes/api/coffee/bags.rs b/src/application/routes/api/coffee/bags.rs index 4e8b0de..e85c900 100644 --- a/src/application/routes/api/coffee/bags.rs +++ b/src/application/routes/api/coffee/bags.rs @@ -46,7 +46,7 @@ pub(crate) async fn load_bag_page( let (bags, navigator) = crate::application::routes::support::build_page_view( page, request, - BagView::from_domain, + BagView::from, BAG_PAGE_PATH, BAG_FRAGMENT_PATH, search.map(String::from), diff --git a/src/application/routes/api/coffee/brews.rs b/src/application/routes/api/coffee/brews.rs index 5bf0fc1..af09b26 100644 --- a/src/application/routes/api/coffee/brews.rs +++ b/src/application/routes/api/coffee/brews.rs @@ -133,7 +133,7 @@ pub(crate) async fn load_brew_page( let (brews, navigator) = crate::application::routes::support::build_page_view( page, request, - BrewView::from_domain, + BrewView::from, BREW_PAGE_PATH, BREW_FRAGMENT_PATH, search.map(String::from), diff --git a/src/application/routes/api/coffee/cups.rs b/src/application/routes/api/coffee/cups.rs index 806f6a5..a8e8e0c 100644 --- a/src/application/routes/api/coffee/cups.rs +++ b/src/application/routes/api/coffee/cups.rs @@ -44,7 +44,7 @@ pub(crate) async fn load_cup_page( Ok(crate::application::routes::support::build_page_view( page, request, - CupView::from_domain, + CupView::from, CUP_PAGE_PATH, CUP_FRAGMENT_PATH, search.map(String::from), diff --git a/src/application/routes/api/coffee/gear.rs b/src/application/routes/api/coffee/gear.rs index ba629ae..0c38ce3 100644 --- a/src/application/routes/api/coffee/gear.rs +++ b/src/application/routes/api/coffee/gear.rs @@ -46,7 +46,7 @@ pub(crate) async fn load_gear_page( Ok(crate::application::routes::support::build_page_view( page, request, - GearView::from_domain, + GearView::from, GEAR_PAGE_PATH, GEAR_FRAGMENT_PATH, search.map(String::from), diff --git a/src/application/routes/app/cafes.rs b/src/application/routes/app/cafes.rs index 1f31f26..e20c3e9 100644 --- a/src/application/routes/app/cafes.rs +++ b/src/application/routes/app/cafes.rs @@ -30,7 +30,7 @@ pub(crate) async fn cafe_detail_page( let image_url = resolve_image_url(&state, EntityType::Cafe, i64::from(cafe.id)).await; let edit_url = format!("/cafes/{}/edit", cafe.id); - let view = CafeDetailView::from_domain(cafe); + let view = CafeDetailView::from(cafe); let template = CafeDetailTemplate { nav_active: "", diff --git a/src/application/routes/app/gear.rs b/src/application/routes/app/gear.rs index 73ac477..9953d41 100644 --- a/src/application/routes/app/gear.rs +++ b/src/application/routes/app/gear.rs @@ -29,7 +29,7 @@ pub(crate) async fn gear_detail_page( let image_url = resolve_image_url(&state, EntityType::Gear, i64::from(id)).await; - let view = GearDetailView::from_domain(gear); + let view = GearDetailView::from(gear); let template = GearDetailTemplate { nav_active: "", diff --git a/src/application/routes/app/home.rs b/src/application/routes/app/home.rs index a4aa858..9877ece 100644 --- a/src/application/routes/app/home.rs +++ b/src/application/routes/app/home.rs @@ -117,19 +117,19 @@ async fn load_home_content(state: &AppState) -> Result { let recent_brews: Vec = recent_brews_page .items .into_iter() - .map(BrewView::from_domain) + .map(BrewView::from) .collect(); let open_bags = open_bags_page .items .into_iter() - .map(BagView::from_domain) + .map(BagView::from) .collect(); let recent_events = recent_events_page .items .into_iter() - .map(TimelineEventView::from_domain) + .map(TimelineEventView::from) .collect(); Ok(HomeContent { diff --git a/src/application/routes/app/roasters.rs b/src/application/routes/app/roasters.rs index 0a4b3da..6ee573f 100644 --- a/src/application/routes/app/roasters.rs +++ b/src/application/routes/app/roasters.rs @@ -30,7 +30,7 @@ pub(crate) async fn roaster_detail_page( let image_url = resolve_image_url(&state, EntityType::Roaster, i64::from(roaster.id)).await; let edit_url = format!("/roasters/{}/edit", roaster.id); - let view = RoasterDetailView::from_domain(roaster); + let view = RoasterDetailView::from(roaster); let template = RoasterDetailTemplate { nav_active: "", diff --git a/src/application/routes/app/timeline.rs b/src/application/routes/app/timeline.rs index 6aa0f4d..5bde1bb 100644 --- a/src/application/routes/app/timeline.rs +++ b/src/application/routes/app/timeline.rs @@ -128,7 +128,7 @@ async fn load_timeline_page( fn prepare_event(event: TimelineEvent) -> TimelinePreparedEvent { let anchor = event.occurred_at.format("%Y-%m").to_string(); let heading = event.occurred_at.format("%B %Y").to_string(); - let view = TimelineEventView::from_domain(event); + let view = TimelineEventView::from(event); TimelinePreparedEvent { anchor, diff --git a/src/presentation/web/views/bags.rs b/src/presentation/web/views/bags.rs index e9bd0e1..1ae3534 100644 --- a/src/presentation/web/views/bags.rs +++ b/src/presentation/web/views/bags.rs @@ -34,8 +34,8 @@ pub struct BagView { pub used_percent: u8, } -impl BagView { - pub fn from_domain(bag: BagWithRoast) -> Self { +impl From for BagView { + fn from(bag: BagWithRoast) -> Self { let used_percent = used_percent(bag.bag.amount, bag.bag.remaining); let (created_date, created_time) = format_datetime(bag.bag.created_at); Self { diff --git a/src/presentation/web/views/brews.rs b/src/presentation/web/views/brews.rs index 7345b31..131cb70 100644 --- a/src/presentation/web/views/brews.rs +++ b/src/presentation/web/views/brews.rs @@ -67,8 +67,8 @@ pub struct BrewView { pub brew_time_raw: Option, } -impl BrewView { - pub fn from_domain(brew: BrewWithDetails) -> Self { +impl From for BrewView { + fn from(brew: BrewWithDetails) -> Self { let ratio = if brew.brew.coffee_weight > 0.0 { format!( "1:{:.1}", @@ -133,7 +133,9 @@ impl BrewView { brew_time_raw: brew.brew.brew_time, } } +} +impl BrewView { /// Build a URL to the add-brew form pre-filled with this brew's parameters. pub fn brew_again_url(&self) -> String { let mut url = format!( diff --git a/src/presentation/web/views/cafes.rs b/src/presentation/web/views/cafes.rs index a1bee55..a6bed44 100644 --- a/src/presentation/web/views/cafes.rs +++ b/src/presentation/web/views/cafes.rs @@ -19,8 +19,8 @@ pub struct CafeDetailView { pub created_time: String, } -impl CafeDetailView { - pub fn from_domain(cafe: Cafe) -> Self { +impl From for CafeDetailView { + fn from(cafe: Cafe) -> Self { let (created_date, created_time) = format_datetime(cafe.created_at); let country_flag = country_to_iso(&cafe.country) .map(iso_to_flag_emoji) diff --git a/src/presentation/web/views/cups.rs b/src/presentation/web/views/cups.rs index 05b9e71..986a608 100644 --- a/src/presentation/web/views/cups.rs +++ b/src/presentation/web/views/cups.rs @@ -21,8 +21,8 @@ pub struct CupView { pub created_time: String, } -impl CupView { - pub fn from_domain(cup: CupWithDetails) -> Self { +impl From for CupView { + fn from(cup: CupWithDetails) -> Self { let (created_date, created_time) = format_datetime(cup.cup.created_at); Self { id: cup.cup.id.to_string(), diff --git a/src/presentation/web/views/gear.rs b/src/presentation/web/views/gear.rs index 4cd650b..c9bc54c 100644 --- a/src/presentation/web/views/gear.rs +++ b/src/presentation/web/views/gear.rs @@ -11,8 +11,8 @@ pub struct GearDetailView { pub created_time: String, } -impl GearDetailView { - pub fn from_domain(gear: Gear) -> Self { +impl From for GearDetailView { + fn from(gear: Gear) -> Self { let (created_date, created_time) = format_datetime(gear.created_at); Self { id: gear.id.to_string(), @@ -37,8 +37,8 @@ pub struct GearView { pub created_time: String, } -impl GearView { - pub fn from_domain(gear: Gear) -> Self { +impl From for GearView { + fn from(gear: Gear) -> Self { let (created_date, created_time) = format_datetime(gear.created_at); Self { id: gear.id.to_string(), diff --git a/src/presentation/web/views/roasters.rs b/src/presentation/web/views/roasters.rs index 87af03d..23fc3ef 100644 --- a/src/presentation/web/views/roasters.rs +++ b/src/presentation/web/views/roasters.rs @@ -17,8 +17,8 @@ pub struct RoasterDetailView { pub created_time: String, } -impl RoasterDetailView { - pub fn from_domain(roaster: Roaster) -> Self { +impl From for RoasterDetailView { + fn from(roaster: Roaster) -> Self { let country_flag = country_to_iso(&roaster.country) .map(iso_to_flag_emoji) .unwrap_or_default(); diff --git a/src/presentation/web/views/timeline.rs b/src/presentation/web/views/timeline.rs index c56d4d8..52eb22f 100644 --- a/src/presentation/web/views/timeline.rs +++ b/src/presentation/web/views/timeline.rs @@ -69,8 +69,8 @@ pub struct TimelineMonthView { pub events: Vec, } -impl TimelineEventView { - pub fn from_domain(event: TimelineEvent) -> Self { +impl From for TimelineEventView { + fn from(event: TimelineEvent) -> Self { let TimelineEvent { id, entity_type, @@ -161,7 +161,9 @@ impl TimelineEventView { brew_data: brew_data_view, } } +} +impl TimelineEventView { fn build_subtitle(entity_type: &str, details: &[TimelineEventDetailView]) -> Option { let find_value = |label: &str| { details