快速上手

未匹配的标注

在本指南中,我们将介绍 CRUD 的每个部分的一些简单示例,CRUD 代表 创建-读取-更新-删除。本指南中的每一步都将在前一步的基础上进行。

在我们开始之前,请确保您已经安装并运行了 PostgreSQL 。如果您正在使用一些不同的数据库,例如SQLite,则某些示例将不会运行,因为实现的 API 可能会有所不同。在项目存储库中,您可以找到每个受支持数据库的各种 示例

关于 Rust 版本的一个说明:

Diesel 需要 Rust 1.65 或更高版本。如果您遵循本指南,请通过运行 rustup update stable 来确保您至少使用了那个版本的 Rust。

初始化一个新项目

我们需要做的第一件事是生成我们的项目

  • 生成一个新项目
    cargo new --lib diesel_demo
    cd diesel_demo
    首先,让我们将 Diesel 添加到我们的依赖项中。我们还将使用一个名为 .env 的工具来管理我们的环境变量。我们还将把它添加到我们的依赖项中。

cargo.toml

[dependencies]
diesel = { version = "2.1.0", features = ["postgres"] }
dotenvy = "0.15"

安装 Diesel 命令 (CLI)

Diesel 提供了一个单独的 CLI 工具来帮助管理您的项目。由于它是一个独立的二进制文件,不会直接影响您的项目代码,因此我们不会将其添加到 Cargo.toml 中。相反,我们只是把它安装在我们的系统上。

cargo install diesel_cli

为你的项目安装 Diesel

我们需要告诉 Diesel 在哪里可以找到我们的数据库。我们通过设置 DATABASE_URL 环境变量来实现这一点。在我们的开发机器上,我们可能会有多个项目在进行,而且我们不想污染我们的环境。我们可以将 url 放在 .env 文件中。

echo DATABASE_URL=postgres://username:password@localhost/diesel_demo > .env

现在,Diesel CLI可以为我们设置一切。

diesel setup

这将创建我们的数据库(如果它还不存在的话),并创建一个空的迁移目录,我们可以使用它来管理我们的模式(稍后会详细介绍)。

现在,我们将编写一个小型 CLI,使我们能够管理博客(忽略了我们只能从该 CLI 访问数据库的事实 …)。我们首先需要的是一个表格来存储我们的帖子。让我们为其创建一个迁移:

diesel migration generate create_posts

Diesel CLI将在所需的结构中为我们创建两个空文件。您将看到如下输出:

Creating migrations/20160815133237_create_posts/up.sql
Creating migrations/20160815133237_create_posts/down.sql

迁移使我们能够随着时间的推移不断发展数据库模式。每个迁移都可以应用(up.sql)或恢复(down.sql)。应用和附加的恢复迁移应该保持数据库架构不变。

接下来,我们将编写迁移的SQL:
up.sql

CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title VARCHAR NOT NULL,
  body TEXT NOT NULL,
  published BOOLEAN NOT NULL DEFAULT FALSE
)

down.sql

DROP TABLE posts

我们可以应用新的迁移:

diesel migration run

最好确保 down.sql 是正确的。您可以通过运行重置迁移来快速确认您的 down.sql 是否正确回滚迁移 :

diesel migration redo

写 rust 代码

好了,足够的SQL,让我们写一些Rust。我们将首先编写一些代码来显示最近发布的五篇文章。我们需要做的第一件事是建立数据库连接。
src/lib.rs

use diesel::pg::PgConnection;
use diesel::prelude::*;
use dotenvy::dotenv;
use std::env;

pub fn establish_connection() -> PgConnection {
    dotenv().ok();

    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    PgConnection::establish(&database_url)
        .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}

我们还想创建一个 Post struct,我们可以将数据读取到该 struct 中,并让 diesel 生成用于在查询中引用表和列的名称。

我们将添加以下行到 src/lib.rs 文件的开始部分:

pub mod models;
pub mod schema;

接下来,我们需要创建刚刚声明的两个模块。
src/models.rs

use diesel::prelude::*;

#[derive(Queryable, Selectable)]
#[diesel(table_name = crate::schema::posts)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}

#[derive(Queryable)] 将生成从 SQL 查询加载 Post struct 所需的所有代码。 #[derive(Selectable)]将生成如下含义的代码,根据您的模型类型,基于通过 #[disesel(table_name=...)]定义的表,构建匹配的 select 子句, 根据#[diesel(check_for_backend(...))添加额外的编译时检查,以验证结构中的所有字段类型是否与其相应的SQL端表达式相兼容。 这部分是可选的,但它大大改进了生成的编译器错误的信息。

通常,模式模块不是手动创建的,而是由diesel CLI生成的。当我们运行 diesel setup 时,
diesel.toml 文件配置中定义的 src/schema.rs 的文件会被创建。该文件应该如下所示
src/schema.rs

