Sequelize.js insert a model with one-to-many relationship - javascript

I have two sequelize models with one-to-many relationship. Let's call them Owner and Property.
Assume they are defined using the sails-hook-sequelize as such (simplified).
//Owner.js
module.exports = {
options: {
tableName: 'owner'
},
attributes: {
id: {
type: Sequelize.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING(255)
},
associations: function () {
Owner.hasMany(Property, {
foreignKey: {
name: 'owner_id'
}
});
}
}
//Property.js
module.exports = {
options: {
tableName: 'property'
},
attributes: {
id: {
type: Sequelize.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING(255)
}
}
Now assume I want to insert an Owner record in my database and insert a few property records to associate with the owner. How do I do this?
I'm looking for something like
Owner.create({name:'nice owner',
property: [{name:'nice property'},
{name:'ugly property'}]});
Surprisingly I can't find this in the Sequelize documentation.

You can't associate property existing records when you create the owner, you have to do that right after, with promise chain.
Owner.create({name:'nice owner'}).then(function(owner){
owner.setProperties([{name:'nice property'}, {name:'ugly property'}]).then(/*...*/);
});
To avoid any problems with those associations (owner created but some associations failed), it's better to use transactions.
sequelize.transaction(function(t) {
return Owner.create({name:'nice owner'}, {transaction: t}).then(function(owner){
return owner.setProperties([{name:'nice property'}, {name:'ugly property'}], {transaction : t});
});
});
However, if you want to create new Owner associated to new Properties you can do something like
Owner.create({
name: 'nice owner',
property: [
{ name: 'nice property'},
{ name: 'ugly property'}
]
},{
include: [ Property]
});
See http://docs.sequelizejs.com/en/latest/docs/associations/#creating-with-associations

Related

Sequelize: Querying a through table

I'm trying to query a through table (Application) and paginate/order the results, but can't seem to get the logic quite right.
The Many-To-Many association:
// Applicants M:N Jobs (through Application)
Applicant.belongsToMany(Job, { through: Application });
Job.belongsToMany(Applicant, { through: Application });
I've queried Application, and then nested the queries for each side of the relation:
exports.getApplications = async (req, res, next) => {
const index = req.query.index || 0;
const limit = req.query.limit || 10;
const applications = await Application.findAll({ limit: parseInt(limit, 10), index: parseInt(index)});
let results = [];
try {
await Promise.all(applications.map(async (application) => {
const job = await Job.findOne({ where: { id: application.jobId } });
const applicant = await Applicant.findOne({ where: { id: application.applicantId } });
results.push({application, job, applicant});
}));
res.status(200).json({msg: 'success', applications: results});
} catch(err) {
console.log(err);
}
}
It seems to work, but feels a bit hacky. Is there a way of querying the through table and getting the associated data from both the Jobs and Applicant tables at the same time?
Thanks!
*EDIT: So I'm trying to return an array of application objects that look something like this:
[
{
applicationId: application.id,
companyId: job.companyId,
company: job.company.name,
position: job.title,
applicantId: applicant.id,
firstName: applicant.firstName,
lastName: applicant.lastName,
},
{...},
{...}
]
...but I'd like to paginate the application results. So:
Application.findAll({ limit, index });
Ideally I'd also then like to be able to order by Job/Applicant properties too
More info:
So thanks to the help so far it looks like I need to also create a belongsTo association for the Application and Job/Applicant so that I can query the Association table and get Job/Applicant data:
// Applicants M:N Jobs (through Application)
Applicant.belongsToMany(Job, { through: Application });
Job.belongsToMany(Applicant, { through: Application });
// Set associations so the Application table can be queried directly
Application.belongsTo(Job, { foreignKey: { name: 'jobId' }});
Application.belongsTo(Applicant, { foreignKey: { name: 'applicantId' }});
I currently create an application in one of my routes using applicant.addJob(currentJob);
// Applicant Model:
const Applicant = sequelize.define('applicant', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false
},
cvUrl: {
type: Sequelize.STRING,
allowNull: true
}
});
// Job Model:
const Job = sequelize.define('job', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
title: {
type: Sequelize.STRING,
allowNull: false
},
// **snip**
createdAt: {
type: Sequelize.DATE(3),
allowNull: false,
},
updatedAt: {
type: Sequelize.DATE(3),
allowNull: false,
}
});
// Application Model:
const Application = sequelize.define('application', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
notNull: true
}
});
With the combination of previous answers
Applicant.belongsToMany(Job, { through: Application ,as:'jobs'});
Job.belongsToMany(Applicant, { through: Application,as:'applicants' })
with these aliases, you can include both into the application
const applications = await Application.findAll({
include: [
{model: Job, as:'jobs', where: {id: application.jobId}},
{model: Applicant, as:'applicants' where:{id: application.jobId}}
],
limit,
raw: true
});
also, you can set include as if applicant and job has an association
{model: Job, as:'jobs', where: {id: application.jobId}
include:[{model:Applicant as:'applicants'}]
}
If I understand your requirement properly, I think you want to do this way.
This is an example to order by Applicant.
For order by Job, change the order option with Job's attribute.
When you use the offset/limit, you need the order and the order option needs to ensure the ordering is always same and unique.
const applications = await Application.findAll({
include: [
{
model: Job
},
{
model: Applicant
}
],
subQuery: false,
order: [
['Applicant', 'lastName'],
['Applicant', 'firstName']
// lastName & firstName is not enough to get a unique order.
// In order to make sure the order is always same and pagination works properly,
// you should add either order by id or createdAt/updatedAt.
['Applicant', 'createdAt', 'desc']
],
offset: index,
limit,
raw: true // To flatten the response.
});
You can give aliases tou your associations and just get
Applicant.belongsToMany(Job, { through: Application ,as:'jobs'});
Job.belongsToMany(Applicant, { through: Application,as:'applicants' });
Then you'll be able to use a simple include
for example Get the job and its applicants with one single query
const job = await Job.findOne({
where: { id: application.jobId },
include: ['applicants'],
});
in your job object you'll get an array of applicants .
More reference for that in here

