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(
page,
request,
BagView::from_domain,
BagView::from,
BAG_PAGE_PATH,
BAG_FRAGMENT_PATH,
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(
page,
request,
BrewView::from_domain,
BrewView::from,
BREW_PAGE_PATH,
BREW_FRAGMENT_PATH,
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(
page,
request,
CupView::from_domain,
CupView::from,
CUP_PAGE_PATH,
CUP_FRAGMENT_PATH,
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(
page,
request,
GearView::from_domain,
GearView::from,
GEAR_PAGE_PATH,
GEAR_FRAGMENT_PATH,
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 edit_url = format!("/cafes/{}/edit", cafe.id);
let view = CafeDetailView::from_domain(cafe);
let view = CafeDetailView::from(cafe);
let template = CafeDetailTemplate {
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 view = GearDetailView::from_domain(gear);
let view = GearDetailView::from(gear);
let template = GearDetailTemplate {
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
.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 {

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 edit_url = format!("/roasters/{}/edit", roaster.id);
let view = RoasterDetailView::from_domain(roaster);
let view = RoasterDetailView::from(roaster);
let template = RoasterDetailTemplate {
nav_active: "",

View file

@ -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,

View file

@ -34,8 +34,8 @@ pub struct BagView {
pub used_percent: u8,
}
impl BagView {
pub fn from_domain(bag: BagWithRoast) -> Self {
impl From<BagWithRoast> 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 {

View file

@ -67,8 +67,8 @@ pub struct BrewView {
pub brew_time_raw: Option<i32>,
}
impl BrewView {
pub fn from_domain(brew: BrewWithDetails) -> Self {
impl From<BrewWithDetails> 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!(

View file

@ -19,8 +19,8 @@ pub struct CafeDetailView {
pub created_time: String,
}
impl CafeDetailView {
pub fn from_domain(cafe: Cafe) -> Self {
impl From<Cafe> 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)

View file

@ -21,8 +21,8 @@ pub struct CupView {
pub created_time: String,
}
impl CupView {
pub fn from_domain(cup: CupWithDetails) -> Self {
impl From<CupWithDetails> 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(),

View file

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

View file

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

View file

@ -69,8 +69,8 @@ pub struct TimelineMonthView {
pub events: Vec<TimelineEventView>,
}
impl TimelineEventView {
pub fn from_domain(event: TimelineEvent) -> Self {
impl From<TimelineEvent> 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<String> {
let find_value = |label: &str| {
details