模型

loco 中的模型指的是实体类,它使得数据库查询和写入变得容易,同时也方便进行迁移和数据填充(seeding)。

Sqlite vs Postgres

您可能在创建新应用时选择了 sqlite,这是默认选项。Loco 允许您在 sqlitepostgres 之间 无缝 切换。

通常情况下,您可以使用 sqlite 进行开发,而使用 postgres 进行生产。有些人更喜欢在开发和生产中都使用 postgres,因为他们会用到 pg 的特定功能。现在也有人使用 sqlite 进行生产。无论哪种方式,都是有效的选择。

要配置 postgres 而不是 sqlite,请进入您的 config/development.yaml(或 production.yaml)并进行如下设置,假设您的应用名为 myapp

database:
  uri: "{{ get_env(name="DATABASE_URL", default="postgres://loco:loco@localhost:5432/myapp_development") }"
您的本地 postgres 数据库应该使用 loco:loco 用户名和密码,数据库名称为 myapp_development。对于测试和生产环境,您的数据库名称应分别为 myapp_testmyapp_production

为了您的方便,这里有一个启动 Postgresql 数据库服务器的 docker 命令:

docker run -d -p 5432:5432 \
  -e POSTGRES_USER=loco \
  -e POSTGRES_DB=myapp_development \
  -e POSTGRES_PASSWORD="loco" \
  postgres:15.3-alpine

最后,您还可以使用 doctor 命令来验证您的连接:

$ cargo loco doctor
    Finished dev [unoptimized + debuginfo] target(s) in 0.32s
    Running `target/debug/myapp-cli doctor`
 SeaORM CLI is installed
 DB connection: success
 Redis connection: success

胖模型,瘦控制器

loco 模型是根据 active record 模式设计的。这意味着它们是您系统中的中心点,您应用的所有逻辑或操作都应该放在这里。

这意味着 User::create 创建一个用户,而且 user.buy(product) 也会购买一个产品。

如果您认同这个方向,您将免费获得以下好处:

  • 节省时间的测试,因为测试您的模型就测试了大部分甚至全部的逻辑和活动部件。
  • 能够从 tasks、或 worker 以及其他地方 运行完整的应用工作流程。
  • 通过组合模型,以及仅此而已,有效地组合特性和用例。
  • 本质上,模型变成您的应用,而控制器只是向世界公开您的应用的一种方式。

我们使用 SeaORM 作为 ActiveRecord 抽象背后的主要 ORM。

  • 为什么不是 Diesel? - 尽管 Diesel 性能更好,但它的宏和总体方法与我们尝试做的事情不兼容
  • 为什么不是 sqlx? - SeaORM 在底层使用了 sqlx,因此如果您愿意,可以使用原始的 sqlx

模型示例

loco 模型的生命周期从 迁移 开始,然后从数据库结构自动为您生成 实体 Rust 代码:

src/
  models/
    _entities/   <--- 自动生成的代码
      users.rs   <--- 裸实体和辅助 traits
    users.rs  <--- 您的自定义 activerecord 代码

使用 users activerecord 就像您在 SeaORM 下使用它一样 请参阅此处的示例

通过 扩展users activerecord 添加功能:

impl super::_entities::users::ActiveModel {
    /// .
    ///
    /// # Errors
    ///
    /// .
    pub fn foobar(&self) -> Result<(), DbErr> {
        // 实现并返回一个 `user.foobar()`
    }
}

制作模型

模型生成器

要添加新模型,模型生成器会创建一个迁移,运行它,然后从您的数据库模式触发实体同步,这将补充并创建您的模型实体。

$ cargo loco generate model posts title:string! content:text user:references

当通过迁移添加模型时,将提供以下默认字段:

  • created_at (ts!):这是一个时间戳,指示您的模型何时创建。
  • updated_at (ts!):这是一个时间戳,指示您的模型何时更新。

如果您在迁移命令中提供了这些字段,则会忽略这些默认字段。

对于模式数据类型,您可以使用以下映射来理解模式:

