davidpdrsn / juniper-eager-loading

Library for avoiding N+1 query bugs with Juniper
68 stars 8 forks source link

Mutation example #42

Closed ghost closed 4 years ago

ghost commented 4 years ago

I am trying to get this to work with mutations and OptionHasOne, but I do not really understand how to proceed. I am just starting to learn Rust and so far your projects and documentation have been a great help. Can you point me in the right direction?

What do I need to return at country: Default::default() ?

use juniper::{FieldResult};
use crate::graphql::{Context, MutationFields};
use juniper::Executor;
use juniper_from_schema::{QueryTrail, Walked};
use crate::graphql::models::profile::Profile;
use crate::db::models::profile::{Profile as ProfileModel};
use crate::db::models::country::Country;
use crate::graphql::ProfileInput;
use log::info;

pub struct Mutation;

impl MutationFields for Mutation {
    fn field_update_profile(
        &self,
        executor: &Executor<'_, Context>,
        _trail: &QueryTrail<'_, Profile, Walked>,
        data: ProfileInput
    ) 
        -> FieldResult<Profile> {

        let ctx = &executor.context();
        let conn = &ctx.db();

        let name = &data.name;
        let country_code = &data.country_code;

        info!("Updating profile {:?} {:?}", name, country_code);

        if let Some(code) = country_code {
            let result = Country::find_by_country_code(conn, code.to_string());
            if let Some(country) = result {
                info!("Updating country {:?}", country);
            }
        }

        Ok(Profile {
            profile: (ProfileModel {
                id: 0,
                name: name.to_string(),
                country_id: 0
            }),
            country: Default::default()
        })
    }
}
davidpdrsn commented 4 years ago

Mutation fields are very similar to query fields. However before returning the data you make some kind of state change.

So after updating the profile in whatever way is necessary you have to use EagerLoadAllChildren::eager_load_all_children_for_each to load the data you have to return. That should be very similar to field_all_users shown here.

ghost commented 4 years ago

Hi David, Thanks. In my case I only want to return the edited profile with the country.

fn map_models_to_graphql_nodes<'a, T, M: Clone>(
    models: &[M],
    trail: &QueryTrail<'a, T, Walked>,
    ctx: &Context,
) -> Result<Vec<T>, diesel::result::Error>
    where
        T: EagerLoadAllChildren
        + GraphqlNodeForModel<Model=M, Context=Context, Error=diesel::result::Error>,
{
    let mut users = T::from_db_models(models);
    T::eager_load_all_children_for_each(&mut users, models, ctx, trail)?;
    Ok(users)
}

This gives me back a Vec with in my case all profiles. Does that make sense?

You can find my code here

davidpdrsn commented 4 years ago

You can make a vec containing only the one profile, then call Vec::remove(0) to get it out after eager loading.

Something like:

fn field_user(
    &self,
    executor: &Executor<'_, Context>,
    trail: &QueryTrail<'_, User, Walked>,
    id: ID,
) -> FieldResult<User> {
    use crate::schema::users;
    let ctx = &executor.context();
    let con = &ctx.db();

    let id = id.parse::<i32>()?;

    let user_models = users::table
        .filter(users::id.eq(id))
        .load::<models::User>(*con)?;
    let mut users = User::from_db_models(&user_models);

    User::eager_load_all_children_for_each(&mut users, &user_models, ctx, trail)?;

    Ok(users.remove(0))
}

If your schema is

type Query {
  user(id: ID!): User! @juniper(ownership: "owned")
}
ghost commented 4 years ago

Thanks David, that did the trick. I can expand from that.