thedodd / wither

An ODM for MongoDB built on the official MongoDB Rust driver.
https://docs.rs/wither
Other
325 stars 40 forks source link

Create a custom derive for model definitions. #12

Closed thedodd closed 5 years ago

thedodd commented 6 years ago

There were a few things mentally blocking me on this at first.

Now that I've been able to think about it a bit, I'm thinking that a solid path forward will be to use a custom derive for all of the core components, and then allow users to optionally implement a new Migrate trait on their models, which is where the model's migrations will be defined.

Deriving Model on your structs will give you a default implementation of sync, so that you can sync your indices with the database. If you choose to also impl Migrate on your struct, you will get a default implementation of sync_and_migrate, which will call the default sync first, and then execute the migrations.

thedodd commented 6 years ago

Should have a path forward now. Need to do some hacking on this when I have time. Ideally we'll be able to have a model definition like this:

#[derive(Model)] // This will add `Serialize` & `Deserialize` if not present.
struct User {
    // Must be Option<T> & will also add needed serde bits for
    // `skip_serializing_if="Option::is_none"`.
    #[model(id)]
    id: Option<ObjectId>,

    // For a simple index on a single field, specify direction (asc,dsc) & named arguments.
    // All of the other index options will take on their default when left unspecified.
    // Index direction must be first argument.
    #[model(index: dsc, background=true, unique=true)]
    email: String,

    // A stupid example, but remove this document after 5 min.
    #[model(index: asc, background=true, ttl=300)] // Might allow simple arithmetic here for ease of use.
    unverified_since: Option<DateTime<Utc>>,

    // Compound indices will have a slightly more complex invocation.
    // The first field's direction will be the first argument, as normal.
    // But then the other fields of the compound index must be presented inside of braces. Simple.
    // Then the remaining named arguments will follow.
    #[model(index: asc, {compound1: asc, compound2: dsc},  background=true)]
    compound0: u64,
    compound1: i64,
    compound2: String,
}

Need to reflect a bit more on this when I have time, but this may be a decent starting point to shoot for.

thedodd commented 6 years ago

Some thoughts on what the new migration pattern will look like.

#[derive(Model)]
struct User {
    ...
}

impl Migrate for User {
    fn migrations() -> Vec<Box<Migration>> {
        // Define migrations here.
    }
}

fn main() {
    // Just sync indices.
    User::sync(db).expect("Expected sync to succeed.");
    // Sync indices & execute migrations.
    User::sync_and_migrate().expect("Expected sync and migrations to succeed.");
}