// @generated automatically by Diesel CLI.

diesel::table! {
    posts (id) {
        id -> Int4,
        title -> Varchar,
        body -> Text,
        published -> Bool,
    }
}

确切的输出可能会因数据库的不同而略有不同,但应该是等效的。

table! 宏基于数据库模式创建一组代码来表示所有的表和列。我们将在下一个例子中看到如何准确地使用它。要深入了解生成的代码,请参阅深度模式指南。

每当我们运行或恢复迁移时,此文件都会自动更新。

字段顺序的一点说明

使用 #[drivee(Queryable)] 假设 Post struct 上字段的顺序与 posts 表中的列匹配,因此请确保按照 schema.rs 文件中的顺序定义它们。将 #[drive(Selectable)]SelectableHelper::as_select 组合使用可确保字段顺序始终匹配。

让我们编写代码来实际显示我们的帖子。
src/bin/show_posts.rs

use self::models::*;
use diesel::prelude::*;
use diesel_demo::*;

fn main() {
    use self::schema::posts::dsl::*;

    let connection = &mut establish_connection();
    let results = posts
        .filter(published.eq(true))
        .limit(5)
        .select(Post::as_select())
        .load(connection)
        .expect("Error loading posts");

    println!("Displaying {} posts", results.len());
    for post in results {
        println!("{}", post.title);
        println!("-----------\n");
        println!("{}", post.body);
    }
}

这里使用 self::...::* 来导入一些别名, 这样我们就可以叫 posts 而不是叫 posts::table,以及 published 而不是 posts::published。 当我们只处理一张表时,这很有用,但这并不总是我们想要的。 将导入保持为 self::..* 始终很重要,在当前函数内部,以防止污染模块命名空间。

我们可以通过 cargo run --bin show_posts 运行我们的脚本。不幸的是,结果不会非常顺利,因为我们在数据库中实际上没有任何帖子。尽管如此,我们已经编写了相当多的代码,所以让我们提交。

关于这个例子的完整代码,可以查看 这里.

接下来,让我们编写一些代码来创建一个新的帖子。我们需要一个结构来插入新记录。
src/models.rs

use crate::schema::posts;

#[derive(Insertable)]
#[diesel(table_name = posts)]
pub struct NewPost<'a> {
    pub title: &'a str,
    pub body: &'a str,
}

现在让我们添加一个函数来保存一个新帖子。
src/lib.rs

use self::models::{NewPost, Post};

pub fn create_post(conn: &mut PgConnection, title: &str, body: &str) -> Post {
    use crate::schema::posts;

    let new_post = NewPost { title, body };

    diesel::insert_into(posts::table)
        .values(&new_post)
        .returning(Post::as_returning())
        .get_result(conn)
        .expect("Error saving new post")
}

当我们在新增和更新时调用 .get_result, 它自动将 RETURNING* 添加到查询语句的末尾,并允许我们将其加载到任何为正确实现 Queryable 的 struct 中。整洁的!

关于 RETURNING 的一点提示

并非所有数据库都支持 RETURNING 子句。在支持 RETURNING 子句的后端(如PostgreSQL 和 SQLite ),我们也可以从插入中获取数据。在 SQLite 后端,自 3.35.0 版本起就支持 RETURNING 。若要启用 RETURNING 子句,请添加 returning_clauses_for_sqlite_3_35功能标志 。MySQL不支持 RETURNING 子句。要取回所有插入的行,我们可以调用 .get_results 而不是 .execute。如果您在不同的数据库系统上遵循本指南,请确保签出特定于您的数据库系统的示例。

Diesel可以在单个查询中插入多个记录。只需将“Vec”或切片传递给 insert_into, 然后调用 get_results 来代替 get_result。 如果您实际上不想对刚刚插入的行执行任何操作, 调用 .execute 即可. 那样的话,编译器不会抱怨你的 :)

现在我们已经设置好了一切,我们可以创建一个小脚本来写一篇新文章。
src/bin/write_post.rs

use diesel_demo::*;
use std::io::{stdin, Read};

fn main() {
    let connection = &mut establish_connection();

    let mut title = String::new();
    let mut body = String::new();

    println!("What would you like your title to be?");
    stdin().read_line(&mut title).unwrap();
    let title = title.trim_end(); // Remove the trailing newline

    println!(
        "\nOk! Let's write {} (Press {} when finished)\n",
        title, EOF
    );
    stdin().read_to_string(&mut body).unwrap();

    let post = create_post(connection, title, &body);
    println!("\nSaved draft {} with id {}", title, post.id);
}

#[cfg(not(windows))]
const EOF: &str = "CTRL+D";

#[cfg(windows)]
const EOF: &str = "CTRL+Z";

