模型
loco
中的模型指的是实体类,它使得数据库查询和写入变得容易,同时也方便进行迁移和数据填充(seeding)。
Sqlite vs Postgres
您可能在创建新应用时选择了 sqlite
,这是默认选项。Loco 允许您在 sqlite
和 postgres
之间 无缝 切换。
通常情况下,您可以使用 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") }"
loco:loco
用户名和密码,数据库名称为 myapp_development
。对于测试和生产环境,您的数据库名称应分别为 myapp_test
和 myapp_production
。
为了您的方便,这里有一个启动 Postgresql 数据库服务器的 docker 命令:
最后,您还可以使用 doctor 命令来验证您的连接:
)
胖模型,瘦控制器
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 添加功能:
制作模型
模型生成器
要添加新模型,模型生成器会创建一个迁移,运行它,然后从您的数据库模式触发实体同步,这将补充并创建您的模型实体。
$ cargo loco generate model posts title:string! content:text user:references
当通过迁移添加模型时,将提供以下默认字段:
created_at
(ts!):这是一个时间戳,指示您的模型何时创建。updated_at
(ts!):这是一个时间戳,指示您的模型何时更新。
如果您在迁移命令中提供了这些字段,则会忽略这些默认字段。
对于模式数据类型,您可以使用以下映射来理解模式:
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
使用 user:references
使用特殊的 references
类型,这将在 post
和 user
之间创建关系,并在 posts
表中添加 user_id
引用字段。
使用 approved_by:references:users
使用特殊的 references:<table>
类型,这将在 post
和 user
之间创建关系,并在 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___
- 示例:
AddUserRefToPosts
(User
并不重要,因为您可以单独指定一个或多个引用,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
添加了适当的代码)。
单独使用 down
命令只会回滚最后一次迁移。如果您想回滚多次迁移,您可以指定要回滚的迁移数量。
动词、单数和复数
- references: 对于表名使用单数,以及
:references
类型。user:references
(引用Users
),vote:references
(引用Votes
)。:references:<table>
也可用departing_train:references:trains
(引用Trains
)。 - 列名: 任何您喜欢的。首选
snake_case
。 - 表名: 复数,snake case。
users
,draft_posts
。 - 迁移名称: 任何可以作为文件名的名称,首选 snake case。
create_table_users
,add_vote_id_to_movies
。 - 模型名称: 为您自动生成。通常生成的名称是 PascalCase,复数。
Users
,UsersVotes
。
以下是一些示例,展示了命名约定:
- 模型名称为复数:
movies
- 引用 director 为单数:
director:references
- 引用 added_by 为单数,被引用的模型是一个模型且为复数:
added_by:references:users
- 列名使用 snake case:
long_title:string
编写迁移
要使用迁移 DSL,请确保您导入了以下 loco_rs::schema::*
和 SeaORM prelude
。
use *;
use *;
然后,创建一个结构体:
;
然后实现您的迁移(见下文)。
创建表
创建表,提供两个数组:(1)列(2)引用。
将引用留空以不创建任何引用字段。
创建连接表
将引用提供给第二个数组参数。使用空字符串 ""
表示您希望我们为您生成引用列名(例如,user
引用将暗示通过 group_users
中的 user_id
列连接 users
表)。
提供非空字符串以指示引用列名的特定名称。
添加列
添加单个列。您可以在单个迁移中使用任意数量的此类语句(以添加多列)。
编写高级迁移
直接使用 manager
可以让您在编写迁移时访问更高级的操作。
添加列
manager
.alter_table
.await
删除列
manager
.alter_table
.await
添加索引
您可以复制以下代码来添加索引
manager
.create_index
.await;
创建数据修复
在迁移中创建数据修复很容易 - 只需根据需要使用 SQL 语句:
async
话虽如此,您可以自行决定在以下位置编写数据修复代码:
task
- 您可以在其中使用高级模型migration
- 您可以在其中更改结构并使用原始 SQL 修复由此产生的数据- 或临时的
playground
- 您可以在其中使用高级模型或尝试各种操作
验证
我们在底层使用了 validator 库。首先,使用您需要的约束构建您的验证器,然后为您的 ActiveModel
实现 Validatable
。
请注意,Validatable
是您指示 Loco 提供哪个 Validator
以及如何从模型构建它的方式。
现在您可以在代码中无缝使用 user.validate()
,当它是 Ok
时,模型有效,否则您将在 Err(...)
中找到可供检查的验证错误。
关系
一对多
以下是如何将 Company
与现有 User
模型关联。
$ cargo loco generate model company name:string user:references
这将在 Company
中创建一个带有 user_id
字段的迁移,该字段将引用 User
。
多对多
以下是如何创建一个典型的 “votes” 表,该表将 User
和 Movie
与多对多连接表链接起来。请注意,它在模型生成器中使用了特殊的 --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_id
和 movie_id
组成的复合主键。由于它恰好有 2 个 ID,SeaORM 将其识别为多对多连接表,并生成具有适当 via()
关系的实体:
// User,新生成的实体,在 _entities/users.rs 中具有 `via` 关系
// ..
使用 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:
(
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)
);
强制字段包括 id
、password
、email
和 created_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 实现中:
- 导航到您应用的 Hook 实现。
- 在 seed 函数实现中添加 seed。以下是 Rust 中的示例:
此实现确保在调用 seed 函数时执行 seed。根据您应用的结构和需求调整具体细节。
通过 CLI 管理 Seed
- 重置数据库 在导入 seed 文件之前清除所有现有数据。当您想从全新的数据库状态开始时,这非常有用,确保不保留旧数据。
- 将数据库表转储到文件 将数据库表的内容导出到文件。此功能允许您备份数据库的当前状态或准备数据以在不同环境中重用。
要访问 seed 命令,请使用以下 CLI 结构:
)
使用测试
-
启用 testing 功能 (
testing
) -
在您的测试部分,按照以下示例操作:
use *;
async
多数据库
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
现在,您可以在控制器中使用辅助数据库:
use DatabaseConnection;
use ;
pub async
多数据库(多租户)
要连接两个以上不同的数据库,数据库配置应如下所示:
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
现在,您可以在控制器中使用多个数据库:
use DatabaseConnection;
use ;
use MultiDb;
pub async
测试
如果您使用生成器创建了模型迁移,那么您还应该在 tests/models/posts.rs
中有一个自动生成的模型测试(还记得我们生成了一个名为 post
的模型吗?)。
一个典型的测试包含设置测试数据、启动应用以及在测试代码运行之前自动重置数据库所需的一切。它看起来像这样:
use *;
async
为了简化测试过程,Loco
提供了有用的函数,使编写测试更加方便。确保在您的 Cargo.toml
中启用 testing 功能:
[]
= { = "*", = ["testing"] }
数据库清理
在某些情况下,您可能希望使用干净的数据集运行测试,确保每个测试都是独立的,并且不受先前数据的影响。要启用此功能,请在 config/test.yaml
文件中的 database 部分将 dangerously_truncate
选项修改为 true。此设置确保 Loco 在每个实现 boot app 的测试之前截断所有数据。
⚠️ 注意:使用此功能时要小心,以避免意外数据丢失,尤其是在生产环境中。
- 建议在运行所有相关任务时使用 serial crate。
- 要决定要截断哪些表,请将实体模型添加到 App hook:
;
数据填充(Seeding)
use *;
async
本文档深入指导了如何利用 Loco 的测试助手,涵盖数据库清理、快照测试的数据清理以及测试的数据填充。
快照测试数据清理
快照测试通常涉及将数据结构与动态字段(如 created_date
、id
、pid
等)进行比较。为了确保快照的一致性,Loco 定义了一个常量数据列表,其中包含正则表达式替换。这些替换可以将动态数据替换为占位符。
使用 insta 进行快照的示例。
在以下示例中,您可以使用 cleanup_user_model
清理所有用户模型数据。
use *;
async
您还可以直接使用清理常量,以 CLEANUP_
开头。