("uuid", "uuid_uniq"),
("uuid_col", "uuid_null"),
("uuid_col!", "uuid"),
("string", "string_null"),
("string!", "string"),
("string^", "string_uniq"),
("text", "text_null"),
("text!", "text"),
("tiny_integer", "tiny_integer_null"),
("tiny_integer!", "tiny_integer"),
("tiny_integer^", "tiny_integer_uniq"),
("small_integer", "small_integer_null"),
("small_integer!", "small_integer"),
("small_integer^", "small_integer_uniq"),
("int", "integer_null"),
("int!", "integer"),
("int^", "integer_uniq"),
("big_int", "big_integer_null"),
("big_int!", "big_integer"),
("big_int^", "big_integer_uniq"),
("float", "float_null"),
("float!", "float"),
("double", "double_null"),
("double!", "double"),
("decimal", "decimal_null"),
("decimal!", "decimal"),
("decimal_len", "decimal_len_null"),
("decimal_len!", "decimal_len"),
("bool", "boolean_null"),
("bool!", "boolean"),
("tstz", "timestamp_with_time_zone_null"),
("tstz!", "timestamp_with_time_zone"),
("date", "date_null"),
("date!", "date"),
("ts", "timestamp_null"),
("ts!", "timestamp"),
("json", "json_null"),
("json!", "json"),
("jsonb", "json_binary_null"),
("jsonb!", "json_binary"),

使用 user:references 使用特殊的 references 类型,这将在 postuser 之间创建关系,并在 posts 表中添加 user_id 引用字段。

使用 approved_by:references:users 使用特殊的 references:<table> 类型,这将在 postuser 之间创建关系,并在 posts 表中添加 approved_by 引用字段。

您可以生成一个空模型:

$ cargo loco generate model posts

或者一个数据模型,没有任何引用:

$ cargo loco generate model posts title:string! content:text

迁移

除了使用模型生成器之外,您还可以通过创建迁移来驱动您的模式。

$ cargo loco generate migration <迁移名称> [name:type, name:type ...]

这将在您项目的根目录 migration/ 中创建一个迁移。

您可以应用它:

$ cargo loco db migrate

并从中生成实体(Rust 代码):

$ cargo loco db entities

Loco 是一个迁移优先的框架,类似于 Rails。这意味着当您想要添加模型、数据字段或面向模型的更改时 - 您首先要创建一个描述它的迁移,然后应用该迁移以在 model/_entities 中获得生成的实体。

这强制执行了 一切皆代码可重现性原子性,其中不会丢失任何模式知识。

命名迁移非常重要,正在生成的迁移类型是从迁移名称推断出来的。

创建新表

  • 名称模板:Create___
  • 示例:CreatePosts
$ cargo loco g migration CreatePosts title:string content:string

添加列

  • 名称模板:Add___To___
  • 示例:AddNameAndAgeToUsers(字符串 NameAndAge 并不重要,您可以单独指定列,但 Users 很重要,因为这将是表名)
$ cargo loco g migration AddNameAndAgeToUsers name:string age:int

删除列

  • 名称模板:Remove___From___
  • 示例:RemoveNameAndAgeFromUsers(与 添加列 中相同的注意事项)
$ cargo logo g migration RemoveNameAndAgeFromUsers name:string age:int

添加引用

  • 名称模板:Add___RefTo___
  • 示例:AddUserRefToPostsUser 并不重要,因为您可以单独指定一个或多个引用,Posts 很重要,因为它将是迁移中的表名)
$ cargo loco g migration AddUserRefToPosts user:references

创建连接表

  • 名称模板:CreateJoinTable___And___(支持在 2 个表之间)
  • 示例:CreateJoinTableUsersAndGroups
$ cargo loco g migration CreateJoinTableUsersAndGroups count:int

您还可以添加一些关于关系的状态列(例如这里的 count)。

创建空迁移

对于不属于上述模式之一的迁移,请使用任何描述性名称来创建空迁移。

$ cargo loco g migration FixUsersTable

回滚迁移

如果您意识到自己犯了错误,您可以随时撤消迁移。这将撤消迁移所做的更改(假设您在迁移中为 down 添加了适当的代码)。

cargo loco db down

单独使用 down 命令只会回滚最后一次迁移。如果您想回滚多次迁移,您可以指定要回滚的迁移数量。

cargo loco db down 2

