Каков стандартный шаблон для связи трех таблиц (отношение «многие ко многим») в Diesel?

База данных - Postgres

У меня такое отношение:

users <—>> users_organizations <<—> organizations

Схема:

table! {
    organizations (id) {
        id -> Int4,
        name -> Varchar,
    }
}

table! {
    users (id) {
        id -> Int4,
        name -> Varchar,
        email -> Varchar,
        password -> Varchar,
    }
}

table! {
    users_organizations (id, user_id, organization_id) {
        id -> Int4,
        user_id -> Int4,
        organization_id -> Int4,
    }
}

Модели:

#[derive(Identifiable, Queryable, Debug, Serialize, Deserialize)]
pub struct Organization {
    pub id: i32,
    pub name: String,
}


#[derive(Identifiable, Queryable, PartialEq, Debug, Serialize, Deserialize)]
pub struct User {
    pub id: i32,
    pub name: String,
    pub email: String,
    pub password: String,
}


#[derive(Identifiable, Queryable, Debug, Associations, Serialize, Deserialize)]
#[belongs_to(User)]
#[belongs_to(Organization)]
#[table_name = "users_organizations"]
pub struct UserOrganization {
    pub id: i32,
    pub user_id: i32,
    pub organization_id: i32,
}

Я хочу создать организацию. Чтобы поддерживать такую ​​связь, мне нужно вручную добавить идентификаторы пользователя и организации в таблицу users_organizations. Есть ли лучший подход для реализации такого отношения?

let new_organization = NewOrganization { name: &msg.name };
let organization = insert_into(organizations::table)
    .values(&new_organization)
    .get_result::(conn)
    .map_err(|_| error::ErrorInternalServerError(“Error creating organization”))?;

let new_user_org = NewUserOrganizationIDs {
    user_id: msg.user_id,
    organization_id: organization.id,
};

insert_into(users_organizations::table)
    .values(&new_user_org)
    .get_result::<UserOrganization>(conn)
    .map_err(|_| error::ErrorInternalServerError("Error creating user-organization data"))

Тот же вопрос здесь. В случае выбора всех организаций, относящихся к пользователю (и наоборот), я получил следующий код:

let user = users::table.filter(users::id.eq(&msg.user_id))
        .get_result::<User>(conn)
        .map_err(|_| error::ErrorNotFound("User doesn't exist"))?;

let user_organizations = UserOrganization::belonging_to(&user)
    .get_results::<UserOrganization>(conn)
    .map_err(|_| error::ErrorNotFound("User doesn't have any organization"))?;

let mut organization_ids = vec![];
for user_org in &user_organizations {
    organization_ids.push(user_org.organization_id);    
}

organizations::table.filter(organizations::id.eq_any(organization_ids))
    .get_results::<Organization>(conn)
    .map_err(|_| error::ErrorNotFound("Organizations don't exist"))

person ivan_ochc    schedule 11.09.2018    source источник
comment
Сегодня я наткнулся на ту же проблему и до сих пор не нашел решения. Единственное, что я могу предложить вам, - это рассмотреть возможность запуска всего в одну транзакцию, чтобы у вас не было противоречивых данных в случае сбоя одной вставки.   -  person vasilakisfil    schedule 10.09.2019


Ответы (1)


Этот ответ взят из чата Diesel @SRugina и @weiznich (отредактировано и адаптировано для вопроса) .

Как мне написать отношения "многие ко многим" с Дизелем?

Обычно я комбинирую belonging_to и join, что-то вроде:

UserOrganization::belonging_to(&organizations)
    .inner_join(user::table)

Есть что-нибудь похожее на belonging_to_many?

Нет, belonging_to_many не существует, потому что Diesel не пытается скрыть от вас базу данных. Это вызовет проблемы, как только вы захотите делать сложные или нестандартные вещи. В зависимости от вашего конкретного случая использования также может быть вариант объединения всех трех таблиц.

Как мне использовать inner_join в этом сценарии?

У вас есть три таблицы: users, organizations и user_organizations, и вы хотите получить все организации для конкретного пользователя.

Есть два варианта. Первый вариант - это только один запрос, но он может не соответствовать вашему требуемому формату данных, если вы хотите сделать это для всех пользователей:

users::table
    .inner_join(user_organizations::table.inner_join(organizations::table))
    .filter(users::id.eq(user_id))
    .select(organizations::all_columns)
    .load::<Organization>(conn)?;

Второй вариант позволяет группировать результаты для каждого пользователя с помощью встроенного API ассоциаций:

let user = users::table
    .find(user_id)
    .first::<User>(conn)?;

UserOrganization::belonging_to(&user)
    .inner_join(organizations::table)
    .select(organizations::all_columns)
    .load::<Organization>(conn)?;

Для вставки требуются три отдельных вставки. Мы не пытаемся скрыть это, потому что, в конце концов, пользователь выбирает, как обеспечить согласованность данных в случае неудачной вставки. Использование транзакции - это распространенный выбор.

person joelparkerhenderson    schedule 26.09.2019