5 个版本
0.2.2 | 2024 年 8 月 14 日 |
---|---|
0.2.1 | 2023 年 12 月 4 日 |
0.2.0 | 2023 年 4 月 18 日 |
0.1.1 | 2023 年 1 月 25 日 |
0.1.0 | 2023 年 1 月 24 日 |
#106 在 开发工具
每月 265 次下载
37KB
486 代码行
cder
Rust 轻量级、简单的数据库种子工具
cder (see-der) 是一个数据库种子工具,帮助您在本地环境中导入 fixture 数据。
以编程方式生成种子是一个简单的任务,但维护它们却不是。每次当您的模式发生变化时,您的种子可能会被破坏。这需要您的团队付出额外的努力来保持它们更新。
使用 cder,您可以
- 以可读的格式维护数据,与种子程序分离
- 使用 内嵌标签 在线处理引用完整性
- 重用现有的结构和插入函数,只需要很少的粘合代码
cder 没有数据库交互机制,因此它可以与任何类型的 ORM 或数据库包装器(例如 sqlx)一起工作,您的应用程序已经拥有。
这种内嵌标签机制是受 Ruby on Rails 为测试数据生成提供的 fixtures 启发的。
安装
# Cargo.toml
[dependencies]
cder = "0.2"
用法
快速入门
假设您有用户表作为种子目标
CREATE TABLE
users (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` VARCHAR(255) NOT NULL,
`email` VARCHAR(255) NOT NULL,
)
在您的应用程序中您还有
- 一个类型为
<T>
的结构(通常是模型,基于底层表构建) - 数据库插入方法:返回新记录的 id:
Fn(T) -> Result<i64>
首先,在结构体上添加DeserializeOwned特性。(cder引入了serde作为依赖,因此可以使用derive(Deserialize)
宏来完成这项工作)
use serde::Deserialize;
#[derive(Deserialize)] // add this derive macro
User {
name: String,
email: String,
}
impl User {
// can be sync or async functions
async fn insert(&self) -> Result<(i64)> {
//
// inserts a corresponding record into table, and returns its id when succeeded
//
}
}
您的用户种子由两个独立的文件定义,数据和胶水代码。
现在创建一个种子数据文件'fixtures/users.yml'。
# fixtures/users.yml
User1:
name: Alice
email: '[email protected]'
User2:
name: Bob
email: '[email protected]'
现在您可以将上述两个用户插入到数据库中。
use cder::DatabaseSeeder;
async fn populate_seeds() -> Result<()> {
let mut seeder = DatabaseSeeder::new()
seeder
.populate_async("fixtures/users.yml", |input| {
async move { User::insert(&input).await }
})
.await?;
Ok(())
}
瞧!您将在数据库中填充记录Alice
和Bob
。
与非异步函数一起工作
如果您的函数是非异步(普通)函数,请使用Seeder::populate
而不是Seeder::populate_async
。
use cder::DatabaseSeeder;
fn main() -> Result<()> {
let mut seeder = DatabaseSeeder::new();
seeder
.populate("fixtures/users.yml", |input| {
// this block can contain any non-async functions
// but it has to return Result<i64> in the end
diesel::insert_into(users)
.values((name.eq(input.name), email.eq(input.email)))
.returning(id)
.get_result(conn)
.map(|value| value.into())
})
Ok(())
}
构建实例
如果您想在插入之前对反序列化的结构体有更细粒度的控制,请使用StructLoader。
use cder::{ Dict, StructLoader };
fn construct_users() -> Result<()> {
// provide your fixture filename followed by its directory
let mut loader = StructLoader::<User>::new("users.yml", "fixtures");
// deserializes User struct from the given fixture
// the argument is related to name resolution (described later)
loader.load(&Dict::<String>::new())?;
let customer = loader.get("User1")?;
assert_eq!(customer.name, "Alice");
assert_eq!(customer.email, "[email protected]");
let customer = loader.get("User2")?;
assert_eq!(customer.name, "Bob");
assert_eq!(customer.email, "[email protected]");
ok(())
}
动态定义值
cder根据一些规则替换了某些标签的值。这种'预处理'在反序列化之前运行,因此您可以定义依赖于您本地环境的动态值。
目前涵盖了以下两种情况
1. 定义关系(外键)
假设您有两条记录要插入到companies
表中。companies.id
是未知的,因为它们在插入时由本地数据库提供。
# fixtures/companies.yml
Company1:
name: MassiveSoft
Company2:
name: BuggyTech
现在您有引用这些公司的用户记录
# fixtures/users.yml
User1:
name: Alice
company_id: 1 // this might be wrong
您可能会在构建User1时失败,因为Company1不保证id=1(尤其是在您已经操作过companies表的情况下)。为此,请使用${{ REF(label) }}
标签替换未定的值。
User1:
name: Alice
company_id: ${{ REF(Company1) }}
现在,Seeder是如何知道Company1记录的id的?如前所述,给 Seeder 的块必须返回Result<i64>
。Seeder将结果值映射到记录标签,稍后将重新使用它来解决标签引用。
use cder::DatabaseSeeder;
async fn populate_seeds() -> Result<()> {
let mut seeder = DatabaseSeeder::new();
// you can specify the base directory, relative to the project root
seeder.set_dir("fixtures");
// Seeder stores mapping of companies record label and its id
seeder
.populate_async("companies.yml", |input| {
async move { Company::insert(&input).await }
})
.await?;
// the mapping is used to resolve the reference tags
seeder
.populate_async("users.yml", |input| {
async move { User::insert(&input).await }
})
.await?;
Ok(())
}
注意事项
- 先插入包含'引用'记录的文件(如上面的示例中的'companies'),然后再插入'引用'记录(如'users')。
- 目前 Seeder 在读取源文件时解析标签。这意味着您不能在同一个文件中有对记录的引用。如果您想从一个文件中引用用户记录,可以通过将yaml文件分成两部分来实现。
2. 环境变量
您还可以使用${{ ENV(var_name) }}
语法来引用环境变量。
Dev:
name: Developer
email: ${{ ENV(DEVELOPER_EMAIL) }}
如果定义了该环境变量,则电子邮件将被替换为DEVELOPER_EMAIL
。
如果您更喜欢使用默认值,请使用(类似shell的)语法
Dev:
name: Developer
email: ${{ ENV(DEVELOPER_EMAIL:-"[email protected]") }}
如果没有指定默认值,所有指向未定义环境变量的标签都将简单地替换为空字符串""。
数据表示
cder根据serde-yaml反序列化yaml数据,支持强大的serde序列化框架。使用serde,您可以反序列化几乎任何结构体。您可以看到一些示例结构体,它们具有各种属性和yaml文件,可以用作它们的种子。
以下是一些所需的YAML格式的要点。有关更多详细信息,请查看serde-yaml的GitHub页面。
基础
Label_1:
name: Alice
email: '[email protected]'
Label_2:
name: Bob
email: '[email protected]'
请注意,cder要求每个记录都必须有标签(Label_x)。标签可以是任何东西(只要它是有效的yaml键),但您可能希望保持它们唯一,以避免意外误引用。
枚举和复杂类型
可以使用YAML的!标签
反序列化枚举。假设您有一个名为CustomerProfile的结构体,其中包含枚举Contact
。
struct CustomerProfile {
name: String,
contact: Option<Contact>,
}
enum Contact {
Email { email: String }
Employee(usize),
Unknown
}
您可以根据以下方式生成具有每种联系类型的客户:
Customer1:
name: "Jane Doe"
contact: !Email { email: "[email protected]" }
Customer2:
name: "Uncle Doe"
contact: !Employee(10100)
Customer3:
name: "John Doe"
contact: !Unknown
不推荐用于生产环境
cder旨在填充开发(或可能测试)环境中的种子。不建议用于生产。
许可
该项目根据MIT许可证作为开源软件提供。
贡献
除非您明确声明,否则您提交的任何有意提交以包含在此crate中的贡献,均应按MIT许可证授权,不附加任何额外条款或条件。
欢迎在GitHub上提交错误报告和pull请求:https://github.com/estie-inc/cder
依赖项
~4–6MB
~114K SLoC