[Rust]SeaORMの基本とポイント
SeaORMはRustでWebサービスを構築するためのリレーショナルORMです。
SeaORMはdieselと異なりasyncに対応しているので、高速webフレームワークActixでも利用できます。
将来性を感じる非常に良いライブラリなのですが、日本語での説明があまりに少ないので、はまった箇所などを記載して残しておきます。
環境
- rust 1.64.0
- SeaORM 0.9.3
- postgres version 14
想定読者
- rustの基本を理解している。
- 他の言語でORMを経験している(railsのActiveRecordなど)。
SeaORMとは
asyncで利用可能なORMです。
migrationにも対応していて、テストコードも書きやすいのが特徴です。
学習コストは少し高めです。
postgresの型との対照表
初めてSeaORMを利用する時に苦労するのが型定義だと思います。
なぜなら、migrationファイルで設定する型と、entityのrustで設定する型で違いがあるからです。
ここでは、基本的な型についての対応表を記載しておきます。
seaORM | rust | postgres |
---|---|---|
integer() | i32 | integer |
string_len(512) | String | character varying(512) |
timestamp_with_time_zone() | DateTimeUtc | timestamp with time zone |
nullを許可するデータの場合、entityのrustではOptionを利用します。
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)]
#[sea_orm(table_name = "users")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = true)]
#[serde(skip_deserializing)]
pub id: i32,
pub first_name: Option<String>,
pub last_name: Option<String>,
pub display_name: Option<String>,
pub email: Option<String>,
pub birthday: Option<String>,
pub gender: Option<i32>,
pub lang: Option<i32>,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub deleted_at: Option<DateTimeUtc>,
}
sqlログの出力
開発時はどんなsqlが出力されているか、ログを出力したくなるでしょう。
sea_ormでログを出力するには
tracing_subscriber
というcrateを入れる必要があります。
[dependencies]
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
実装は以下のようにします。
async fn main() -> std::io::Result<()> {
dotenv().ok();
std::env::set_var("RUST_LOG", "debug");
tracing_subscriber::fmt::init();
}
本番で出力しないように、環境変数で切り替えられるようにしておきましょう。
基本クエリ
基本クエリの使い方を紹介します。
IDデータ取得
find_by_idを利用します。
結果はResultで分岐します。
match Users::find_by_id(user_id).one(conn).await {
Ok(o) => match o {
Some(user) => {
let response = UserResponse {
id: user.id,
};
return HttpResponse::Ok().json(response);
}
None => return HttpResponse::NotFound().json("nou found user data"),
},
Err(e) => match e {
DbErr::RecordNotFound(_) => {
return HttpResponse::NotFound().json("erorr nou found user data")
}
_ => return HttpResponse::NotFound().json("error"),
},
};
where条件で絞り込む
filter(カラム).eq(値)を使います。
let address: Option<addresses::Model> = Addresses::find().filter(addresses::Column::Address.eq(publickey)).one(conn).await.unwrap();
上記は結果データが一意なので、
one
で1件のデータを取得しています。
Relation
ORMの利点はテーブルRelationの処理を便利にしてくれることです。
SeaORMも当然利用できます。
しかし、かなり癖があるので慣れるまで苦労するでしょう。
コツはER図を書いて、Relationをきちんと把握しながらコードに設定を落としていくことです。
Relationを貼るには、Relatedを実装する必要があります。
impl Related<super::addresses_users::Entity> for Entity {
fn to() -> RelationDef {
super::addresses_users::Relation::Addresses.def()
}
fn via() -> Option<RelationDef> {
Some(super::addresses_users::Relation::Users.def().rev())
}
}
ポイントは
impl Related<super::addresses_users::Entity> for Entity
の
super::addresses_users::Entity
の部分です。
ここでEntityを紐づけています。
このサンプルのコードでは
addresses_usersというmany to manyである中間テーブルのentityのrelationの設定をしています。
その他
他にハマった箇所と解決方法を簡単に紹介します。
versionエラー
SeaORMのversionを下げたら、エラーで動かなくなりました。
cargo clernで解決しました。
Discussion