Search code examples
rustrust-diesel

diesel: inner_join forward query


I am learning to use inner_join for forward query


pub async fn hello(pool: web::Data<DbPool>) -> HttpResult {
    let conn = pool.get().map_err(ErrorInternalServerError)?;

    let data: Vec<(models::Book, models::User)> = books::table
        .inner_join(users::table)
        .load::<(models::Book, models::User)>(&conn)
        .map_err(ErrorInternalServerError)?;

    Ok(HttpResponse::Ok().json(data))
}

I get the correct data

[
    [{
        "id": 1,
        "name": "js",
        "user_id": 1
    }, {
        "id": 1,
        "username": "admin"
    }],
    [{
        "id": 2,
        "name": "rust",
        "user_id": 1
    }, {
        "id": 1,
        "username": "admin"
    }]
]

But the structure of this json data is not what I want

I want this nested structure

[{
    "id": 1,
    "name": "js",
    "user": {
        "id": 1,
        "username": "admin"
    }
}, {
    "id": 2,
    "name": "rust",
    "user": {
        "id": 1,
        "username": "admin"
    }
}]

I don't know how to convert, is there a best practice


More information

schema:

table! {
    books (id) {
        id -> Unsigned<Bigint>,
        name -> Varchar,
        user_id -> Unsigned<Bigint>,
    }
}

table! {
    users (id) {
        id -> Unsigned<Bigint>,
        username -> Varchar,
    }
}

joinable!(books -> users (user_id));

allow_tables_to_appear_in_same_query!(
    books,
    users,
);

models:

#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, Debug, Clone)]
#[belongs_to(User, foreign_key = "user_id")]
#[table_name = "books"]
pub struct Book {
    #[serde(skip_deserializing)]
    pub id: PK,
    pub name: String,
    pub user_id: PK,
}

#[derive(Identifiable, Queryable, Serialize, Deserialize, Debug, Clone)]
#[table_name = "users"]
pub struct User {
    pub id: PK,
    pub username: String,
}

diesel version

diesel = { version = "1.4.4", features = ["mysql", "r2d2", "chrono", "numeric"] }

Solution

  • your real problem is with serde crate

    if you have data type like this (String, i32, ...) serde will create array in output see following code

    fn main() {
       let val = ("Hello", 123, 2.5);
    
       let result = serde_json::to_string(&val).unwrap();
       println!("{}", result); // ["Hello",123,2.5] 
    }
    

    so if you wanna solve it you can do something like this

    first make your custom model

    struct User {
       id:      i32,
       username: String,
    }
    
    struct ResponseModel {
       id:   i32,
       name: String,
       user: User,
    } 
    

    now implement From trait for ResponseModel

    impl From<(models::Book, models::User)> for ResponseModel {
        fn from(values: (models::Book, models::User)) -> Self {
            Self {
                id: values.0.id,
                name: values.0.name,
                user: User {
                    id: values.1.id,
                    username: values.1.username,
                },
            }
        }
    }
    

    now change hello fn like this

    pub async fn hello(pool: web::Data<DbPool>) -> HttpResult {
       let conn = pool.get().map_err(ErrorInternalServerError)?;
    
       let data: Vec<ResponseModel> = books::table
           .inner_join(users::table)
           .load::<(models::Book, models::User)>(&conn)
           .map(|x| x.into_iter().map(ResponseModel::from).collect())
           .map_err(ErrorInternalServerError)?;
    
       Ok(HttpResponse::Ok().json(data))
    }