动词、单数和复数

  • references: 对于表名使用单数,以及 :references 类型。user:references(引用 Users),vote:references(引用 Votes)。 :references:<table> 也可用 departing_train:references:trains(引用 Trains)。
  • 列名: 任何您喜欢的。首选 snake_case
  • 表名: 复数,snake caseusersdraft_posts
  • 迁移名称: 任何可以作为文件名的名称,首选 snake case。create_table_usersadd_vote_id_to_movies
  • 模型名称: 为您自动生成。通常生成的名称是 PascalCase,复数。UsersUsersVotes

以下是一些示例,展示了命名约定:

$ cargo loco generate model movies long_title:string added_by:references:users director:references
  • 模型名称为复数:movies
  • 引用 director 为单数:director:references
  • 引用 added_by 为单数,被引用的模型是一个模型且为复数:added_by:references:users
  • 列名使用 snake case:long_title:string

编写迁移

要使用迁移 DSL,请确保您导入了以下 loco_rs::schema::* 和 SeaORM prelude

use loco_rs::schema::*;
use sea_orm_migration::prelude::*;

然后,创建一个结构体:

#[derive(DeriveMigrationName)]
pub struct Migration;

然后实现您的迁移(见下文)。

创建表

创建表,提供两个数组:(1)列(2)引用。

将引用留空以不创建任何引用字段。

impl MigrationTrait for Migration {
    async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
        create_table(
            m,
            "posts",
            &[
                ("title", ColType::StringNull),
                ("content", ColType::StringNull),
            ],
            &[],
        )
        .await
    }

    async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
        drop_table(m, "posts").await
    }
}

创建连接表

将引用提供给第二个数组参数。使用空字符串 "" 表示您希望我们为您生成引用列名(例如,user 引用将暗示通过 group_users 中的 user_id 列连接 users 表)。

提供非空字符串以指示引用列名的特定名称。

impl MigrationTrait for Migration {
    async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
        create_join_table(m, "group_users", &[], &[("user", ""), ("group", "")]).await
    }

    async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
        drop_table(m, "group_users").await
    }
}

添加列

添加单个列。您可以在单个迁移中使用任意数量的此类语句(以添加多列)。

impl MigrationTrait for Migration {
    async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
        add_column(m, "users", "amount", ColType::DecimalLenNull(24,8)).await?;
        Ok(())
    }

    async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
        remove_column(m, "users", "amount").await?;
        Ok(())
    }
}

编写高级迁移

直接使用 manager 可以让您在编写迁移时访问更高级的操作。

添加列

  manager
    .alter_table(
        Table::alter()
            .table(Movies::Table)
            .add_column_if_not_exists(integer(Movies::Rating))
            .to_owned(),
    )
    .await

删除列

  manager
    .alter_table(
        Table::alter()
            .table(Movies::Table)
            .drop_column(Movies::Rating)
            .to_owned(),
    )
    .await

添加索引

您可以复制以下代码来添加索引

  manager
    .create_index(
        Index::create()
            .name("idx-movies-rating")
            .table(Movies::Table)
            .col(Movies::Rating)
            .to_owned(),
    )
    .await;

创建数据修复

在迁移中创建数据修复很容易 - 只需根据需要使用 SQL 语句:

  async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {

    let db = manager.get_connection();

    // 使用 `db` 发出 SQL 查询
    // https://www.sea-ql.org/SeaORM/docs/basic-crud/raw-sql/#use-raw-query--execute-interface

    Ok(())
  }

话虽如此,您可以自行决定在以下位置编写数据修复代码:

  • task - 您可以在其中使用高级模型
  • migration - 您可以在其中更改结构并使用原始 SQL 修复由此产生的数据
  • 或临时的 playground - 您可以在其中使用高级模型或尝试各种操作

验证

我们在底层使用了 validator 库。首先,使用您需要的约束构建您的验证器,然后为您的 ActiveModel 实现 Validatable

#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(length(min = 2, message = "Name must be at least 2 characters long."))]
    pub name: String,
    #[validate(custom(function = "validation::is_valid_email"))]
    pub email: String,
}

impl Validatable for super::_entities::users::ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            name: self.name.as_ref().to_owned(),
            email: self.email.as_ref().to_owned(),
        })
    }
}

请注意,Validatable 是您指示 Loco 提供哪个 Validator 以及如何从模型构建它的方式。