我们可以使用 cargo run--bin write_post 来运行我们的新脚本。继续写一篇博客。发挥创意!这是我自己的:

   Compiling diesel_demo v0.1.0 (file:///Users/sean/Documents/Projects/open-source/diesel_demo)
     Running `target/debug/write_post`

What would you like your title to be?
Diesel demo

Ok! Let's write Diesel demo (Press CTRL+D when finished)

You know, a CLI application probably isn't the best interface for a blog demo.
But really I just wanted a semi-simple example, where I could focus on Diesel.
I didn't want to get bogged down in some web framework here.

Saved draft Diesel demo with id 1

不幸的是, 运行 show_posts 仍然不会显示我们的新帖子, 因为我们把它保存为草稿。 如果我们回顾 show_posts 中的代码, 我们添加 .filter(published.eq(true)), 并且在迁移过程中,我们将默认值 published 设置为 false。我们需要发布它! 但为了做到这一点,我们需要研究如何更新现有记录。首先让我们提交一下代码. 这个 demo 的代码可以在 这里 看到 .

现在我们已经完成了创建和读取,更新实际上相对简单。让我们直接进入脚本:
src/bin/publish_post.rs

use self::models::Post;
use diesel::prelude::*;
use diesel_demo::*;
use std::env::args;

fn main() {
    use self::schema::posts::dsl::{posts, published};

    let id = args()
        .nth(1)
        .expect("publish_post requires a post id")
        .parse::<i32>()
        .expect("Invalid ID");
    let connection = &mut establish_connection();

    let post = diesel::update(posts.find(id))
        .set(published.eq(true))
        .returning(Post::as_returning())
        .get_result(connection)
        .unwrap();
    println!("Published post {}", post.title);
}

就这些,让我们运行起来 cargo run --bin publish_post 1.

Compiling diesel_demo v0.1.0 (file:///Users/sean/Documents/Projects/open-source/diesel_demo)
   Running `target/debug/publish_post 1`
Published post Diesel demo

此外,让我们实现获取单个帖子的可能性。我们将显示带有标题的帖子的id. 注意调用 .optional(). 这将返回 Option<Post>,而不是抛出错误,然后我们可以在模式匹配中使用该错误. 有关修改构造的select语句的其他方法,请参阅 QueryDsl文档
src/bin/get_post.rs

use self::models::Post;
use diesel::prelude::*;
use diesel_demo::*;
use std::env::args;

fn main() {
    use self::schema::posts::dsl::posts;

    let post_id = args()
        .nth(1)
        .expect("get_post requires a post id")
        .parse::<i32>()
        .expect("Invalid ID");

    let connection = &mut establish_connection();

    let post = posts
        .find(post_id)
        .select(Post::as_select())
        .first(connection)
        .optional(); // This allows for returning an Option<Post>, otherwise it will throw an error

    match post {
        Ok(Some(post)) => println!("Post with id: {} has a title: {}", post.id, post.title),
        Ok(None) => println!("Unable to find post {}", post_id),
        Err(_) => println!("An error occured while fetching post {}", post_id),
    }
}

We can see our post with cargo run --bin get_post 1.

 Compiling diesel_demo v0.1.0 (file:///Users/sean/Documents/Projects/open-source/diesel_demo)
   Running `target/debug/get_post 1`
   Post with id: 1 has a title: Diesel demo

And now, finally, we can see our post with cargo run --bin show_posts.

     Running `target/debug/show_posts`
Displaying 1 posts
Diesel demo
----------

You know, a CLI application probably isn't the best interface for a blog demo.
But really I just wanted a semi-simple example, where I could focus on Diesel.
I didn't want to get bogged down in some web framework here.
Plus I don't really like the Rust web frameworks out there. We might make a
new one, soon.

We’ve still only covered three of the four letters of CRUD though. Let’s show how to delete things. Sometimes we write something we really hate, and we don’t have time to look up the ID. So let’s delete based on the title, or even just some words in the title.
src/bin/delete_post.rs

use diesel::prelude::*;
use diesel_demo::*;
use std::env::args;

fn main() {
    use self::schema::posts::dsl::*;

    let target = args().nth(1).expect("Expected a target to match against");
    let pattern = format!("%{}%", target);

    let connection = &mut establish_connection();
    let num_deleted = diesel::delete(posts.filter(title.like(pattern)))
        .execute(connection)
        .expect("Error deleting posts");

    println!("Deleted {} posts", num_deleted);
}

We can run the script with cargo run --bin delete_post demo (at least with the title I chose). Your output should look something like:

   Compiling diesel_demo v0.1.0 (file:///Users/sean/Documents/Projects/open-source/diesel_demo)
     Running `target/debug/delete_post demo`
Deleted 1 posts

When we try to run cargo run --bin show_posts again, we can see that the post was in fact deleted. This barely scratches the surface of what you can do with Diesel, but hopefully this tutorial has given you a good foundation to build off of. We recommend exploring the API docs to see more. The final code for this tutorial can be found here.

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~