🦀

[Rust]SeaORMの基本とポイント

2022/12/08に公開

https://www.sea-ql.org/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を利用します。

entity/src/users.rs
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を入れる必要があります。

Cargo.toml
[dependencies]
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }

https://github.com/tokio-rs/tracing

実装は以下のようにします。

src/main.rs
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を実装する必要があります。

entity/src/users.rs
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())
    }
}

ポイントは

entity/src/users.rs
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