现在您可以在代码中无缝使用 user.validate(),当它是 Ok 时,模型有效,否则您将在 Err(...) 中找到可供检查的验证错误。

关系

一对多

以下是如何将 Company 与现有 User 模型关联。

$ cargo loco generate model company name:string user:references

这将在 Company 中创建一个带有 user_id 字段的迁移,该字段将引用 User

多对多

以下是如何创建一个典型的 “votes” 表,该表将 UserMovie 与多对多连接表链接起来。请注意,它在模型生成器中使用了特殊的 --link 标志。

让我们创建一个新的 Movie 实体:

$ cargo loco generate model movies title:string

现在创建 User(我们已经有了)和 Movie(我们刚刚生成的)之间的连接表,以记录投票:

$ cargo loco generate model --link users_votes user:references movie:references vote:int
    ..
    ..
Writing src/models/_entities/movies.rs
Writing src/models/_entities/users.rs
Writing src/models/_entities/mod.rs
Writing src/models/_entities/prelude.rs
... Done.

这将创建一个名为 UsersVotes 的多对多连接表,其中包含由 user_idmovie_id 组成的复合主键。由于它恰好有 2 个 ID,SeaORM 将其识别为多对多连接表,并生成具有适当 via() 关系的实体:

// User,新生成的实体,在 _entities/users.rs 中具有 `via` 关系

// ..
impl Related<super::movies::Entity> for Entity {
    fn to() -> RelationDef {
        super::users_votes::Relation::Movies.def()
    }
    fn via() -> Option<RelationDef> {
        Some(super::users_votes::Relation::Users.def().rev())
    }
}

使用 via() 将使 find_related 遍历连接表,而您无需了解连接表的详细信息。

配置

模型配置对您来说令人兴奋,因为它控制着开发、测试和生产的各个方面,并带来了大量来自生产经验的好处。

database:
  # 数据库连接 URI
  uri: 
  # 启用后,将记录 sql 查询。
  enable_logging: false
  # 设置获取连接时的超时持续时间。
  connect_timeout: 500
  # 设置关闭连接前的空闲持续时间。
  idle_timeout: 500
  # 连接池的最小连接数。
  min_connections: 1
  # 连接池的最大连接数。
  max_connections: 1
  # 应用程序加载时运行迁移
  auto_migrate: true
  # 应用程序加载时截断数据库。这是一个危险的操作,请确保您仅在开发环境或测试模式下使用此标志
  dangerously_truncate: false
  # 应用程序加载时重新创建模式。这是一个危险的操作,请确保您仅在开发环境或测试模式下使用此标志
  dangerously_recreate: false

通过组合这些标志,您可以创建不同的体验来帮助您提高工作效率。

您可以在应用启动前截断数据库 -- 这对于运行测试很有用,或者您可以在应用启动时重新创建整个数据库 -- 这对于集成测试或设置新环境很有用。在生产环境中,您需要关闭这些功能(因此使用了 “dangerously” 部分)。

数据填充(Seeding)

Loco 具有内置的 'seeds' 功能,可以快速轻松地完成此过程。这在开发和测试环境中频繁重新加载数据库时尤其有用。入门非常简单

Loco 配备了一个方便的 seeds 功能,简化了快速轻松地重新加载数据库的过程。此功能在开发和测试环境中频繁重置时尤其有价值。让我们探索如何开始使用此功能:

创建新的 Seed

1. 创建新的 Seed 文件

导航到 src/fixtures 并创建一个新的 seed 文件。例如:

src/
  fixtures/
    users.yaml

在此 yaml 文件中,列出一组要插入的数据库记录。每个记录都应包含强制性的数据库字段,具体取决于您的数据库约束。可选值由您自行决定。假设您有一个像这样的数据库 DDL:

CREATE TABLE public.users (
	id serial4 NOT NULL,
	email varchar NOT NULL,
	"password" varchar NOT NULL,
	reset_token varchar NULL,
	created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
	CONSTRAINT users_email_key UNIQUE (email),
	CONSTRAINT users_pkey PRIMARY KEY (id)
);

强制字段包括 idpasswordemailcreated_at。重置令牌可以留空。您的迁移内容文件应类似于以下内容:

