Relacionamento Um-para-Muitos (One-to-Many) com Sequelize no Node.js
Em um exemplo típico, em uma empresa podem trabalhar várias pessoas. Ou seja, temos um relacionamento um-para-muitos (1 empresa - muitos funcionários). Para criar esse tipo de relacionamento no Sequelize, utilizamos o método hasMany()
. Por exemplo:
const Sequelize = require("sequelize");
// definindo o objeto Sequelize
const sequelize = new Sequelize({
dialect: "sqlite",
storage: "programicio.db",
define: {
timestamps: false,
},
});
// definindo o modelo User
const User = sequelize.define("user", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
name: {
type: Sequelize.STRING,
allowNull: false,
},
age: {
type: Sequelize.INTEGER,
allowNull: false,
},
});
const Company = sequelize.define("company", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
name: {
type: Sequelize.STRING,
allowNull: false,
},
});
Company.hasMany(User);
sequelize
.sync({ force: true })
.then(() => {
console.log("Tables have been created");
})
.catch((err) => console.log(err));
O método hasMany()
é chamado na model principal, que neste caso é Company
. No próprio método, passamos a model dependente, User
.
Como resultado, o SQLite criará duas tabelas que serão descritas pelo seguinte código SQL:
CREATE TABLE IF NOT EXISTS `companies` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS `users` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR(255) NOT NULL,
`age` INTEGER NOT NULL,
`companyId` INTEGER REFERENCES `companies` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
);
Por padrão, na tabela dependente users
será criado um campo adicional, cujo nome será o nome da model principal acrescido do sufixo "Id", que neste caso é companyId
. Esse campo permitirá que uma linha da tabela users
referencie um objeto da tabela companies
.
Neste código, vemos que, ao excluir um objeto da tabela companies
, os objetos dependentes na tabela users
não são removidos. Em vez disso, o campo companyId
das linhas associadas será definido como NULL
, conforme especificado pela cláusula ON DELETE SET NULL
. No entanto, uma estratégia mais comum é a exclusão em cascata, onde a exclusão do objeto principal resulta na exclusão de todos os objetos relacionados. Para configurar isso, o método hasMany()
aceita um segundo parâmetro, que é um objeto de configuração do relacionamento entre as models. O parâmetro onDelete
define o comportamento na exclusão:
Company.hasMany(User, { onDelete: "cascade" });
Operações Básicas
Agora vamos explorar algumas operações básicas que podem causar dúvidas ao trabalhar com modelos em um relacionamento um-para-muitos.
Ao criar um objeto do modelo dependente, muitas vezes é necessário especificar a referência ao modelo principal relacionado. Para isso, podemos usar a propriedade da model que corresponde ao nome do campo da chave estrangeira na tabela correspondente. No exemplo acima, na tabela users
, foi criado o campo companyId
para estabelecer a ligação com a tabela companies
. Embora essa propriedade não tenha sido explicitamente definida na model Company
, ela é criada implicitamente. Veja um exemplo de criação de objetos:
// criando uma empresa
Company.create({ name: "Microsoft" })
.then((res) => {
// obtendo o id da empresa criada
const compId = res.id;
// criando dois funcionários para essa empresa
User.create({ name: "Tom", age: 39, companyId: compId }).catch((err) => console.log(err));
User.create({ name: "Alice", age: 36, companyId: compId }).catch((err) => console.log(err));
})
.catch((err) => console.log(err));
Há outra maneira de adicionar um objeto dependente: por meio da model principal. A model principal possui, implicitamente, um método chamado createNomeDoModeloDependente()
(por exemplo, createUser()
):
// encontrar a empresa com id=1
Company.findByPk(1)
.then((company) => {
if (!company) return console.log("Company not found");
console.log(company);
// adicionando um objeto para esta empresa
company.createUser({ name: "Bob", age: 43 }).catch((err) => console.log(err));
})
.catch((err) => console.log(err));
Na prática, a única diferença em relação à primeira abordagem de criação é que, neste caso, não é necessário especificar o id
da model principal.
Para obter todos os objetos dependentes relacionados à model principal, existe o método getNomeDosModelosDependentes
(por exemplo, getUsers()
). Por exemplo, para obter todos os funcionários da empresa com id=1
:
Company.findByPk(1)
.then((company) => {
if (!company) return console.log("Company not found");
company
.getUsers()
.then((users) => {
for (user of users) console.log(user.name, " - ", company.name);
})
.catch((err) => console.log(err));
})
.catch((err) => console.log(err));
Saída no console:
Tom - Microsoft Alice - Microsoft Bob - Microsoft
Todas as outras operações com modelos dependentes relacionados podem ser realizadas da mesma forma que com modelos independentes.