Skip to main content

tablo_core/column/
computed.rs

1//! [`ComputedColumn`]: a display-only column projecting any text off the row.
2
3use std::sync::Arc;
4
5use derive_where::derive_where;
6use topcoat::context::Cx;
7
8use super::{Column, ColumnToggle, ColumnWidth, Includes};
9use crate::display::Alignment;
10
11/// A display-only column rendering any text from the row, with no query predicate.
12///
13/// ```rust
14/// # #[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
15/// # struct Post { #[key] #[auto] id: uuid::Uuid, body: String }
16/// tablo_core::ComputedColumn::new("Words", |p: &Post| {
17///     p.body.split_whitespace().count().to_string()
18/// });
19/// ```
20///
21/// A closure that reads a relation declares it with [`include_unscoped`](Self::include_unscoped),
22/// so the page loads it. One relation's record or count is a
23/// [`RelationColumn`](super::RelationColumn) or a [`CountColumn`](super::CountColumn), which
24/// declare their own and show only what the related resource shows the user.
25///
26/// It maps to no column, so it neither searches nor sorts.
27#[derive_where(Clone, Debug)]
28pub struct ComputedColumn<M> {
29    name: String,
30    label: crate::Text,
31    #[derive_where(skip(Debug))]
32    project: Arc<dyn Fn(&M) -> String + Send + Sync>,
33    width: ColumnWidth,
34    alignment: Alignment,
35    toggle: ColumnToggle,
36    /// Relations the projection reads.
37    includes: Includes<M>,
38    /// The fields the projection reads, when declared.
39    reads: Option<crate::lens::Reads<M>>,
40}
41
42impl<M> ComputedColumn<M>
43where
44    M: toasty::schema::Model,
45{
46    /// Declare a column headed `label` rendering `project(row)`, named after the label: its
47    /// plain text in lowercase, or its message's id.
48    pub fn new(
49        label: impl Into<crate::Text>,
50        project: impl Fn(&M) -> String + Send + Sync + 'static,
51    ) -> Self {
52        let label = label.into();
53        Self {
54            name: label.key(),
55            label,
56            project: Arc::new(project),
57            width: ColumnWidth::Auto,
58            alignment: Alignment::Start,
59            toggle: ColumnToggle::Fixed,
60            includes: Includes::new(),
61            reads: None,
62        }
63    }
64
65    /// Declare a relation the projection reads, so the table loads it. The projection reads
66    /// whatever the include loads, under no resource's query or policy, as an
67    /// [`unscoped`](super::RelationColumn::unscoped) relation column does: another tenant's record,
68    /// or one the user may not view, reaches it.
69    pub fn include_unscoped<T>(mut self, relation: impl Into<toasty::stmt::Include<M, T>>) -> Self {
70        self.includes = self.includes.with(relation);
71        self
72    }
73
74    /// Declare the fields the projection reads, which a resource restricting a field with
75    /// [`field_access`](crate::ResourceDef::field_access) needs: the column shows only to a user
76    /// who may view every one.
77    pub fn reading(mut self, reads: crate::lens::Reads<M>) -> Self {
78        self.reads = Some(reads);
79        self
80    }
81
82    /// Declare this column's width.
83    pub fn width(mut self, width: ColumnWidth) -> Self {
84        self.width = width;
85        self
86    }
87
88    /// Align the header and the cells.
89    pub fn align(mut self, alignment: Alignment) -> Self {
90        self.alignment = alignment;
91        self
92    }
93
94    toggle_builders!(toggle);
95}
96
97impl<M> Column<M> for ComputedColumn<M>
98where
99    M: toasty::schema::Model + Send + Sync + 'static,
100{
101    fn name(&self) -> &str {
102        &self.name
103    }
104
105    fn label(&self) -> crate::Text {
106        self.label.clone()
107    }
108
109    fn text(&self, _cx: &Cx, row: &M) -> String {
110        (self.project)(row)
111    }
112
113    fn column_width(&self) -> ColumnWidth {
114        self.width
115    }
116
117    fn toggle(&self) -> ColumnToggle {
118        self.toggle
119    }
120
121    fn alignment(&self) -> Alignment {
122        self.alignment
123    }
124
125    fn includes(&self) -> Includes<M> {
126        self.includes.clone()
127    }
128
129    fn reads(&self) -> Option<crate::lens::Reads<M>> {
130        self.reads.clone()
131    }
132}