sea_orm/entity/
active_model_ex.rs

1use super::compound::{HasMany, HasOne};
2use crate::{ActiveModelTrait, DbErr, EntityTrait, ModelTrait, TryIntoModel};
3use core::ops::{Index, IndexMut};
4
5/// Container for belongs_to or has_one relation
6#[derive(Debug, Default, Clone)]
7pub enum HasOneModel<E: EntityTrait> {
8    /// Unspecified value, do nothing
9    #[default]
10    NotSet,
11    /// Specify the value for the has one relation
12    Set(Box<E::ActiveModelEx>),
13}
14
15/// Container for 1-N or M-N related Models
16#[derive(Debug, Default, Clone)]
17pub enum HasManyModel<E: EntityTrait> {
18    /// Unspecified value, do nothing
19    #[default]
20    NotSet,
21    /// Replace all items with this value set; delete leftovers
22    Replace(Vec<E::ActiveModelEx>),
23    /// Add new items to this has many relation; do not delete
24    Append(Vec<E::ActiveModelEx>),
25}
26
27/// Action to perform on ActiveModel
28#[derive(Debug, Copy, Clone, PartialEq, Eq)]
29pub enum ActiveModelAction {
30    /// Insert
31    Insert,
32    /// Update
33    Update,
34    /// Insert the model if primary key is `NotSet`, update otherwise.
35    /// Only works if the entity has auto increment primary key.
36    Save,
37}
38
39impl<E> HasOneModel<E>
40where
41    E: EntityTrait,
42{
43    /// Construct a `HasOneModel::Set`
44    pub fn set<AM: Into<E::ActiveModelEx>>(model: AM) -> Self {
45        Self::Set(Box::new(model.into()))
46    }
47
48    /// Replace the inner Model
49    pub fn replace<AM: Into<E::ActiveModelEx>>(&mut self, model: AM) {
50        *self = Self::Set(Box::new(model.into()));
51    }
52
53    /// Take ownership of this model, leaving `NotSet` in place
54    pub fn take(&mut self) -> Option<E::ActiveModelEx> {
55        match std::mem::take(self) {
56            Self::Set(model) => Some(*model),
57            _ => None,
58        }
59    }
60
61    /// Get a reference, if set
62    pub fn as_ref(&self) -> Option<&E::ActiveModelEx> {
63        match self {
64            Self::Set(model) => Some(model),
65            _ => None,
66        }
67    }
68
69    /// Get a mutable reference, if set
70    #[allow(clippy::should_implement_trait)]
71    pub fn as_mut(&mut self) -> Option<&mut E::ActiveModelEx> {
72        match self {
73            Self::Set(model) => Some(model),
74            _ => None,
75        }
76    }
77
78    /// Return true if there is a model
79    pub fn is_set(&self) -> bool {
80        matches!(self, Self::Set(_))
81    }
82
83    /// Return true if self is NotSet
84    pub fn is_not_set(&self) -> bool {
85        matches!(self, Self::NotSet)
86    }
87
88    /// Return true if self is NotSet
89    pub fn is_none(&self) -> bool {
90        matches!(self, Self::NotSet)
91    }
92
93    /// Return true if the containing model is set and changed
94    pub fn is_changed(&self) -> bool {
95        match self {
96            Self::Set(model) => model.is_changed(),
97            _ => false,
98        }
99    }
100
101    /// Convert into an `Option<ActiveModelEx>`
102    pub fn into_option(self) -> Option<E::ActiveModelEx> {
103        match self {
104            Self::Set(model) => Some(*model),
105            Self::NotSet => None,
106        }
107    }
108
109    /// For type inference purpose
110    #[doc(hidden)]
111    pub fn empty_slice(&self) -> &[E::ActiveModelEx] {
112        &[]
113    }
114
115    /// Convert this back to a `ModelEx` container
116    pub fn try_into_model(self) -> Result<HasOne<E>, DbErr>
117    where
118        E::ActiveModelEx: TryIntoModel<E::ModelEx>,
119    {
120        Ok(match self {
121            Self::Set(model) => HasOne::Loaded(Box::new((*model).try_into_model()?)),
122            Self::NotSet => HasOne::Unloaded,
123        })
124    }
125}
126
127impl<E> PartialEq for HasOneModel<E>
128where
129    E: EntityTrait,
130    E::ActiveModelEx: PartialEq,
131{
132    fn eq(&self, other: &Self) -> bool {
133        match (self, other) {
134            (HasOneModel::NotSet, HasOneModel::NotSet) => true,
135            (HasOneModel::Set(a), HasOneModel::Set(b)) => a == b,
136            _ => false,
137        }
138    }
139}
140
141impl<E> PartialEq<Option<E::ActiveModelEx>> for HasOneModel<E>
142where
143    E: EntityTrait,
144    E::ActiveModelEx: PartialEq,
145{
146    fn eq(&self, other: &Option<E::ActiveModelEx>) -> bool {
147        match (self, other) {
148            (HasOneModel::NotSet, None) => true,
149            (HasOneModel::Set(a), Some(b)) => a.as_ref() == b,
150            _ => false,
151        }
152    }
153}
154
155impl<E> Eq for HasOneModel<E>
156where
157    E: EntityTrait,
158    E::ActiveModelEx: Eq,
159{
160}
161
162impl<E> HasManyModel<E>
163where
164    E: EntityTrait,
165{
166    /// Take ownership of the models, leaving `NotSet` in place
167    pub fn take(&mut self) -> Self {
168        std::mem::take(self)
169    }
170
171    /// Borrow models as slice
172    pub fn as_slice(&self) -> &[E::ActiveModelEx] {
173        match self {
174            Self::Replace(models) | Self::Append(models) => models,
175            Self::NotSet => &[],
176        }
177    }
178
179    /// Get a mutable vec. If self is `NotSet`, convert to append.
180    pub fn as_mut_vec(&mut self) -> &mut Vec<E::ActiveModelEx> {
181        match self {
182            Self::Replace(models) | Self::Append(models) => models,
183            Self::NotSet => {
184                *self = Self::Append(vec![]);
185                self.as_mut_vec()
186            }
187        }
188    }
189
190    /// Consume self as vector
191    pub fn into_vec(self) -> Vec<E::ActiveModelEx> {
192        match self {
193            Self::Replace(models) | Self::Append(models) => models,
194            Self::NotSet => vec![],
195        }
196    }
197
198    /// Returns an empty container of self
199    pub fn empty_holder(&self) -> Self {
200        match self {
201            Self::Replace(_) => Self::Replace(vec![]),
202            Self::Append(_) => Self::Append(vec![]),
203            Self::NotSet => Self::NotSet,
204        }
205    }
206
207    /// Push an item to self
208    pub fn push<AM: Into<E::ActiveModelEx>>(&mut self, model: AM) -> &mut Self {
209        let model = model.into();
210        match self {
211            Self::Replace(models) | Self::Append(models) => models.push(model),
212            Self::NotSet => {
213                *self = Self::Append(vec![model]);
214            }
215        }
216
217        self
218    }
219
220    /// Push an item to self, but convert Replace to Append
221    pub fn append<AM: Into<E::ActiveModelEx>>(&mut self, model: AM) -> &mut Self {
222        self.convert_to_append().push(model)
223    }
224
225    /// Replace all items in this set
226    pub fn replace_all<I>(&mut self, models: I) -> &mut Self
227    where
228        I: IntoIterator<Item = E::ActiveModelEx>,
229    {
230        *self = Self::Replace(models.into_iter().collect());
231        self
232    }
233
234    /// Convert self to Append, if set
235    pub fn convert_to_append(&mut self) -> &mut Self {
236        match self.take() {
237            Self::Replace(models) | Self::Append(models) => {
238                *self = Self::Append(models);
239            }
240            Self::NotSet => {
241                *self = Self::NotSet;
242            }
243        }
244
245        self
246    }
247
248    /// Reset self to NotSet
249    pub fn not_set(&mut self) {
250        *self = Self::NotSet;
251    }
252
253    /// If self is `Replace`
254    pub fn is_replace(&self) -> bool {
255        matches!(self, Self::Replace(_))
256    }
257
258    /// If self is `Append`
259    pub fn is_append(&self) -> bool {
260        matches!(self, Self::Append(_))
261    }
262
263    /// Return true if self is `Replace` or any containing model is changed
264    pub fn is_changed(&self) -> bool {
265        match self {
266            Self::Replace(_) => true,
267            Self::Append(models) => models.iter().any(|model| model.is_changed()),
268            Self::NotSet => false,
269        }
270    }
271
272    /// Find within the models by primary key, return true if found
273    pub fn find(&self, model: &E::Model) -> bool {
274        let pk = model.get_primary_key_value();
275
276        for item in self.as_slice() {
277            if let Some(pk_item) = item.get_primary_key_value() {
278                if pk_item == pk {
279                    return true;
280                }
281            }
282        }
283
284        false
285    }
286
287    /// Convert this back to a `ModelEx` container
288    pub fn try_into_model(self) -> Result<HasMany<E>, DbErr>
289    where
290        E::ActiveModelEx: TryIntoModel<E::ModelEx>,
291    {
292        Ok(match self {
293            Self::Replace(models) | Self::Append(models) => HasMany::Loaded(
294                models
295                    .into_iter()
296                    .map(|t| t.try_into_model())
297                    .collect::<Result<Vec<_>, DbErr>>()?,
298            ),
299            Self::NotSet => HasMany::Unloaded,
300        })
301    }
302}
303
304impl<E> PartialEq for HasManyModel<E>
305where
306    E: EntityTrait,
307    E::ActiveModelEx: PartialEq,
308{
309    fn eq(&self, other: &Self) -> bool {
310        match (self, other) {
311            (HasManyModel::NotSet, HasManyModel::NotSet) => true,
312            (HasManyModel::Replace(a), HasManyModel::Replace(b)) => a == b,
313            (HasManyModel::Append(a), HasManyModel::Append(b)) => a == b,
314            _ => false,
315        }
316    }
317}
318
319impl<E> Eq for HasManyModel<E>
320where
321    E: EntityTrait,
322    E::ActiveModelEx: Eq,
323{
324}
325
326impl<E: EntityTrait> From<HasManyModel<E>> for Option<Vec<E::ActiveModelEx>> {
327    fn from(value: HasManyModel<E>) -> Self {
328        match value {
329            HasManyModel::NotSet => None,
330            HasManyModel::Replace(models) | HasManyModel::Append(models) => Some(models),
331        }
332    }
333}
334
335impl<E: EntityTrait> Index<usize> for HasManyModel<E> {
336    type Output = E::ActiveModelEx;
337
338    fn index(&self, index: usize) -> &Self::Output {
339        match self {
340            HasManyModel::NotSet => {
341                panic!("index out of bounds: the HasManyModel is NotSet (index: {index})")
342            }
343            HasManyModel::Replace(models) | HasManyModel::Append(models) => models.index(index),
344        }
345    }
346}
347
348impl<E: EntityTrait> IndexMut<usize> for HasManyModel<E> {
349    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
350        match self {
351            HasManyModel::NotSet => {
352                panic!("index out of bounds: the HasManyModel is NotSet (index: {index})")
353            }
354            HasManyModel::Replace(models) | HasManyModel::Append(models) => models.index_mut(index),
355        }
356    }
357}
358
359impl<E: EntityTrait> IntoIterator for HasManyModel<E> {
360    type Item = E::ActiveModelEx;
361    type IntoIter = std::vec::IntoIter<E::ActiveModelEx>;
362
363    fn into_iter(self) -> Self::IntoIter {
364        match self {
365            HasManyModel::Replace(models) | HasManyModel::Append(models) => models.into_iter(),
366            HasManyModel::NotSet => Vec::new().into_iter(),
367        }
368    }
369}
370
371/// Converts from a set of models into `Append`, which performs non-destructive action
372impl<E: EntityTrait> From<Vec<E::ActiveModelEx>> for HasManyModel<E> {
373    fn from(value: Vec<E::ActiveModelEx>) -> Self {
374        HasManyModel::Append(value)
375    }
376}