Skip to content

Latest commit

 

History

History
317 lines (255 loc) · 8.69 KB

09_sea_orm_creating_models_and_entities.md

File metadata and controls

317 lines (255 loc) · 8.69 KB

Sea-ORM: Creating Models and Entities

The first model we are going to create is User. To create the migration for the User model, run this command,

sea-orm-cli migrate generate create_user_table

The general command is basically,

sea-orm-cli migrate generate <name_of_your_migration>

Here, we chose create_user_table but you can choose whatever is meaningful to you.

After running the above command, we should have a new migration file inside our migration crate. In my case it looked like this,

migration
├── Cargo.toml
├── README.md
└── src
    ├── lib.rs
    ├── m20220101_000001_create_table.rs
    ├── m20240822_150235_create_user_table.rs
    └── main.rs

Note: The m20240822_150235 part in m20240822_150235_create_user_table.rs is auto-generated. The m20240822 part is the current date in yyyymmdd format starting with an m which is probably short for migration, and the 150235 part is probably the time in UTC when the migration is created. This is just a guess though, I haven't checked the documentation on this yet. So if you are curious, you might want to check that out.

Let's open up m20240822_150235_create_user_table.rs in our editor and let's see what it looks like,

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> {
        // Replace the sample below with your own migration scripts
        todo!();

        manager
            .create_table(
                Table::create()
                    .table(Post::Table)
                    .if_not_exists()
                    .col(pk_auto(Post::Id))
                    .col(string(Post::Title))
                    .col(string(Post::Text))
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        // Replace the sample below with your own migration scripts
        todo!();

        manager
            .drop_table(Table::drop().table(Post::Table).to_owned())
            .await
    }
}

#[derive(DeriveIden)]
enum Post {
    Table,
    Id,
    Title,
    Text,
}

We will remove the todo!()s, change the Post enum to User, update the fields for our User and then change the migration script.

First, let's update User,

#[derive(DeriveIden)]
enum User {
    Table,
    Id,
    Username,
    Firstname,
    Lastname,
    Email,
    IsActive,
    LastLogin,
    DateJoined,
    CreatedAt,
    UpdatedAt,
}

Now, let's update the up() function in our MigrationTrait. The down() function is fine as it is. Here's how the migration file looks like after all the changes,

use sea_orm_migration::prelude::*;

#[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(User::Table)
                    .if_not_exists()
                    .col(ColumnDef::new(User::Id).integer().not_null().auto_increment().primary_key())
                    .col(ColumnDef::new(User::Username).string().not_null().unique_key())
                    .col(ColumnDef::new(User::Firstname).string())
                    .col(ColumnDef::new(User::Lastname).string())
                    .col(ColumnDef::new(User::Email).string().unique_key().not_null())
                    .col(ColumnDef::new(User::IsActive).boolean().default(false))
                    .col(ColumnDef::new(User::LastLogin).date_time())
                    .col(ColumnDef::new(User::DateJoined).date_time())
                    .col(ColumnDef::new(User::CreatedAt).date_time())
                    .col(ColumnDef::new(User::UpdatedAt).date_time())
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(User::Table).to_owned())
            .await
    }
}

#[derive(DeriveIden)]
enum User {
    Table,
    Id,
    Username,
    Firstname,
    Lastname,
    Email,
    IsActive,
    LastLogin,
    DateJoined,
    CreatedAt,
    UpdatedAt,
}

To make sure our newly created migration runs with the default migration command, we need to add it to the list of migrations that needs to be applied. They are located in the migration/src/lib.rs file. Here's what it looked like before,

pub use sea_orm_migration::prelude::*;

mod m20220101_000001_create_table;

pub struct Migrator;

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
    fn migrations() -> Vec<Box<dyn MigrationTrait>> {
        vec![
            Box::new(m20220101_000001_create_table::Migration),
        ]
    }
}

At this moment, this file contains the example migration m20220101_000001_create_table.rs, that was created automatically by Sea-ORM when we ran the migration creation command. We can remove that from this list and also delete the corresponding migration file as we won't need that. After we add our own migration to this vec![], the lib.rs file looks like this,

pub use sea_orm_migration::prelude::*;

mod m20240913_193712_create_user_table;

pub struct Migrator;

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
    fn migrations() -> Vec<Box<dyn MigrationTrait>> {
        vec![
            Box::new(m20240913_193712_create_user_table::Migration),
        ]
    }
}

Our migration setup is complete. Now let's run the migration. Run the following command,

sea-orm-cli migrate up

If the migration runs without any issues, you should see something like this at the end,

Applying all pending migrations
Applying migration 'm20240913_193712_create_user_table'
Migration 'm20240913_193712_create_user_table' has been applied

This will result in the creation of a User table in our actix-fullstack database in our PostgreSQL database under the public schema. You can verify that with the command line if you are comfortable or use a tool like DataGrip (paid) or BeeKeeper Studio (free).

Now we should also create the entity crate,

sea-orm-cli generate entity -o entity/src

If these commands ran successfully, you should see two new directories in your project workspace and they would look like the following with their contents,

entity
└── src
    ├── mod.rs
    ├── prelude.rs
    └── user.rs

Notice that our entity crate doesn't have a Cargo.toml file. We need to create that manually.

Create a Cargo.toml file inside entity,

entity
├── Cargo.toml  # we just created this one
└── src
    ├── mod.rs
    ├── prelude.rs
    └── user.rs

Inside entity/Cargo.toml paste the following,

[package]
name = "entity"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
name = "entity"
path = "src/mod.rs"

[dependencies]
serde = { version = "1.0.125", features = ["derive"] }

[dependencies.sea-orm]
version = "0.12.15"

The entity/src/user.rs file will be generated automatically based on our migration. Let's see what's inside this file,

//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.0

use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "user")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    #[sea_orm(unique)]
    pub username: String,
    pub firstname: Option<String>,
    pub lastname: Option<String>,
    #[sea_orm(unique)]
    pub email: String,
    pub is_active: Option<bool>,
    pub last_login: Option<DateTime>,
    pub date_joined: Option<DateTime>,
    pub created_at: Option<DateTime>,
    pub updated_at: Option<DateTime>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

We need to add the entity and migration crates to our project as workspace members so that we can work with them. We also need to add them as dependencies to be able to reference them in our project. In the Cargo.toml of the main project, add in the following,

[workspace]
members = [".", "entity", "migration"]

[dependencies]
// other dependencies
entity = { path = "entity" }
migration = { path = "migration" }

We can now start using the User model in our application. Except for a rather important field I didn't remember to include and that is a password field. Let's add that and test it out.