Sequelize asking for multiple values of association when explicitly defining foreign key

I am creating a model in sequelize as:
const Desk = sequelize.define('Desk', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
number: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{
indexes: [
{
unique: true,
fields: ['store_id', 'number'],
message: 'A desk with same number exists on this store'
}
],
tableName: 'desk',
underscored: true,
});
I create an association as such:
Desk.belongsTo(models.Store, {
foreignKey: {
name: 'storeId',
allowNull: false
}
});
I explicitly specify the name of the foreign key so that I can add it as an index.
The problem arises when I try to create a row as such:
const data = { number: 4, storeId: 1 };
await Desk.create(data);
The above code raises an exception that StoreId cannot be null even though I explicitly changed the foreign key to storeId. Even if I try changing the data to
const data = { number: 4, StoreId: 1 };
I get the error: storeId cannot be null
I don't understand if I making some mistake in creating the model but it should not be asking me for multiple values of same field.

Sequelize: Using "where" inside a child include overrides parent's "where" criteria. Composite primary key involved

So,
I have two tables, with a 1:M relationship. They have in common two primary keys: tenant and user_id.
I have defined the model relationship and btw, I am not sure if I did it correctly because I am still not sure how to handle composite primary keys on Sequelize. This works well with my many other queries, and I think it influences the problem.
// Sequelize model set-up:
const user = serviceLayerDB.define('user',
{ // Database columns:
tenant: {
type: Sequelize.STRING(45),
primaryKey: true
},
user_id: {
type: Sequelize.STRING(24),
primaryKey: true
},
status: {
type: Sequelize.STRING(11)
}
});
const user_component = serviceLayerDB.define('user_component',
{ // Database columns:
tenant: {
type: Sequelize.STRING(45),
primaryKey: true
},
user_id: {
type: Sequelize.STRING(24),
primaryKey: true
},
component_id: {
type: Sequelize.STRING(24),
primaryKey: true
},
active: {
type: Sequelize.BOOLEAN
}
});
// Sequelize relationship set-up:
user.hasMany(user_component, { foreignKey: 'user_id' });
user.hasMany(user_component, { foreignKey: 'tenant' });
BUT the problem comes when I have the following query:
// Retrieving user and related components.
function getSubscriptions() {
let options = {
where: {
tenant: 'company_A',
user_id: '1001'
},
include: [{ // Adding components, filtered by "active" value:
model: user_component,
where: {
active: 1
},
required: false
}]
};
user.findAll(options)
.then(function(data) {
if (data.length === 0) { // If no data found:
console.log('No data found');
return;
}
// Curate Sequelize result:
let curatedData = data.map(function(userInstance) { return userInstance.get({ plain: true}) }); // Workaround to be able to perform .get().
console.log(JSON.stringify(curatedData, null, 2));
})
.catch(function(error) {
console.log('critical', 'Failed to find data in database. Error: ' + error);
})
}
// Execute:
getSubscriptions();
What I want is to find the user and its components, but only the ones with the active value set to 1. It is not working: the result is every component with the value active set to 1 under the same "tenant", the child include is ignoring the "user_id" that we indicated in the parent.
Am I right to think this is related to my composite primary key? How to fix this in the most elegant manner?
You have to use aliases when you associate a model to another model more then once.
For instance:
user.hasMany(user_component, { foreignKey: 'user_id', as: 'UserComponents' });
user.hasMany(user_component, { foreignKey: 'tenant', as: 'TenantComponents' });
And afterwards you should decide for what exact association you wish to do an include operation:
the association by user_id field
let options = {
where: {
tenant: 'company_A',
user_id: '1001'
},
include: [{ // Adding components, filtered by "active" value:
model: user_component,
as: 'UserComponents'
where: {
active: 1
},
required: false
}]
};
the association by tenant field
let options = {
where: {
tenant: 'company_A',
user_id: '1001'
},
include: [{ // Adding components, filtered by "active" value:
model: user_component,
as: 'TenantComponents'
where: {
active: 1
},
required: false
}]
};
If you would like both child collections with the active: 1 condition you can do this:
let options = {
where: {
tenant: 'company_A',
user_id: '1001'
},
include: [{ // Adding components, filtered by "active" value:
model: user_component,
as: 'UserComponents'
where: {
active: 1
},
required: false,
separate: true
}, { // Adding components, filtered by "active" value:
model: user_component,
as: 'TenantComponents'
where: {
active: 1
},
required: false,
separate: true
}]
};
Please pay attention to separate: true option: this option tells sequielize to do separate queries for childs.
If you wish to get not all users but those ones with active only components (which ones: through user_id or tenant field?) you should set required: true in include. But in this case don't include both associations with required: true. This leads to miltiplication of amount of records in the result SQL query and consumes much more memory.