---
- id: 1
  email: user1@example.com
  password: "$2b$12$gf4o2FShIahg/GY6YkK2wOcs8w4.lu444wP6BL3FyjX0GsxnEV6ZW"
  created_at: "2023-11-12T12:34:56.789"
- id: 2
  pid: 22222222-2222-2222-2222-222222222222
  email: user2@example.com
  reset_token: "SJndjh2389hNJKnJI90U32NKJ"
  password: "$2b$12$gf4o2FShIahg/GY6YkK2wOcs8w4.lu444wP6BL3FyjX0GsxnEV6ZW"
  created_at: "2023-11-12T12:34:56.789"

连接 Seed

通过执行以下步骤将您的 seed 集成到应用的 Hook 实现中:

  1. 导航到您应用的 Hook 实现。
  2. 在 seed 函数实现中添加 seed。以下是 Rust 中的示例:
impl Hooks for App {
    // 其他实现...

    async fn seed(ctx: &AppContext, base: &Path) -> Result<()> {
        db::seed::<users::ActiveModel>(&ctx.db, &base.join("users.yaml").display().to_string()).await?;
        Ok(())
    }
}

此实现确保在调用 seed 函数时执行 seed。根据您应用的结构和需求调整具体细节。

通过 CLI 管理 Seed

  • 重置数据库 在导入 seed 文件之前清除所有现有数据。当您想从全新的数据库状态开始时,这非常有用,确保不保留旧数据。
  • 将数据库表转储到文件 将数据库表的内容导出到文件。此功能允许您备份数据库的当前状态或准备数据以在不同环境中重用。

要访问 seed 命令,请使用以下 CLI 结构:

Seed your database with initial data or dump tables to files

Usage: demo_app-cli db seed [OPTIONS]

Options:
  -r, --reset                      Clears all data in the database before seeding
  -d, --dump                       Dumps all database tables to files
      --dump-tables <DUMP_TABLES>  Specifies specific tables to dump
      --from <FROM>                Specifies the folder containing seed files (defaults to 'src/fixtures') [default: src/fixtures]
  -e, --environment <ENVIRONMENT>  Specify the environment [default: development]
  -h, --help                       Print help
  -V, --version                    Print version

使用测试

  1. 启用 testing 功能 (testing)

  2. 在您的测试部分,按照以下示例操作:

use loco_rs::testing::prelude::*;

#[tokio::test]
#[serial]
async fn handle_create_with_password_with_duplicate() {

    let boot = boot_test::<App, Migrator>().await;
    seed::<App>(&boot.app_context).await.unwrap();
    assert!(get_user_by_id(1).ok());
}

多数据库

Loco 使您能够使用多个数据库并在您的应用程序中共享实例。

额外的数据库

要设置额外的数据库,请从数据库连接和配置开始。推荐的方法是导航到您的配置文件并在 settings 下添加以下内容:

settings:
  extra_db:
    uri: postgres://loco:loco@localhost:5432/loco_app
    enable_logging: false
    connect_timeout: 500
    idle_timeout: 500
    min_connections: 1
    max_connections: 1
    auto_migrate: true
    dangerously_truncate: false
    dangerously_recreate: false

将此 initializer 加载到 initializers hook 中,如下例所示

