From 5eaa58fe2adba7290efb0a142a1b2ccce5839f22 Mon Sep 17 00:00:00 2001 From: greenhandatsjtu Date: Mon, 4 Jul 2022 09:56:23 +0800 Subject: [PATCH 1/4] feat: enable convert from ActiveModel to Model --- sea-orm-macros/src/derives/active_model.rs | 57 ++++++++++++++++++++-- src/entity/model.rs | 18 +++++++ src/lib.rs | 8 +-- 3 files changed, 76 insertions(+), 7 deletions(-) diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 635897dcb..73aa4e2bb 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -6,7 +6,8 @@ use syn::{punctuated::Punctuated, token::Comma, Data, DataStruct, Field, Fields, /// Method to derive an [ActiveModel](sea_orm::ActiveModel) pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result { - let fields = match data { + // including ignored fields + let all_fields = match data { Data::Struct(DataStruct { fields: Fields::Named(named), .. @@ -17,8 +18,15 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = all_fields + .clone() + .into_iter() + .map(|Field { ident, .. }| format_ident!("{}", ident.unwrap().to_string())) + .collect(); let field: Vec = fields .clone() @@ -63,6 +71,27 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = fields.into_iter().map(|Field { ty, .. }| ty).collect(); + let ignore_attr: Vec = all_fields + .clone() + .map(|field| !field_not_ignored(&field)) + .collect(); + + let field_value: Vec = all_field + .iter() + .zip(ignore_attr) + .map(|(field, ignore)| { + if ignore { + quote! { + Default::default() + } + } else { + quote! { + a.#field.into_value().unwrap().unwrap() + } + } + }) + .collect(); + Ok(quote!( #[derive(Clone, Debug, PartialEq)] pub struct ActiveModel { @@ -92,6 +121,28 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result for ::Model { + type Error = DbErr; + fn try_from(a: ActiveModel) -> Result { + #(if matches!(a.#field, sea_orm::ActiveValue::NotSet) { + return Err(DbErr::Custom(format!("field {} is NotSet", stringify!(#field)))); + })* + Ok( + Self { + #(#all_field: #field_value),* + } + ) + } + } + + #[automatically_derived] + impl sea_orm::TryIntoModel<::Model> for ActiveModel { + fn try_into_model(self) -> Result<::Model, DbErr> { + self.try_into() + } + } + #[automatically_derived] impl sea_orm::ActiveModelTrait for ActiveModel { type Entity = Entity; diff --git a/src/entity/model.rs b/src/entity/model.rs index 34520dc3d..3869ec52f 100644 --- a/src/entity/model.rs +++ b/src/entity/model.rs @@ -115,3 +115,21 @@ pub trait FromQueryResult: Sized { SelectorRaw::>::from_statement(stmt) } } + +/// A Trait for any type that can be converted into an Model +pub trait TryIntoModel +where + M: ModelTrait, +{ + /// Method to call to perform the conversion + fn try_into_model(self) -> Result; +} + +impl TryIntoModel for M +where + M: ModelTrait, +{ + fn try_into_model(self) -> Result { + Ok(self) + } +} diff --git a/src/lib.rs b/src/lib.rs index 36b392c6c..b0604a2cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -261,9 +261,9 @@ //! 1. [Change Log](https://github.com/SeaQL/sea-orm/tree/master/CHANGELOG.md) //! //! ## Who's using SeaORM? -//! +//! //! The following products are powered by SeaORM: -//! +//! //! //! //! @@ -273,9 +273,9 @@ //! //! //!
-//! +//! //! SeaORM is the foundation of [StarfishQL](https://github.com/SeaQL/starfish-ql), an experimental graph database and query engine developed by SeaQL. -//! +//! //! For more projects, see [Built with SeaORM](https://github.com/SeaQL/sea-orm/blob/master/COMMUNITY.md#built-with-seaorm). //! //! ## License From 30b72cb16bbb85030abcf0a5a6a99780f1a75e67 Mon Sep 17 00:00:00 2001 From: greenhandatsjtu Date: Mon, 22 Aug 2022 16:19:40 +0800 Subject: [PATCH 2/4] feat: add tests for converting from ActiveModel to Model --- src/entity/active_model.rs | 238 ++++++++++++++++++++++++++++--------- 1 file changed, 184 insertions(+), 54 deletions(-) diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 0fd54b713..266f907b6 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -37,8 +37,8 @@ pub use ActiveValue::NotSet; /// ``` #[derive(Clone, Debug)] pub enum ActiveValue -where - V: Into, + where + V: Into, { /// A defined [Value] actively being set Set(V), @@ -51,21 +51,21 @@ where /// Defines a set operation on an [ActiveValue] #[allow(non_snake_case)] pub fn Set(v: V) -> ActiveValue -where - V: Into, + where + V: Into, { ActiveValue::set(v) } /// Defines an not set operation on an [ActiveValue] #[deprecated( - since = "0.5.0", - note = "Please use [`ActiveValue::NotSet`] or [`NotSet`]" +since = "0.5.0", +note = "Please use [`ActiveValue::NotSet`] or [`NotSet`]" )] #[allow(non_snake_case)] pub fn Unset(_: Option) -> ActiveValue -where - V: Into, + where + V: Into, { ActiveValue::not_set() } @@ -73,8 +73,8 @@ where /// Defines an unchanged operation on an [ActiveValue] #[allow(non_snake_case)] pub fn Unchanged(value: V) -> ActiveValue -where - V: Into, + where + V: Into, { ActiveValue::unchanged(value) } @@ -273,10 +273,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// # } /// ``` async fn insert<'a, C>(self, db: &'a C) -> Result<::Model, DbErr> - where - ::Model: IntoActiveModel, - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + ::Model: IntoActiveModel, + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let am = ActiveModelBehavior::before_save(self, true)?; let model = ::insert(am) @@ -395,10 +395,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// # } /// ``` async fn update<'a, C>(self, db: &'a C) -> Result<::Model, DbErr> - where - ::Model: IntoActiveModel, - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + ::Model: IntoActiveModel, + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let am = ActiveModelBehavior::before_save(self, false)?; let model: ::Model = Self::Entity::update(am).exec(db).await?; @@ -408,10 +408,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// Insert the model if primary key is `NotSet`, update otherwise. /// Only works if the entity has auto increment primary key. async fn save<'a, C>(self, db: &'a C) -> Result - where - ::Model: IntoActiveModel, - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + ::Model: IntoActiveModel, + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let mut is_update = true; for key in ::PrimaryKey::iter() { @@ -473,9 +473,9 @@ pub trait ActiveModelTrait: Clone + Debug { /// # } /// ``` async fn delete<'a, C>(self, db: &'a C) -> Result - where - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let am = ActiveModelBehavior::before_delete(self)?; let am_clone = am.clone(); @@ -489,9 +489,9 @@ pub trait ActiveModelTrait: Clone + Debug { /// Note that this method will not alter the primary key values in ActiveModel. #[cfg(feature = "with-json")] fn set_from_json(&mut self, json: serde_json::Value) -> Result<(), DbErr> - where - <::Entity as EntityTrait>::Model: IntoActiveModel, - for<'de> <::Entity as EntityTrait>::Model: + where + <::Entity as EntityTrait>::Model: IntoActiveModel, + for<'de> <::Entity as EntityTrait>::Model: serde::de::Deserialize<'de>, { use crate::Iterable; @@ -519,9 +519,9 @@ pub trait ActiveModelTrait: Clone + Debug { /// Create ActiveModel from a JSON value #[cfg(feature = "with-json")] fn from_json(json: serde_json::Value) -> Result - where - <::Entity as EntityTrait>::Model: IntoActiveModel, - for<'de> <::Entity as EntityTrait>::Model: + where + <::Entity as EntityTrait>::Model: IntoActiveModel, + for<'de> <::Entity as EntityTrait>::Model: serde::de::Deserialize<'de>, { use crate::{Iden, Iterable}; @@ -616,16 +616,16 @@ pub trait ActiveModelBehavior: ActiveModelTrait { /// A Trait for any type that can be converted into an ActiveModel pub trait IntoActiveModel -where - A: ActiveModelTrait, + where + A: ActiveModelTrait, { /// Method to call to perform the conversion fn into_active_model(self) -> A; } impl IntoActiveModel for A -where - A: ActiveModelTrait, + where + A: ActiveModelTrait, { fn into_active_model(self) -> A { self @@ -634,8 +634,8 @@ where /// Constraints to perform the conversion of a type into an [ActiveValue] pub trait IntoActiveValue -where - V: Into, + where + V: Into, { /// Method to perform the conversion fn into_active_value(self) -> ActiveValue; @@ -720,8 +720,8 @@ impl_into_active_value!(crate::prelude::Decimal); impl_into_active_value!(crate::prelude::Uuid); impl Default for ActiveValue -where - V: Into, + where + V: Into, { /// Create an [ActiveValue::NotSet] fn default() -> Self { @@ -730,8 +730,8 @@ where } impl ActiveValue -where - V: Into, + where + V: Into, { /// Create an [ActiveValue::Set] pub fn set(value: V) -> Self { @@ -799,8 +799,8 @@ where } impl std::convert::AsRef for ActiveValue -where - V: Into, + where + V: Into, { fn as_ref(&self) -> &V { match self { @@ -811,8 +811,8 @@ where } impl PartialEq for ActiveValue -where - V: Into + std::cmp::PartialEq, + where + V: Into + std::cmp::PartialEq, { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -825,8 +825,8 @@ where } impl From> for ActiveValue> -where - V: Into + Nullable, + where + V: Into + Nullable, { fn from(value: ActiveValue) -> Self { match value { @@ -867,7 +867,7 @@ mod tests { name: "Apple".to_owned(), cake_id: 1, } - .into_active_model(), + .into_active_model(), fruit::ActiveModel { id: NotSet, name: Set("Apple".to_owned()), @@ -894,7 +894,7 @@ mod tests { my_fruit::UpdateFruit { cake_id: Some(Some(1)), } - .into_active_model(), + .into_active_model(), fruit::ActiveModel { id: NotSet, name: NotSet, @@ -906,7 +906,7 @@ mod tests { my_fruit::UpdateFruit { cake_id: Some(None), } - .into_active_model(), + .into_active_model(), fruit::ActiveModel { id: NotSet, name: NotSet, @@ -924,10 +924,140 @@ mod tests { ); } + #[test] + #[cfg(feature = "macros")] + fn test_derive_try_into_model_1() { + mod my_fruit { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "fruit")] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub name: String, + pub cake_id: Option, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + assert_eq!( + my_fruit::ActiveModel { + id: Set(1), + name: Set("Pineapple".to_owned()), + cake_id: Set(None), + }.try_into_model().unwrap(), + my_fruit::Model { + id: 1, + name: "Pineapple".to_owned(), + cake_id: None, + }); + + assert_eq!( + my_fruit::ActiveModel { + id: Set(2), + name: Set("Apple".to_owned()), + cake_id: Set(Some(1)), + }.try_into_model().unwrap(), + my_fruit::Model { + id: 2, + name: "Apple".to_owned(), + cake_id: Some(1), + }); + + assert_eq!( + my_fruit::ActiveModel { + id: Set(1), + name: NotSet, + cake_id: Set(None), + }.try_into_model(), + Err(DbErr::Custom(String::from("field name is NotSet")))); + + assert_eq!( + my_fruit::ActiveModel { + id: Set(1), + name: Set("Pineapple".to_owned()), + cake_id: NotSet, + }.try_into_model(), + Err(DbErr::Custom(String::from("field cake_id is NotSet")))); + } + + #[test] + #[cfg(feature = "macros")] + fn test_derive_try_into_model_2() { + mod my_fruit { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "fruit")] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub name: String, + #[sea_orm(ignore)] + pub cake_id: Option, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + assert_eq!( + my_fruit::ActiveModel { + id: Set(1), + name: Set("Pineapple".to_owned()), + }.try_into_model().unwrap(), + my_fruit::Model { + id: 1, + name: "Pineapple".to_owned(), + cake_id: None, + }); + } + + #[test] + #[cfg(feature = "macros")] + fn test_derive_try_into_model_3() { + mod my_fruit { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] + #[sea_orm(table_name = "fruit")] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + #[sea_orm(ignore)] + pub name: String, + pub cake_id: Option, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + assert_eq!( + my_fruit::ActiveModel { + id: Set(1), + cake_id: Set(Some(1)), + }.try_into_model().unwrap(), + my_fruit::Model { + id: 1, + name: "".to_owned(), + cake_id: Some(1), + }); + } + #[test] #[cfg(feature = "with-json")] #[should_panic( - expected = r#"called `Result::unwrap()` on an `Err` value: Json("missing field `id`")"# + expected = r#"called `Result::unwrap()` on an `Err` value: Json("missing field `id`")"# )] fn test_active_model_set_from_json_1() { let mut cake: cake::ActiveModel = Default::default(); @@ -935,7 +1065,7 @@ mod tests { cake.set_from_json(json!({ "name": "Apple Pie", })) - .unwrap(); + .unwrap(); } #[test] @@ -1082,12 +1212,12 @@ mod tests { Transaction::from_sql_and_values( DbBackend::Postgres, r#"INSERT INTO "fruit" ("name") VALUES ($1) RETURNING "id", "name", "cake_id""#, - vec!["Apple".into()] + vec!["Apple".into()], ), Transaction::from_sql_and_values( DbBackend::Postgres, r#"UPDATE "fruit" SET "name" = $1, "cake_id" = $2 WHERE "fruit"."id" = $3 RETURNING "id", "name", "cake_id""#, - vec!["Orange".into(), 1i32.into(), 2i32.into()] + vec!["Orange".into(), 1i32.into(), 2i32.into()], ), ] ); From 27afc0199ac9f723fa8f6366b4a4019b9f98681c Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 24 Aug 2022 17:00:41 +0800 Subject: [PATCH 3/4] cargo fmt --- src/entity/active_model.rs | 144 ++++++++++++++++++++----------------- 1 file changed, 80 insertions(+), 64 deletions(-) diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 266f907b6..9457591a8 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -37,8 +37,8 @@ pub use ActiveValue::NotSet; /// ``` #[derive(Clone, Debug)] pub enum ActiveValue - where - V: Into, +where + V: Into, { /// A defined [Value] actively being set Set(V), @@ -51,21 +51,21 @@ pub enum ActiveValue /// Defines a set operation on an [ActiveValue] #[allow(non_snake_case)] pub fn Set(v: V) -> ActiveValue - where - V: Into, +where + V: Into, { ActiveValue::set(v) } /// Defines an not set operation on an [ActiveValue] #[deprecated( -since = "0.5.0", -note = "Please use [`ActiveValue::NotSet`] or [`NotSet`]" + since = "0.5.0", + note = "Please use [`ActiveValue::NotSet`] or [`NotSet`]" )] #[allow(non_snake_case)] pub fn Unset(_: Option) -> ActiveValue - where - V: Into, +where + V: Into, { ActiveValue::not_set() } @@ -73,8 +73,8 @@ pub fn Unset(_: Option) -> ActiveValue /// Defines an unchanged operation on an [ActiveValue] #[allow(non_snake_case)] pub fn Unchanged(value: V) -> ActiveValue - where - V: Into, +where + V: Into, { ActiveValue::unchanged(value) } @@ -273,10 +273,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// # } /// ``` async fn insert<'a, C>(self, db: &'a C) -> Result<::Model, DbErr> - where - ::Model: IntoActiveModel, - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + ::Model: IntoActiveModel, + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let am = ActiveModelBehavior::before_save(self, true)?; let model = ::insert(am) @@ -395,10 +395,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// # } /// ``` async fn update<'a, C>(self, db: &'a C) -> Result<::Model, DbErr> - where - ::Model: IntoActiveModel, - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + ::Model: IntoActiveModel, + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let am = ActiveModelBehavior::before_save(self, false)?; let model: ::Model = Self::Entity::update(am).exec(db).await?; @@ -408,10 +408,10 @@ pub trait ActiveModelTrait: Clone + Debug { /// Insert the model if primary key is `NotSet`, update otherwise. /// Only works if the entity has auto increment primary key. async fn save<'a, C>(self, db: &'a C) -> Result - where - ::Model: IntoActiveModel, - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + ::Model: IntoActiveModel, + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let mut is_update = true; for key in ::PrimaryKey::iter() { @@ -473,9 +473,9 @@ pub trait ActiveModelTrait: Clone + Debug { /// # } /// ``` async fn delete<'a, C>(self, db: &'a C) -> Result - where - Self: ActiveModelBehavior + 'a, - C: ConnectionTrait, + where + Self: ActiveModelBehavior + 'a, + C: ConnectionTrait, { let am = ActiveModelBehavior::before_delete(self)?; let am_clone = am.clone(); @@ -489,9 +489,9 @@ pub trait ActiveModelTrait: Clone + Debug { /// Note that this method will not alter the primary key values in ActiveModel. #[cfg(feature = "with-json")] fn set_from_json(&mut self, json: serde_json::Value) -> Result<(), DbErr> - where - <::Entity as EntityTrait>::Model: IntoActiveModel, - for<'de> <::Entity as EntityTrait>::Model: + where + <::Entity as EntityTrait>::Model: IntoActiveModel, + for<'de> <::Entity as EntityTrait>::Model: serde::de::Deserialize<'de>, { use crate::Iterable; @@ -519,9 +519,9 @@ pub trait ActiveModelTrait: Clone + Debug { /// Create ActiveModel from a JSON value #[cfg(feature = "with-json")] fn from_json(json: serde_json::Value) -> Result - where - <::Entity as EntityTrait>::Model: IntoActiveModel, - for<'de> <::Entity as EntityTrait>::Model: + where + <::Entity as EntityTrait>::Model: IntoActiveModel, + for<'de> <::Entity as EntityTrait>::Model: serde::de::Deserialize<'de>, { use crate::{Iden, Iterable}; @@ -616,16 +616,16 @@ pub trait ActiveModelBehavior: ActiveModelTrait { /// A Trait for any type that can be converted into an ActiveModel pub trait IntoActiveModel - where - A: ActiveModelTrait, +where + A: ActiveModelTrait, { /// Method to call to perform the conversion fn into_active_model(self) -> A; } impl IntoActiveModel for A - where - A: ActiveModelTrait, +where + A: ActiveModelTrait, { fn into_active_model(self) -> A { self @@ -634,8 +634,8 @@ impl IntoActiveModel for A /// Constraints to perform the conversion of a type into an [ActiveValue] pub trait IntoActiveValue - where - V: Into, +where + V: Into, { /// Method to perform the conversion fn into_active_value(self) -> ActiveValue; @@ -720,8 +720,8 @@ impl_into_active_value!(crate::prelude::Decimal); impl_into_active_value!(crate::prelude::Uuid); impl Default for ActiveValue - where - V: Into, +where + V: Into, { /// Create an [ActiveValue::NotSet] fn default() -> Self { @@ -730,8 +730,8 @@ impl Default for ActiveValue } impl ActiveValue - where - V: Into, +where + V: Into, { /// Create an [ActiveValue::Set] pub fn set(value: V) -> Self { @@ -799,8 +799,8 @@ impl ActiveValue } impl std::convert::AsRef for ActiveValue - where - V: Into, +where + V: Into, { fn as_ref(&self) -> &V { match self { @@ -811,8 +811,8 @@ impl std::convert::AsRef for ActiveValue } impl PartialEq for ActiveValue - where - V: Into + std::cmp::PartialEq, +where + V: Into + std::cmp::PartialEq, { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -825,8 +825,8 @@ impl PartialEq for ActiveValue } impl From> for ActiveValue> - where - V: Into + Nullable, +where + V: Into + Nullable, { fn from(value: ActiveValue) -> Self { match value { @@ -867,7 +867,7 @@ mod tests { name: "Apple".to_owned(), cake_id: 1, } - .into_active_model(), + .into_active_model(), fruit::ActiveModel { id: NotSet, name: Set("Apple".to_owned()), @@ -894,7 +894,7 @@ mod tests { my_fruit::UpdateFruit { cake_id: Some(Some(1)), } - .into_active_model(), + .into_active_model(), fruit::ActiveModel { id: NotSet, name: NotSet, @@ -906,7 +906,7 @@ mod tests { my_fruit::UpdateFruit { cake_id: Some(None), } - .into_active_model(), + .into_active_model(), fruit::ActiveModel { id: NotSet, name: NotSet, @@ -950,40 +950,50 @@ mod tests { id: Set(1), name: Set("Pineapple".to_owned()), cake_id: Set(None), - }.try_into_model().unwrap(), + } + .try_into_model() + .unwrap(), my_fruit::Model { id: 1, name: "Pineapple".to_owned(), cake_id: None, - }); + } + ); assert_eq!( my_fruit::ActiveModel { id: Set(2), name: Set("Apple".to_owned()), cake_id: Set(Some(1)), - }.try_into_model().unwrap(), + } + .try_into_model() + .unwrap(), my_fruit::Model { id: 2, name: "Apple".to_owned(), cake_id: Some(1), - }); + } + ); assert_eq!( my_fruit::ActiveModel { id: Set(1), name: NotSet, cake_id: Set(None), - }.try_into_model(), - Err(DbErr::Custom(String::from("field name is NotSet")))); + } + .try_into_model(), + Err(DbErr::Custom(String::from("field name is NotSet"))) + ); assert_eq!( my_fruit::ActiveModel { id: Set(1), name: Set("Pineapple".to_owned()), cake_id: NotSet, - }.try_into_model(), - Err(DbErr::Custom(String::from("field cake_id is NotSet")))); + } + .try_into_model(), + Err(DbErr::Custom(String::from("field cake_id is NotSet"))) + ); } #[test] @@ -1012,12 +1022,15 @@ mod tests { my_fruit::ActiveModel { id: Set(1), name: Set("Pineapple".to_owned()), - }.try_into_model().unwrap(), + } + .try_into_model() + .unwrap(), my_fruit::Model { id: 1, name: "Pineapple".to_owned(), cake_id: None, - }); + } + ); } #[test] @@ -1046,18 +1059,21 @@ mod tests { my_fruit::ActiveModel { id: Set(1), cake_id: Set(Some(1)), - }.try_into_model().unwrap(), + } + .try_into_model() + .unwrap(), my_fruit::Model { id: 1, name: "".to_owned(), cake_id: Some(1), - }); + } + ); } #[test] #[cfg(feature = "with-json")] #[should_panic( - expected = r#"called `Result::unwrap()` on an `Err` value: Json("missing field `id`")"# + expected = r#"called `Result::unwrap()` on an `Err` value: Json("missing field `id`")"# )] fn test_active_model_set_from_json_1() { let mut cake: cake::ActiveModel = Default::default(); @@ -1065,7 +1081,7 @@ mod tests { cake.set_from_json(json!({ "name": "Apple Pie", })) - .unwrap(); + .unwrap(); } #[test] From 5841f4fefba90ee3a0ed302f4ae23aa87ecbf508 Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Wed, 24 Aug 2022 17:59:42 +0800 Subject: [PATCH 4/4] Refactoring --- sea-orm-macros/src/derives/active_model.rs | 134 ++++++++++++--------- sea-orm-macros/src/util.rs | 7 +- 2 files changed, 84 insertions(+), 57 deletions(-) diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 73aa4e2bb..0a0a2fc2e 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -1,8 +1,14 @@ -use crate::util::{escape_rust_keyword, field_not_ignored, trim_starting_raw_identifier}; +use crate::util::{ + escape_rust_keyword, field_not_ignored, format_field_ident, trim_starting_raw_identifier, +}; use heck::CamelCase; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, quote_spanned}; -use syn::{punctuated::Punctuated, token::Comma, Data, DataStruct, Field, Fields, Lit, Meta, Type}; +use syn::{ + punctuated::{IntoIter, Punctuated}, + token::Comma, + Data, DataStruct, Field, Fields, Lit, Meta, Type, +}; /// Method to derive an [ActiveModel](sea_orm::ActiveModel) pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result { @@ -20,19 +26,19 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = all_fields - .clone() - .into_iter() - .map(|Field { ident, .. }| format_ident!("{}", ident.unwrap().to_string())) - .collect(); + Ok(quote!( + #derive_active_model + #derive_into_model + )) +} - let field: Vec = fields - .clone() - .into_iter() - .map(|Field { ident, .. }| format_ident!("{}", ident.unwrap().to_string())) - .collect(); +fn derive_active_model(all_fields: IntoIter) -> syn::Result { + let fields = all_fields.filter(field_not_ignored); + + let field: Vec = fields.clone().into_iter().map(format_field_ident).collect(); let name: Vec = fields .clone() @@ -71,27 +77,6 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = fields.into_iter().map(|Field { ty, .. }| ty).collect(); - let ignore_attr: Vec = all_fields - .clone() - .map(|field| !field_not_ignored(&field)) - .collect(); - - let field_value: Vec = all_field - .iter() - .zip(ignore_attr) - .map(|(field, ignore)| { - if ignore { - quote! { - Default::default() - } - } else { - quote! { - a.#field.into_value().unwrap().unwrap() - } - } - }) - .collect(); - Ok(quote!( #[derive(Clone, Debug, PartialEq)] pub struct ActiveModel { @@ -121,28 +106,6 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result for ::Model { - type Error = DbErr; - fn try_from(a: ActiveModel) -> Result { - #(if matches!(a.#field, sea_orm::ActiveValue::NotSet) { - return Err(DbErr::Custom(format!("field {} is NotSet", stringify!(#field)))); - })* - Ok( - Self { - #(#all_field: #field_value),* - } - ) - } - } - - #[automatically_derived] - impl sea_orm::TryIntoModel<::Model> for ActiveModel { - fn try_into_model(self) -> Result<::Model, DbErr> { - self.try_into() - } - } - #[automatically_derived] impl sea_orm::ActiveModelTrait for ActiveModel { type Entity = Entity; @@ -194,3 +157,62 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result) -> syn::Result { + let active_model_fields = model_fields.clone().filter(field_not_ignored); + + let active_model_field: Vec = active_model_fields + .into_iter() + .map(format_field_ident) + .collect(); + let model_field: Vec = model_fields + .clone() + .into_iter() + .map(format_field_ident) + .collect(); + + let ignore_attr: Vec = model_fields + .clone() + .map(|field| !field_not_ignored(&field)) + .collect(); + + let model_field_value: Vec = model_field + .iter() + .zip(ignore_attr) + .map(|(field, ignore)| { + if ignore { + quote! { + Default::default() + } + } else { + quote! { + a.#field.into_value().unwrap().unwrap() + } + } + }) + .collect(); + + Ok(quote!( + #[automatically_derived] + impl std::convert::TryFrom for ::Model { + type Error = DbErr; + fn try_from(a: ActiveModel) -> Result { + #(if matches!(a.#active_model_field, sea_orm::ActiveValue::NotSet) { + return Err(DbErr::Custom(format!("field {} is NotSet", stringify!(#active_model_field)))); + })* + Ok( + Self { + #(#model_field: #model_field_value),* + } + ) + } + } + + #[automatically_derived] + impl sea_orm::TryIntoModel<::Model> for ActiveModel { + fn try_into_model(self) -> Result<::Model, DbErr> { + self.try_into() + } + } + )) +} diff --git a/sea-orm-macros/src/util.rs b/sea-orm-macros/src/util.rs index 379b486ca..38b5a64c6 100644 --- a/sea-orm-macros/src/util.rs +++ b/sea-orm-macros/src/util.rs @@ -1,4 +1,5 @@ -use syn::{punctuated::Punctuated, token::Comma, Field, Meta}; +use quote::format_ident; +use syn::{punctuated::Punctuated, token::Comma, Field, Ident, Meta}; pub(crate) fn field_not_ignored(field: &Field) -> bool { for attr in field.attrs.iter() { @@ -25,6 +26,10 @@ pub(crate) fn field_not_ignored(field: &Field) -> bool { true } +pub(crate) fn format_field_ident(field: Field) -> Ident { + format_ident!("{}", field.ident.unwrap().to_string()) +} + pub(crate) fn trim_starting_raw_identifier(string: T) -> String where T: ToString,