RustにはSeaORMというとても使いやすいORMがあります。
SeaORMのDocsは非常に読みやすく、Rustにある程度慣れている人であれば容易に使用することができると思います。しかし、ActiveEnumの説明箇所が個人的にわかりにくく(私の読解力がないだけです。)、かなりの時間を浪費してしまったので、備忘録として書き記します。
ActiveEnumの使い方
早速使い方を書いていきます。
この記事の内容のレポジトリ
プログラムを直接読みたい方は、この記事の内容のレポジトリを用意しているので、以下のリンクからご覧ください。
環境
OS: Zorin OS 17.1 x86_64
DB: PostgreSQL (Dockerを使用)
rustc: 1.80.1
cargo: 1.80.1
sea-orm-cliの導入
cargo install sea-orm-cli
projectの作成
今回は、sampleというプロジェクト名で作成します。
cargo new sample
cd sample
docker-compose.yamlを用意
今回はPostgreSQLを使用します。
version: "3.8"
services:
postgres:
container_name: sample_db
image: postgres:latest
volumes:
- ./db:/var/lib/postgresql
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- "5433:5432"
SeaORMを導入
SeaORMのバージョンは、1.0.0にしてください。
cargo add sea-orm@=1.0.0
migrationフォルダを初期化
sea-orm-cli migrate init
project内のディレクトリ構造が以下のようになっていれば、成功です。
$ tree
.
├── Cargo.lock
├── Cargo.toml
├── docker-compose.yaml
├── migration
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── lib.rs
│ ├── m20220101_000001_create_table.rs
│ └── main.rs
├── src
│ └── main.rs
└── target
├── CACHEDIR.TAG
(以下略)
テーブルの定義
今回は以下のようなテーブルを作成します。
Category columnでActiveEnumを使用します。
ActiveEnumを使用するCategory columnは、Stringとして定義します。
// migration/src/m20220101_000001_create_table.rs
use sea_orm_migration::{prelude::*, schema::*};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Post::Table)
.if_not_exists()
.col(
ColumnDef::new(Post::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Post::Title).string().not_null())
.col(ColumnDef::new(Post::Content).string().not_null())
.col(ColumnDef::new(Post::Category).string().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Post::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
enum Post {
Table,
Id,
Title,
Content,
Category,
}
migration/Cargo.toml
のfeatures
を以下のように編集します。
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
async-std = { version = "1", features = ["attributes", "tokio1"] }
[dependencies.sea-orm-migration]
version = "1.0.0"
features = [
"runtime-tokio-native-tls", # `ASYNC_RUNTIME` feature
"sqlx-postgres", # `DATABASE_DRIVER` feature
]
docker-compose up -d
コンテナを起動します。
docker-compose up -d
Migrationの実行
以下のコマンドでMigrationを実行します。
DATABASE_URL="postgres://postgres:postgres@localhost:5433/test_db" sea-orm-cli migrate refresh
Entityの生成
以下のコマンドでEntityを生成します。
sea-orm-cli generate entity \
-u postgres://postgres:postgres@localhost:5433/test_db \
-o entity/src
Cargo.tomlの編集
entity/Cargo.toml
を作成し、以下のように編集します。
[package]
name = "entity"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "entity"
path = "src/mod.rs"
[dependencies.sea-orm]
version = "1.0.0"
プロジェクトの直下にあるCargo.toml
を以下の内容に編集します。
[package]
name = "sample"
version = "0.1.0"
edition = "2021"
[workspace]
members = [".", "entity", "migration"]
[dependencies]
entity = { path = "entity" }
migration = { path = "migration" }
sea-orm = { version = "=1.0.0", features = [ "sqlx-postgres", "runtime-tokio-native-tls", "macros" ] }
enumの定義
entity/src/post.rs
にCategoryのenumを定義します。
enum名は、CategoryEnum
にしています。Model構造体のcategoryをString
からCategoryEnum
に変更することを忘れないようにしましょう。
// entity/src/post.rs
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0
use sea_orm::entity::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
pub enum CategoryEnum {
#[sea_orm(string_value = "NixOS")]
NixOS,
#[sea_orm(string_value = "ZorinOS")]
ZorinOS,
#[sea_orm(string_value = "ArchLinux")]
ArchLinux,
#[sea_orm(string_value = "Windows")]
Windows,
}
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "post")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub title: String,
pub content: String,
pub category: CategoryEnum,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
これで、Rust側ではcategory
はCategoryEnum
のみを受け入れるようになりました!
CRUD操作
tokioを導入します。
cargo add tokio -F full
雑なプログラムですが、CRUD操作も書き記しておきます。
// src/main.rs
use ::entity::post::{self, CategoryEnum, Entity as Post};
use sea_orm::{
ActiveModelTrait, ColumnTrait, Database, DatabaseConnection, EntityTrait, ModelTrait,
QueryFilter, Set,
};
#[tokio::main(flavor = "current_thread")]
async fn main() {
// connnect database
let db: DatabaseConnection =
Database::connect("postgres://postgres:postgres@localhost:5433/test_db")
.await
.expect("Failed to connect to db");
//insert
let post_model: post::ActiveModel = post::ActiveModel {
title: Set("Title".to_string()),
content: Set("Content".to_string()),
category: Set(CategoryEnum::NixOS),
..Default::default()
};
let post: sea_orm::InsertResult<post::ActiveModel> =
Post::insert(post_model).exec(&db).await.unwrap();
println!("Inserted: {:?}", post);
//find
let post_data: Option<post::Model> = Post::find()
.filter(post::Column::Category.eq(CategoryEnum::NixOS))
.one(&db)
.await
.unwrap();
println!("Found post data\n{:#?}", post_data);
//update
match post_data {
Some(post_data) => {
let mut update_post_data: post::ActiveModel = post_data.into();
update_post_data.title = Set("Updated Title".to_string());
update_post_data.content = Set("Updated Content".to_string());
update_post_data.category = Set(CategoryEnum::Windows);
let updated_post: post::Model = update_post_data.update(&db).await.unwrap();
println!("Updated post data\n{:#?}", updated_post);
}
None => {
println!("No post data found");
}
}
//delete
let delete_post_data: Option<post::Model> = Post::find()
.filter(post::Column::Category.eq(CategoryEnum::Windows))
.one(&db)
.await
.unwrap();
match delete_post_data {
Some(delete_post_data) => {
let deleted_post: sea_orm::DeleteResult = delete_post_data.delete(&db).await.unwrap();
println!("Deleted post data\n{:?}", deleted_post);
}
None => {
println!("No post data found");
}
}
}
参考サイト