diff --git a/sea-orm-macros/src/derives/active_model.rs b/sea-orm-macros/src/derives/active_model.rs index 635897dcb..0a0a2fc2e 100644 --- a/sea-orm-macros/src/derives/active_model.rs +++ b/sea-orm-macros/src/derives/active_model.rs @@ -1,12 +1,19 @@ -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 { - let fields = match data { + // including ignored fields + let all_fields = match data { Data::Struct(DataStruct { fields: Fields::Named(named), .. @@ -17,14 +24,21 @@ pub fn expand_derive_active_model(ident: Ident, data: Data) -> syn::Result = fields - .clone() - .into_iter() - .map(|Field { ident, .. }| format_ident!("{}", ident.unwrap().to_string())) - .collect(); + let derive_active_model = derive_active_model(all_fields.clone())?; + let derive_into_model = derive_into_model(all_fields.clone())?; + + Ok(quote!( + #derive_active_model + #derive_into_model + )) +} + +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() @@ -143,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, diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index 0fd54b713..9457591a8 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -924,6 +924,152 @@ 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( @@ -1082,12 +1228,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()], ), ] ); 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