Последствия: проблема с "ownToMany" (ассоциация "многие ко многим") - PullRequest
1 голос
/ 26 мая 2020

Я пытаюсь создать ассоциацию «многие ко многим» в Sequelize (и Postgres), используя подход belongsToMany, но у меня возникли некоторые проблемы. Сценарий действительно распространен и включает три таблицы: t_users (название модели User), t_roles (название модели Role и их таблицу ссылок t_user_role (название модели UserRole).

Стоит отметить, что все таблицы, включая таблицу ссылок, были ранее созданы с использованием node-db-migrate (при необходимости я могу предоставить код миграции).

Перед объяснением проблемы , задействованные файлы:

Модель пользователя


class User extends Sequelize.Model {
  constructor(props) {
    super(props);
  }
}

const dbUser = {
  a_id: {
    primaryKey: true,
    autoIncrement: true,
    type: Sequelize.BIGINT,
  },
  a_date_created: {
    type: Sequelize.DATE,
    allowNull: false,
  },
  a_first_name: {
    type: Sequelize.STRING,
    allowNull: false,
  },
  a_last_name: {
    type: Sequelize.STRING,
    allowNull: false,
  },
  a_email: {
    type: Sequelize.TEXT,
    unique: true,
    allowNull: false,
  },
  a_password: {
    type: Sequelize.STRING,
    allowNull: false,
  },
  a_birthday: {
    type: Sequelize.DATE,
    allowNull: false,
  },
  a_is_active: {
    type: Sequelize.BOOLEAN,
    allowNull: false,
  },
};

User.init(dbUser, {
  sequelize: db, // db holds the sequelize connection instance
  modelName: 't_user',
  timestamps: false,
  tableName: 't_users',
});


User.associate = (models) => {
  User.belongsToMany(models.Role, {
    through: { model: UserRole, unique: false },
    as: 'roles',
    foreignKey: 'a_user',
    otherKey: 'a_role',
  });
};

Ролевая модель


class Role extends Sequelize.Model {
  constructor(props) {
    super(props);
  }
}

const dbRole = {
  a_id: {
    primaryKey: true,
    autoIncrement: true,
    type: Sequelize.BIGINT,
  },
  a_role: {
    type: Sequelize.STRING,
    allowNull: false,
  },
};

Role.init(dbRole, {
  sequelize: db,
  modelName: 't_role',
  timestamps: false,
  tableName: 't_roles',
});

Role.associate = (models) => {
  Role.belongsToMany(models.User, {
    through: { model: UserRole, unique: false },
    as: 'UserOfRoles',
    foreignKey: 'a_role',
    otherKey: 'a_user',
  });
};

UserRole

class UserRole extends Sequelize.Model {
  constructor(props) {
    super(props);
  }
}

const dbUserRole = {
  a_id: {
    primaryKey: true,
    autoIncrement: true,
    type: Sequelize.BIGINT,
  },
  a_role: {
    type: Sequelize.BIGINT,
    allowNull: false,
    primaryKey: false,
    references: {
      model: Role,
      key: 'a_id',
    },
    onDelete: 'CASCADE',
  },
  a_user: {
    type: Sequelize.BIGINT,
    allowNull: false,
    references: {
      model: User,
      key: 'a_id',
    },
    onDelete: 'CASCADE',
  },
};

UserRole.init(dbUserRole, {
  sequelize: db,
  modelName: 't_user_role',
  timestamps: false,
  tableName: 't_user_role',
});

Все ассоциации вызываются в моих моделях index.js

Модели

import User from './User';
import UserRole from './UserRole';
import Role from './Role';

const models = {
  User,
  Role,
  UserRole,
};

Object.keys(models).forEach((modelName) => {
  if (models[modelName].associate) {
    models[modelName].associate(models);
  }
});

export default models;

С учетом сказанного , теперь проблема.

Я пытаюсь запросить таблицу User и получить пользователя и, используя include, также roles, связанный с пользователем. Но по какой-то причине все результаты, которые я получаю, не зависят от роли пользователя.

Используемый мной запрос


const result = await User.findAll({
  where: {
    a_id: 1, // For simplicity
  },
  include: [
    {
      model: models.Role, // The Role model class
      as: 'roles',
      attributes: ['a_role'],
      through: {
        attributes: [],
      },
    },
  ],
});

Этот запрос возвращает мне следующий объект par sed как JSON (даже если я не разбираю его, ключ roles отсутствует в простом объекте ответа sequelize):

[
  {
    "a_id": 1,
    "a_date_created": "2020-05-24T00:00:00.000Z",
    "a_first_name": "Thiago",
    "a_last_name": "moreira",
    "a_email": "thiago@mail.com",
    "a_password": "encrypted stuff",
    "a_birthday": "1991-09-04T00:00:00.000Z",
    "a_is_active": true
  }
]

Запрос, который Sequelize выводит

SELECT "t_user"."a_id",
    "t_user"."a_date_created",
    "t_user"."a_first_name",
    "t_user"."a_last_name",
    "t_user"."a_email",
    "t_user"."a_password",
    "t_user"."a_birthday",
    "t_user"."a_is_active",
    "roles"."a_id" AS "roles.a_id",
    "roles"."a_role" AS "roles.a_role",
    "roles->t_user_role"."a_id" AS "roles.t_user_role.a_id",
    "roles->t_user_role"."a_role" AS "roles.t_user_role.a_role",
    "roles->t_user_role"."a_user" AS "roles.t_user_role.a_user"
FROM "t_users" AS "t_user"
    LEFT OUTER JOIN ( "t_user_role" AS "roles->t_user_role"
        INNER JOIN "t_roles" AS "roles" ON "roles"."a_id" = "roles->t_user_role"."a_role"
    )
    ON "t_user"."a_id" = "roles->t_user_role"."a_user";

(Это raw query и отлично работает, когда я использую его в Postgres)

Согласно моим показаниям около inte rnet, findAll метод, если он предоставлен с include, должен также иметь возможность возвращать данные ассоциации. Чтобы сделать все немного более странным, есть два сценария ios, в которых я могу получить роль пользователя (но если я правильно понял документацию, эти «обходные пути» не понадобятся).

Первый сценарий

const result = await User.findAll({
  where: {
    a_id: 1,
  },
  raw: true, // here is the difference from the previous approach
  nest: true, // here is the difference from the previous approach
  include: [
    {
      model: models.Role,
      as: 'roles',
      attributes: ['a_role'],
      through: {
        attributes: [],
      },
    },
  ],
});

Возвращает

[
  {
    "a_id": 1,
    "a_date_created": "2020-05-24",
    "a_first_name": "Thiago",
    "a_last_name": "moreira",
    "a_email": "thiago@mail.com",
    "a_password": "encrypted stuff",
    "a_birthday": "1991-09-04",
    "a_is_active": true,
    "roles": {
      "a_role": "admin",
      "t_user_role": {
        "a_id": 1,
        "a_role": "1",
        "a_user": "1"
      }
    }
  }
]

Второй сценарий

const result = await User.findAll({
  where: {
    a_id: 1,
  },
});

// Here I get the first instance of the findAll return and
// use the getRoles function generated automatically by the
// Sequelize association.
JSON.stringify(await result[0].getRoles(), null, 2);

Он возвращает

[
  {
    "a_id": 1,
    "a_role": "admin"
  }
]

(при таком подходе мне нужно было бы объединить этот результат с экземпляром User).

Что мне до сих пор не удается понять, так это то, что из-за в этих двух сценариях ios связанные данные, кажется, присутствуют, но по какой-то причине они не возвращаются с использованием «общего» подхода (который, кажется, используется почти в каждом учебнике по Sequelize).

Мне очень жаль ОГРОМНЫЙ вопрос, но, поскольку этот топи c сводит меня с ума, я хотел предоставить весь необходимый контекст.

...