async fn initializers(ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> {
        let  initializers: Vec<Box<dyn Initializer>> = vec![
            Box::new(loco_rs::initializers::extra_db::ExtraDbInitializer),
        ];

        Ok(initializers)
    }

现在,您可以在控制器中使用辅助数据库:

use sea_orm::DatabaseConnection;
use axum::{response::IntoResponse, Extension};

pub async fn list(
    State(ctx): State<AppContext>,
    Extension(secondary_db): Extension<DatabaseConnection>,
) -> Result<impl IntoResponse> {
  let res = Entity::find().all(&secondary_db).await;
}

多数据库(多租户)

要连接两个以上不同的数据库,数据库配置应如下所示:

settings:
  multi_db:
    secondary_db:
      uri: postgres://loco:loco@localhost:5432/loco_app
      enable_logging: false
      connect_timeout: 500
      idle_timeout: 500
      min_connections: 1
      max_connections: 1
      auto_migrate: true
      dangerously_truncate: false
      dangerously_recreate: false
    third_db:
      uri: postgres://loco:loco@localhost:5432/loco_app
      enable_logging: false
      connect_timeout: 500
      idle_timeout: 500
      min_connections: 1
      max_connections: 1
      auto_migrate: true
      dangerously_truncate: false
      dangerously_recreate: false

接下来,将此 initializer 加载到 initializers hook 中,如下例所示

async fn initializers(ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> {
        let  initializers: Vec<Box<dyn Initializer>> = vec![
            Box::new(loco_rs::initializers::multi_db::MultiDbInitializer),
        ];

        Ok(initializers)
    }

现在,您可以在控制器中使用多个数据库:

use sea_orm::DatabaseConnection;
use axum::{response::IntoResponse, Extension};
use loco_rs::db::MultiDb;

pub async fn list(
    State(ctx): State<AppContext>,
    Extension(multi_db): Extension<MultiDb>,
) -> Result<impl IntoResponse> {
  let third_db = multi_db.get("third_db")?;
  let res = Entity::find().all(third_db).await;
}

测试

如果您使用生成器创建了模型迁移,那么您还应该在 tests/models/posts.rs 中有一个自动生成的模型测试(还记得我们生成了一个名为 post 的模型吗?)。

一个典型的测试包含设置测试数据、启动应用以及在测试代码运行之前自动重置数据库所需的一切。它看起来像这样:

use loco_rs::testing::prelude::*;

async fn can_find_by_pid() {
    configure_insta!();

    let boot = boot_test::<App, Migrator>().await;
    seed::<App>(&boot.app_context).await.unwrap();

    let existing_user =
        Model::find_by_pid(&boot.app_context.db, "11111111-1111-1111-1111-111111111111").await;
    let non_existing_user_results =
        Model::find_by_email(&boot.app_context.db, "23232323-2323-2323-2323-232323232323").await;

    assert_debug_snapshot!(existing_user);
    assert_debug_snapshot!(non_existing_user_results);
}

为了简化测试过程,Loco 提供了有用的函数,使编写测试更加方便。确保在您的 Cargo.toml 中启用 testing 功能:

[dev-dependencies]
loco-rs = { version = "*",  features = ["testing"] }

数据库清理

在某些情况下,您可能希望使用干净的数据集运行测试,确保每个测试都是独立的,并且不受先前数据的影响。要启用此功能,请在 config/test.yaml 文件中的 database 部分将 dangerously_truncate 选项修改为 true。此设置确保 Loco 在每个实现 boot app 的测试之前截断所有数据。

⚠️ 注意:使用此功能时要小心,以避免意外数据丢失,尤其是在生产环境中。

  • 建议在运行所有相关任务时使用 serial crate。
  • 要决定要截断哪些表,请将实体模型添加到 App hook:
pub struct App;
#[async_trait]
impl Hooks for App {
    //...
    async fn truncate(ctx: &AppContext) -> Result<()> {
        // truncate_table(&ctx.db, users::Entity).await?;
        Ok(())
    }

}

数据填充(Seeding)

use loco_rs::testing::prelude::*;

#[tokio::test]
#[serial]
async fn is_user_exists() {
    configure_insta!();

    let boot = boot_test::<App, Migrator>().await;
    seed::<App>(&boot.app_context).await.unwrap();
    assert!(get_user_by_id(1).ok());

}

本文档深入指导了如何利用 Loco 的测试助手,涵盖数据库清理、快照测试的数据清理以及测试的数据填充。

快照测试数据清理

快照测试通常涉及将数据结构与动态字段(如 created_dateidpid 等)进行比较。为了确保快照的一致性,Loco 定义了一个常量数据列表,其中包含正则表达式替换。这些替换可以将动态数据替换为占位符。

使用 insta 进行快照的示例。

在以下示例中,您可以使用 cleanup_user_model 清理所有用户模型数据。

use loco_rs::testing::prelude::*;

#[tokio::test]
#[serial]
async fn can_create_user() {
    request::<App, Migrator, _, _>(|request, _ctx| async move {
        // 创建用户测试
        with_settings!({
            filters => cleanup_user_model()
        }, {
            assert_debug_snapshot!(current_user_request.text());
        });
    })
    .await;
}

您还可以直接使用清理常量,以 CLEANUP_ 开头。