refactor: standardize view model conversions on From trait

Convert single-argument from_domain() methods to impl From<T> 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.
This commit is contained in:
Jon Seager 2026-02-13 15:15:07 +00:00
parent 36e2d90ee8
commit 472e851b80
No known key found for this signature in database
16 changed files with 31 additions and 27 deletions

View file

@ -46,7 +46,7 @@ pub(crate) async fn load_bag_page(
let (bags, navigator) = crate::application::routes::support::build_page_view( let (bags, navigator) = crate::application::routes::support::build_page_view(
page, page,
request, request,
BagView::from_domain, BagView::from,
BAG_PAGE_PATH, BAG_PAGE_PATH,
BAG_FRAGMENT_PATH, BAG_FRAGMENT_PATH,
search.map(String::from), search.map(String::from),

View file

@ -133,7 +133,7 @@ pub(crate) async fn load_brew_page(
let (brews, navigator) = crate::application::routes::support::build_page_view( let (brews, navigator) = crate::application::routes::support::build_page_view(
page, page,
request, request,
BrewView::from_domain, BrewView::from,
BREW_PAGE_PATH, BREW_PAGE_PATH,
BREW_FRAGMENT_PATH, BREW_FRAGMENT_PATH,
search.map(String::from), search.map(String::from),

View file

@ -44,7 +44,7 @@ pub(crate) async fn load_cup_page(
Ok(crate::application::routes::support::build_page_view( Ok(crate::application::routes::support::build_page_view(
page, page,
request, request,
CupView::from_domain, CupView::from,
CUP_PAGE_PATH, CUP_PAGE_PATH,
CUP_FRAGMENT_PATH, CUP_FRAGMENT_PATH,
search.map(String::from), search.map(String::from),

View file

@ -46,7 +46,7 @@ pub(crate) async fn load_gear_page(
Ok(crate::application::routes::support::build_page_view( Ok(crate::application::routes::support::build_page_view(
page, page,
request, request,
GearView::from_domain, GearView::from,
GEAR_PAGE_PATH, GEAR_PAGE_PATH,
GEAR_FRAGMENT_PATH, GEAR_FRAGMENT_PATH,
search.map(String::from), search.map(String::from),

View file

@ -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 image_url = resolve_image_url(&state, EntityType::Cafe, i64::from(cafe.id)).await;
let edit_url = format!("/cafes/{}/edit", cafe.id); let edit_url = format!("/cafes/{}/edit", cafe.id);
let view = CafeDetailView::from_domain(cafe); let view = CafeDetailView::from(cafe);
let template = CafeDetailTemplate { let template = CafeDetailTemplate {
nav_active: "", nav_active: "",

View file

@ -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 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 { let template = GearDetailTemplate {
nav_active: "", nav_active: "",

View file

@ -117,19 +117,19 @@ async fn load_home_content(state: &AppState) -> Result<HomeContent, AppError> {
let recent_brews: Vec<BrewView> = recent_brews_page let recent_brews: Vec<BrewView> = recent_brews_page
.items .items
.into_iter() .into_iter()
.map(BrewView::from_domain) .map(BrewView::from)
.collect(); .collect();
let open_bags = open_bags_page let open_bags = open_bags_page
.items .items
.into_iter() .into_iter()
.map(BagView::from_domain) .map(BagView::from)
.collect(); .collect();
let recent_events = recent_events_page let recent_events = recent_events_page
.items .items
.into_iter() .into_iter()
.map(TimelineEventView::from_domain) .map(TimelineEventView::from)
.collect(); .collect();
Ok(HomeContent { Ok(HomeContent {

View file

@ -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 image_url = resolve_image_url(&state, EntityType::Roaster, i64::from(roaster.id)).await;
let edit_url = format!("/roasters/{}/edit", roaster.id); let edit_url = format!("/roasters/{}/edit", roaster.id);
let view = RoasterDetailView::from_domain(roaster); let view = RoasterDetailView::from(roaster);
let template = RoasterDetailTemplate { let template = RoasterDetailTemplate {
nav_active: "", nav_active: "",

View file

@ -128,7 +128,7 @@ async fn load_timeline_page(
fn prepare_event(event: TimelineEvent) -> TimelinePreparedEvent { fn prepare_event(event: TimelineEvent) -> TimelinePreparedEvent {
let anchor = event.occurred_at.format("%Y-%m").to_string(); let anchor = event.occurred_at.format("%Y-%m").to_string();
let heading = event.occurred_at.format("%B %Y").to_string(); let heading = event.occurred_at.format("%B %Y").to_string();
let view = TimelineEventView::from_domain(event); let view = TimelineEventView::from(event);
TimelinePreparedEvent { TimelinePreparedEvent {
anchor, anchor,

View file

@ -34,8 +34,8 @@ pub struct BagView {
pub used_percent: u8, pub used_percent: u8,
} }
impl BagView { impl From<BagWithRoast> for BagView {
pub fn from_domain(bag: BagWithRoast) -> Self { fn from(bag: BagWithRoast) -> Self {
let used_percent = used_percent(bag.bag.amount, bag.bag.remaining); let used_percent = used_percent(bag.bag.amount, bag.bag.remaining);
let (created_date, created_time) = format_datetime(bag.bag.created_at); let (created_date, created_time) = format_datetime(bag.bag.created_at);
Self { Self {

View file

@ -67,8 +67,8 @@ pub struct BrewView {
pub brew_time_raw: Option<i32>, pub brew_time_raw: Option<i32>,
} }
impl BrewView { impl From<BrewWithDetails> for BrewView {
pub fn from_domain(brew: BrewWithDetails) -> Self { fn from(brew: BrewWithDetails) -> Self {
let ratio = if brew.brew.coffee_weight > 0.0 { let ratio = if brew.brew.coffee_weight > 0.0 {
format!( format!(
"1:{:.1}", "1:{:.1}",
@ -133,7 +133,9 @@ impl BrewView {
brew_time_raw: brew.brew.brew_time, brew_time_raw: brew.brew.brew_time,
} }
} }
}
impl BrewView {
/// Build a URL to the add-brew form pre-filled with this brew's parameters. /// Build a URL to the add-brew form pre-filled with this brew's parameters.
pub fn brew_again_url(&self) -> String { pub fn brew_again_url(&self) -> String {
let mut url = format!( let mut url = format!(

View file

@ -19,8 +19,8 @@ pub struct CafeDetailView {
pub created_time: String, pub created_time: String,
} }
impl CafeDetailView { impl From<Cafe> for CafeDetailView {
pub fn from_domain(cafe: Cafe) -> Self { fn from(cafe: Cafe) -> Self {
let (created_date, created_time) = format_datetime(cafe.created_at); let (created_date, created_time) = format_datetime(cafe.created_at);
let country_flag = country_to_iso(&cafe.country) let country_flag = country_to_iso(&cafe.country)
.map(iso_to_flag_emoji) .map(iso_to_flag_emoji)

View file

@ -21,8 +21,8 @@ pub struct CupView {
pub created_time: String, pub created_time: String,
} }
impl CupView { impl From<CupWithDetails> for CupView {
pub fn from_domain(cup: CupWithDetails) -> Self { fn from(cup: CupWithDetails) -> Self {
let (created_date, created_time) = format_datetime(cup.cup.created_at); let (created_date, created_time) = format_datetime(cup.cup.created_at);
Self { Self {
id: cup.cup.id.to_string(), id: cup.cup.id.to_string(),

View file

@ -11,8 +11,8 @@ pub struct GearDetailView {
pub created_time: String, pub created_time: String,
} }
impl GearDetailView { impl From<Gear> for GearDetailView {
pub fn from_domain(gear: Gear) -> Self { fn from(gear: Gear) -> Self {
let (created_date, created_time) = format_datetime(gear.created_at); let (created_date, created_time) = format_datetime(gear.created_at);
Self { Self {
id: gear.id.to_string(), id: gear.id.to_string(),
@ -37,8 +37,8 @@ pub struct GearView {
pub created_time: String, pub created_time: String,
} }
impl GearView { impl From<Gear> for GearView {
pub fn from_domain(gear: Gear) -> Self { fn from(gear: Gear) -> Self {
let (created_date, created_time) = format_datetime(gear.created_at); let (created_date, created_time) = format_datetime(gear.created_at);
Self { Self {
id: gear.id.to_string(), id: gear.id.to_string(),

View file

@ -17,8 +17,8 @@ pub struct RoasterDetailView {
pub created_time: String, pub created_time: String,
} }
impl RoasterDetailView { impl From<Roaster> for RoasterDetailView {
pub fn from_domain(roaster: Roaster) -> Self { fn from(roaster: Roaster) -> Self {
let country_flag = country_to_iso(&roaster.country) let country_flag = country_to_iso(&roaster.country)
.map(iso_to_flag_emoji) .map(iso_to_flag_emoji)
.unwrap_or_default(); .unwrap_or_default();

View file

@ -69,8 +69,8 @@ pub struct TimelineMonthView {
pub events: Vec<TimelineEventView>, pub events: Vec<TimelineEventView>,
} }
impl TimelineEventView { impl From<TimelineEvent> for TimelineEventView {
pub fn from_domain(event: TimelineEvent) -> Self { fn from(event: TimelineEvent) -> Self {
let TimelineEvent { let TimelineEvent {
id, id,
entity_type, entity_type,
@ -161,7 +161,9 @@ impl TimelineEventView {
brew_data: brew_data_view, brew_data: brew_data_view,
} }
} }
}
impl TimelineEventView {
fn build_subtitle(entity_type: &str, details: &[TimelineEventDetailView]) -> Option<String> { fn build_subtitle(entity_type: &str, details: &[TimelineEventDetailView]) -> Option<String> {
let find_value = |label: &str| { let find_value = |label: &str| {
details details