Skip to main content

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