Problem adding allowNull in the foreignKey association 1:1 in the models with Sequelize

i am doing a simple project with node.js using Sequelize with MySQL database. I was modeling my models associations. One got the hasMany and the other the belongsTo, I set the informations in both(like, as:...,foreignKey:...,onDelete:...), and set in foreignKey the propertie allowNull. When I save de model with the belongsTo method passing the in the request body the foreignKey it saves properly. But when I do this with model that uses in association the method hasOne it doesn't work. It passes that I need to specify the model ID that uses the belongsTo method.
const connection = require('../../config/database');
const DataTypes = require('sequelize').DataTypes;
const Course = connection.define('course', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
courseName: {
type: DataTypes.STRING(50),
field: 'course_name',
allowNull: false
}
},
{ timestamps: false });
Course.associate = models => {
Course.belongsTo(models.Lecturer, {
foreignKey: {
field: 'lecturer_id',
allowNull: false
},
as: 'lecturer'
});
Course.belongsToMany(models.Student, {
through: 'student_course',
as: 'students',
foreignKey: 'curse_id',
otherKey: 'student_id'
})
}
module.exports = Course;
const connection = require('../../config/database');
const DataTypes = require('sequelize').DataTypes;
const Lecturer = connection.define('lecturer', {
lecturerName: {
type: DataTypes.STRING(50),
field: 'lecturer_name',
allowNull: false
}
},
{ timestamps: false });
Lecturer.associate = models => {
Lecturer.hasOne(models.Course, {
foreignKey: {
field: 'lecturer_id',
allowNull: false
},
as: 'course'
})
}
module.exports = Lecturer;
Result:
[ ValidationErrorItem {
message: 'course.courseId cannot be null',
type: 'notNull Violation',
path: 'courseId',
value: null,
origin: 'CORE',
instance: [Object],
validatorKey: 'is_null',
validatorName: null,
validatorArgs: [] } ]
I can't comment yet, but it looks like the error shows a camel-case column name (courseId) as opposed to a snake-case column name (course_id).
Also, you have misspelled "course_id" in your Course.belongsToMany association.

sequelize querying by association - nested includes?

Here is my account Model:
module.exports = {
attributes: {
accountPkey: {
type: Sequelize.INTEGER,
primaryKey: true,
field: 'account_pkey'
},
accountName: {
type: Sequelize.STRING,
allowNull: false,
field: 'account_name'
},
imagePkey: {
type: Sequelize.INTEGER,
field: 'image_pkey'
}
},
associations: function() {
Account.hasMany(Privilege, {
as: 'Privileges',
foreignKey: 'account_pkey'
});
Account.hasMany(AccountContact, {
as: 'Contacts',
foreignKey: 'account_pkey'
});
},
options: {
tableName: 'v_account',
timestamps: false,
classMethods: {
whereUserIs(user_pkey, privileges) {
return Account.findAll({
include: [{
model: Privilege,
where: {
'Privilege.privilege_type_name': {
$in: privileges
},
'Privilege.user_pkey': {
$eq: user_pkey
}
}
}]
});
}
},
instanceMethods: {},
hooks: {}
}
};
In my whereUserIs class method I am trying to return all accounts for which there exists a privilege which has the user_pkey and has any of the privileges passed in.
Please assume that the Privilege model has account_type_name and user_pkey properties. Is my syntax correct?
Edit: I am actually using this sails hook to load sequelize: https://www.npmjs.com/package/sails-hook-sequelize
Second Edit:
Here is a more complicated query: I would like to find all users which have a privelege on any of the accounts that were queried above:
User.findAll({
include: [
{
model: Privilege,
include: [
{
model: Account,
include: [{
model: Privelege,
where: {
'Privilege.privilege_type_name': {
$in: privileges
},
'Privilege.user_pkey': {
$eq: user_pkey
}
}
}]
}
]
}
]
})
Does this second one make sense?
In both of these queries, will I get a list of entities (i.e. accounts or users) or will they be nested results (of all the includes)? How can I ensure that my lists of accounts and users are unique/distinct?

Categories

Resources