From 5347dfbbaabbc1c0f8a0b22d3a53dc264dbfe07d Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Thu, 12 Jan 2023 17:22:46 +0800 Subject: [PATCH] Cont. feat: expose database connection to `ActiveModelBehaviour`'s methods (#1328) * feat: expose database connection to `ActiveModelBehaviour`'s methods (#1145) * Make ActiveModelTrait async * Add tests * refactoring Co-authored-by: teenjuna <53595243+teenjuna@users.noreply.github.com> --- src/entity/active_model.rs | 36 +++++++++++------ tests/common/bakery_chain/cake.rs | 23 ++++++++--- tests/common/features/edit_log.rs | 15 +++++++ tests/common/features/mod.rs | 2 + tests/common/features/repository.rs | 61 +++++++++++++++++++++++++++-- tests/common/features/schema.rs | 18 +++++++++ tests/string_primary_key_tests.rs | 53 ++++++++++++++++++++++++- 7 files changed, 187 insertions(+), 21 deletions(-) create mode 100644 tests/common/features/edit_log.rs diff --git a/src/entity/active_model.rs b/src/entity/active_model.rs index b5c078f6d9..2a34a28632 100644 --- a/src/entity/active_model.rs +++ b/src/entity/active_model.rs @@ -291,11 +291,11 @@ pub trait ActiveModelTrait: Clone + Debug { Self: ActiveModelBehavior + 'a, C: ConnectionTrait, { - let am = ActiveModelBehavior::before_save(self, true)?; + let am = ActiveModelBehavior::before_save(self, db, true).await?; let model = ::insert(am) .exec_with_returning(db) .await?; - Self::after_save(model, true) + Self::after_save(model, db, true).await } /// Perform the `UPDATE` operation on an ActiveModel @@ -413,9 +413,9 @@ pub trait ActiveModelTrait: Clone + Debug { Self: ActiveModelBehavior + 'a, C: ConnectionTrait, { - let am = ActiveModelBehavior::before_save(self, false)?; + let am = ActiveModelBehavior::before_save(self, db, false).await?; let model: ::Model = Self::Entity::update(am).exec(db).await?; - Self::after_save(model, false) + Self::after_save(model, db, false).await } /// Insert the model if primary key is `NotSet`, update otherwise. @@ -490,10 +490,10 @@ pub trait ActiveModelTrait: Clone + Debug { Self: ActiveModelBehavior + 'a, C: ConnectionTrait, { - let am = ActiveModelBehavior::before_delete(self)?; + let am = ActiveModelBehavior::before_delete(self, db).await?; let am_clone = am.clone(); let delete_res = Self::Entity::delete(am).exec(db).await?; - ActiveModelBehavior::after_delete(am_clone)?; + ActiveModelBehavior::after_delete(am_clone, db).await?; Ok(delete_res) } @@ -597,6 +597,7 @@ pub trait ActiveModelTrait: Clone + Debug { /// ``` /// See module level docs [crate::entity] for a full example #[allow(unused_variables)] +#[async_trait] pub trait ActiveModelBehavior: ActiveModelTrait { /// Create a new ActiveModel with default values. Also used by `Default::default()`. fn new() -> Self { @@ -604,25 +605,38 @@ pub trait ActiveModelBehavior: ActiveModelTrait { } /// Will be called before saving - fn before_save(self, insert: bool) -> Result { + async fn before_save(self, db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { Ok(self) } /// Will be called after saving - fn after_save( + async fn after_save( model: ::Model, + db: &C, insert: bool, - ) -> Result<::Model, DbErr> { + ) -> Result<::Model, DbErr> + where + C: ConnectionTrait, + { Ok(model) } /// Will be called before deleting - fn before_delete(self) -> Result { + async fn before_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { Ok(self) } /// Will be called after deleting - fn after_delete(self) -> Result { + async fn after_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { Ok(self) } } diff --git a/tests/common/bakery_chain/cake.rs b/tests/common/bakery_chain/cake.rs index 73b70195dc..c844806fe7 100644 --- a/tests/common/bakery_chain/cake.rs +++ b/tests/common/bakery_chain/cake.rs @@ -1,4 +1,4 @@ -use sea_orm::entity::prelude::*; +use sea_orm::{entity::prelude::*, ConnectionTrait}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] @@ -49,6 +49,7 @@ impl Related for Entity { } } +#[async_trait::async_trait] impl ActiveModelBehavior for ActiveModel { fn new() -> Self { use sea_orm::Set; @@ -58,7 +59,10 @@ impl ActiveModelBehavior for ActiveModel { } } - fn before_save(self, insert: bool) -> Result { + async fn before_save(self, _db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { use rust_decimal_macros::dec; if self.price.as_ref() == &dec!(0) { Err(DbErr::Custom(format!( @@ -70,7 +74,10 @@ impl ActiveModelBehavior for ActiveModel { } } - fn after_save(model: Model, insert: bool) -> Result { + async fn after_save(model: Model, _db: &C, insert: bool) -> Result + where + C: ConnectionTrait, + { use rust_decimal_macros::dec; if model.price < dec!(0) { Err(DbErr::Custom(format!( @@ -82,7 +89,10 @@ impl ActiveModelBehavior for ActiveModel { } } - fn before_delete(self) -> Result { + async fn before_delete(self, _db: &C) -> Result + where + C: ConnectionTrait, + { if self.name.as_ref().contains("(err_on_before_delete)") { Err(DbErr::Custom( "[before_delete] Cannot be deleted".to_owned(), @@ -92,7 +102,10 @@ impl ActiveModelBehavior for ActiveModel { } } - fn after_delete(self) -> Result { + async fn after_delete(self, _db: &C) -> Result + where + C: ConnectionTrait, + { if self.name.as_ref().contains("(err_on_after_delete)") { Err(DbErr::Custom("[after_delete] Cannot be deleted".to_owned())) } else { diff --git a/tests/common/features/edit_log.rs b/tests/common/features/edit_log.rs new file mode 100644 index 0000000000..7fe5d23c26 --- /dev/null +++ b/tests/common/features/edit_log.rs @@ -0,0 +1,15 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "edit_log")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub action: String, + pub values: Json, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/common/features/mod.rs b/tests/common/features/mod.rs index 36f76a4529..b944836337 100644 --- a/tests/common/features/mod.rs +++ b/tests/common/features/mod.rs @@ -4,6 +4,7 @@ pub mod applog; pub mod byte_primary_key; pub mod collection; pub mod custom_active_model; +pub mod edit_log; pub mod event_trigger; pub mod insert_default; pub mod json_struct; @@ -23,6 +24,7 @@ pub use active_enum_child::Entity as ActiveEnumChild; pub use applog::Entity as Applog; pub use byte_primary_key::Entity as BytePrimaryKey; pub use collection::Entity as Collection; +pub use edit_log::Entity as EditLog; pub use event_trigger::Entity as EventTrigger; pub use insert_default::Entity as InsertDefault; pub use json_struct::Entity as JsonStruct; diff --git a/tests/common/features/repository.rs b/tests/common/features/repository.rs index 651a0bc519..73bbcb303d 100644 --- a/tests/common/features/repository.rs +++ b/tests/common/features/repository.rs @@ -1,6 +1,8 @@ -use sea_orm::entity::prelude::*; +use super::edit_log; +use sea_orm::{entity::prelude::*, ConnectionTrait, Set, TryIntoModel}; +use serde::Serialize; -#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize)] #[sea_orm(table_name = "repository")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] @@ -13,4 +15,57 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} -impl ActiveModelBehavior for ActiveModel {} +#[async_trait::async_trait] +impl ActiveModelBehavior for ActiveModel { + async fn before_save(self, db: &C, _: bool) -> Result + where + C: ConnectionTrait, + { + let model = self.clone().try_into_model()?; + insert_edit_log("before_save", &model, db).await?; + Ok(self) + } + + async fn after_save(model: Model, db: &C, _: bool) -> Result + where + C: ConnectionTrait, + { + insert_edit_log("after_save", &model, db).await?; + Ok(model) + } + + async fn before_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { + let model = self.clone().try_into_model()?; + insert_edit_log("before_delete", &model, db).await?; + Ok(self) + } + + async fn after_delete(self, db: &C) -> Result + where + C: ConnectionTrait, + { + let model = self.clone().try_into_model()?; + insert_edit_log("after_delete", &model, db).await?; + Ok(self) + } +} + +async fn insert_edit_log(action: T, model: &M, db: &C) -> Result<(), DbErr> +where + T: Into, + M: Serialize, + C: ConnectionTrait, +{ + edit_log::ActiveModel { + action: Set(action.into()), + values: Set(serde_json::json!(model)), + ..Default::default() + } + .insert(db) + .await?; + + Ok(()) +} diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index 4910e70893..38e2682e6a 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -43,6 +43,7 @@ pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> { create_insert_default_table(db).await?; create_pi_table(db).await?; create_uuid_fmt_table(db).await?; + create_edit_log_table(db).await?; if DbBackend::Postgres == db_backend { create_collection_table(db).await?; @@ -480,3 +481,20 @@ pub async fn create_uuid_fmt_table(db: &DbConn) -> Result { create_table(db, &stmt, UuidFmt).await } + +pub async fn create_edit_log_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(edit_log::Entity) + .col( + ColumnDef::new(edit_log::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(edit_log::Column::Action).string().not_null()) + .col(ColumnDef::new(edit_log::Column::Values).json().not_null()) + .to_owned(); + + create_table(db, &stmt, EditLog).await +} diff --git a/tests/string_primary_key_tests.rs b/tests/string_primary_key_tests.rs index 56496bdc47..94d1ae554c 100644 --- a/tests/string_primary_key_tests.rs +++ b/tests/string_primary_key_tests.rs @@ -3,6 +3,7 @@ pub mod common; pub use common::{features::*, setup::*, TestContext}; use pretty_assertions::assert_eq; use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection}; +use serde_json::json; #[sea_orm_macros::test] #[cfg(any( @@ -14,13 +15,13 @@ async fn main() -> Result<(), DbErr> { let ctx = TestContext::new("features_schema_string_primary_key_tests").await; create_tables(&ctx.db).await?; create_and_update_repository(&ctx.db).await?; - insert_repository(&ctx.db).await?; + insert_and_delete_repository(&ctx.db).await?; ctx.delete().await; Ok(()) } -pub async fn insert_repository(db: &DatabaseConnection) -> Result<(), DbErr> { +pub async fn insert_and_delete_repository(db: &DatabaseConnection) -> Result<(), DbErr> { let repository = repository::Model { id: "unique-id-001".to_owned(), owner: "GC".to_owned(), @@ -41,6 +42,54 @@ pub async fn insert_repository(db: &DatabaseConnection) -> Result<(), DbErr> { } ); + result.delete(db).await?; + + assert_eq!( + edit_log::Entity::find().all(db).await?, + [ + edit_log::Model { + id: 1, + action: "before_save".into(), + values: json!({ + "description": null, + "id": "unique-id-001", + "name": "G.C.", + "owner": "GC", + }), + }, + edit_log::Model { + id: 2, + action: "after_save".into(), + values: json!({ + "description": null, + "id": "unique-id-001", + "name": "G.C.", + "owner": "GC", + }), + }, + edit_log::Model { + id: 3, + action: "before_delete".into(), + values: json!({ + "description": null, + "id": "unique-id-001", + "name": "G.C.", + "owner": "GC", + }), + }, + edit_log::Model { + id: 4, + action: "after_delete".into(), + values: json!({ + "description": null, + "id": "unique-id-001", + "name": "G.C.", + "owner": "GC", + }), + }, + ] + ); + Ok(()) }