Non-unique add() to an many-to-many connection - sails.js

Is it possible to add multiple of the same object to the many-to-many connection? The current setup i am running gives me an Error: Trying to '.add()' an instance which already exists! everytime i try to add a speaker multiple times. For example, maybe speaker X speaks for 20 min, Speaker Y takes over, and then its speaker X:s turn again. How can i solve this?
this is my event model:
attributes: {
id: {
type: "integer",
primaryKey: true,
autoIncrement: true
},
name: {
type: "string",
required: true
},
speakers: {
collection: "speaker",
via: 'events',
dominant: true
}
},
addSpeaker: function (options, cb) {
Event.findOne(options.id).exec(function (err, event) {
if (err) return cb(err);
if (!event) return cb(new Error('Event not found.'));
event.speakers.add(options.speaker);
event.save(cb);
});
And also an Speaker model:
attributes: {
name: {
type: "string",
required: true
},
title : {
type: "string"
},
event: {
model: "event"
},
events: {
collection: "event",
via: "speakers"
}
}

Related

Mongoose findOneAndUpdate + upsert always replaces existing document

I have a collection I want to upsert with findOneAndUpdate. In addition to that I have two fields (isHandled, isNotADuplicate) that should be:
defaulted to 'false' upon insert
left untouched upon update (e.g. isHandled stays 'true')
I have however found that
isHandled, isNotADuplicate are always defaulted back to 'false'
_id is also regenerated upon every update (I use a compound key to query the doc, not _id)
My Model
export const QuickbrainFindingSchema = new Schema<QuickBrainFindingDocument>({
connectedApplicationType: { type: String, required: true, enum: ['jira'] },//e.g. jira
clientKey: { type: String, required: true },//e.g. 135eb702-156c-3b67-b9d0-a0c97548xxxx
//key
projectKey: { type: String, required: true },//e.g. AL
type: { type: String, required: true },
doc1key: { type: String, required: true },//e.g. AL-7
doc2key: { type: String, required: true },//e.g. AL-16
//data
calculationDate: { type: SchemaTypes.Date, default: Date.now },
direction: { type: String, required: true },
reasonAndMetric: { type: SchemaTypes.Mixed, reason: true },
scoreSummary: { type: String, reason: true },
isHandled: { type: SchemaTypes.Boolean, default: false },
isNotADuplicate: { type: SchemaTypes.Boolean, default: false },
similarityReference: { type: SchemaTypes.ObjectId, required: true, ref: "QuickbrainSimilarityMatrix" }
}, {
//options
});
QuickbrainFindingSchema.index(
{ connectedApplicationType: 1, clientKey: 1, project: 1, doc1key: 1, doc2key: 1, type: 1 },
{ unique: true, name: "compoundKey" }
);
export const QuickbrainFindingModel = model<QuickBrainFindingDocument>("QuickbrainFinding", QuickbrainFindingSchema);
My Code
public async addFinding(
projectKey: string,
doc1key: string,
doc2key: string,
type: ET_FindingType
, data: QuickbrainFindingData): Promise<QuickbrainFinding> {
let keyFull: QuickbrainFindingKey = {
connectedApplicationType: this.connectedApplicationType,
clientKey: this.clientKey,
projectKey: projectKey,
doc1key: doc1key,
doc2key: doc2key,
type: type
};
let insertObj: QuickbrainFinding = <QuickbrainFinding><unknown>{};
Object.assign(insert, keyFull);
Object.assign(insert, data);
delete (<any>insertObj).isHandled;
delete (<any>insertObj).isNotADuplicate;
return new Promise<QuickbrainFinding>(function (ok, nok) {
QuickbrainFindingModel.findOneAndUpdate(
keyFull, { $set: insertObj},
{
runValidators: true,
upsert: true,
setDefaultsOnInsert: true,
new: true,
omitUndefined: true,//I think only available for findAndReplace(..)
})
.lean().exec(function (err, result) {
if (err) {
nok(err);
}
else
ok(result)
});
});
}
Mongoose Debug Output
quickbrainfindings.findOneAndUpdate(
{
connectedApplicationType: 'jira',
clientKey: '135eb702-256c-3b67-b9d0-a0c975487af3',
projectKey: 'ITSMTEST',
doc1key: 'ITSMTEST-7',
doc2key: 'ITSMTEST-10',
type: 'Email'
},
{
'$setOnInsert':
{ __v: 0, isHandled: false, isNotADuplicate: false, _id: ObjectId("60789b02c094eb3ef07d2929") },
'$set': {
connectedApplicationType: 'jira',
clientKey: '135eb702-256c-3b67-b9d0-a0c975487af3', projectKey: 'ITSMTEST', doc1key: 'ITSMTEST-7', doc2key: 'ITSMTEST-10', type: 'Email',
calculationDate: new Date("Thu, 15 Apr 2021 19:58:58 GMT"),
direction: '2', scoreSummary: '100.0%',
similarityReference: ObjectId("60789b029df2079dfa8aa15a"),
reasonAndMetric: [{ reason: 'Title Substring', metricScore: '100%' },
{ reason: 'Title TokenSet', metricScore: '54%' }, { reason: 'Description TokenSet', metricScore: '100%' }]
}
},
{
runValidators: true, upsert: true, remove: false, projection: {},
returnOriginal: false
}
)
What happens
Existing documents are found, but when they are updated I'm confused that:
_id is regenerated
isHandled and isNotADuplicate are reset to 'false' (although insertObj does not contain them)
When looking at the debug output I can see that the new _id is the one fron $setOnInsert, which confuses the heck out of me, since the selector works
Notable
keyFull is used to query the existing document, it does not contain _id;
delete (<any>insertObj).isHandled <- the object used for $set does NOT contain isHandled
This is embarrasing to admit, but thanks to Joe I have found the problem.
Before every findOneAndUpdate / Upsert I had a delete statement removing the existing documents Pipeline:
Delete old documents
Calculate new documents
Upsert new documents -> always resulted in Insert
let matchAnyDoc = this.filterForDocKeyAny(projectKey, docKeyAny, findingType);
matchAnyDoc.forEach(async (condition) => {
QuickbrainFindingModel.deleteMany(condition).exec(function (err, res) {
if (err) {
nok(err);
} else {
ok();
}
});
}, this);

Populate none ref objects of a nested array

I'm working on a project that uses:
"#nestjs/core": "^7.0.0",
"#nestjs/mongoose": "^7.0.0",
"mongoose": "^5.9.12",
// ...
"typescript": "^3.7.4",
With mongoose/mongoDB config:
uri: MONGO_DB_URI,
useUnifiedTopology: true,
useNewUrlParser: true,
useFindAndModify: false,
useCreateIndex: true,
I'm trying to build a simple CRUD for this model:
export const ContactSchema = new mongoose.Schema(
{
source_id: { type: String, required: true },
firstName: { type: String, trim: true },
lastName: { type: String, trim: true },
phones: [
{
number: {
type: String,
required: true,
unique: true,
validate: {
validator: function(value) {
const phoneNumber = parsePhoneNumberFromString(value)
return phoneNumber && phoneNumber.isValid()
},
},
},
type: {
type: String,
default: function() {
return parsePhoneNumberFromString(this.number).getType() || "N/A"
},
},
code: {
type: Number,
default: function() {
return parsePhoneNumberFromString(this.number).countryCallingCode || undefined
},
},
national: {
type: Number,
default: function() {
return parsePhoneNumberFromString(this.number).nationalNumber || undefined
},
},
},
],
email: { type: String, unique: true, required: true, lowercase: true, trim: true },
},
{ timestamps: true },
)
ContactSchema.plugin(mongoosePaginate)
Like every CRUD app, I'm willing to have fildAll() & fildOne() routes that return the body of a given Contact with all his info including the list of its phone numbers. So I used:
// ...
async findAll(): Promise<Contact[]> {
return this.contactModel.find()
// then I add
.populate('phones')
}
async findBySourceId(id: string): Promise<Contact> {
return this.contactModel.findOne({ source_id: id })
// then I add
.populate('phones')
}
// ...
All info are well saved in the DB and there is no missing data (neither phones) and I'm sure that it works the beginning without even adding .poplate('x'), but that changed somewhere and it returns now unpopulated phone array.
Now It returns:
{
"_id": "5ebc22072e18637d84bcf6f0",
"firstName": "Maher",
"lastName": "Boubakri",
"phones": [],
"email": "mhb#test.im",
// ...
}
But, It should return:
{
"_id": "5ebc22072e18637d84bcf6f0",
"firstName": "Maher",
"lastName": "Boubakri",
"phones": [
{
"_id": "5ebc22072e18637d8fd948f9",
"number": "+21622123456",
"code": 216,
"type": "MOBILE",
"national": 22123456,
}
],
"email": "mhb#test.im",
// ...
}
Note: It is clear that MongoDB generates _id for every phone object, but, it is not a ref Object.
Any idea will be so helpful,
Thank you.
populate is used to join two (or more) collections using the references
here you don't have any references, so you don't need it
just use find() without populate
Based on #Mohammed 's comment and answer, adding .lean() after updating mongoose fixed the problem.
// ...
async findAll(): Promise<Contact[]> {
return this.contactModel.find().lean()
}
async findBySourceId(id: string): Promise<Contact> {
return this.contactModel.findOne({ source_id: id }).lean()
}
// ...

sails update the doc, nothing happen, not err message

eventController:
newHelper: function(req, res) {
const eventID = req.body.eventID;
let newHelper = req.body.newHelper;
newHelper.eventAssoc = eventID;
Wapphelprecords.create(newHelper).exec(function(err, newhelper) {
if (err) {
return res.serverError(err); }
sails.log('add new helper:', newhelper);
return res.json(newhelper);
});
}
when I do this action, the database nothing happen, and no err message, this is model under blow:
WappeventController model:
module.exports = {
attributes: {
eventID: {
type: 'integer',
// autoIncrement: true,
unique: true,
// defaultsTo: 0
},
openid: {
type: 'string',
},
author: {
model: 'wappuserinfo'
},
content: {
type: 'string'
},
allowShare: {
type: 'boolean'
},
imageList: {
type: 'array'
},
money: {
type: 'float',
},
helpers: {
collection: 'wapphelprecords',
via: 'eventAssoc',
},
bestHelper: {
collection: 'wapphelprecords',
via: 'eventAssoc',
}
},
Wapphelprecords Model:
module.exports = {
attributes: {
eventAssoc: {
model: 'wappevents',
},
content: {
type: 'string'
},
contact: {
type: 'string'
},
userInfo: {
model: 'wappuserinfo'
},
bestHelper: {
type: 'boolean'
},
moneyEarn: {
type: 'float'
}
}
};
when I do newHelper action, the database nothing happened, and nothing error notice, I just do not understand. need help, thx.
spend whole day to fix it, and finally:
module.exports = {
attributes: {
eventID: {
type: 'integer',
// autoIncrement: true,
primaryKey: true, //<---- set this primarkey to true
unique: true,
// defaultsTo: 0
},
openid: {
type: 'string',
},
author: {
model: 'wappuserinfo'
},
content: {
type: 'string'
},
allowShare: {
type: 'boolean'
},
imageList: {
type: 'array'
},
money: {
type: 'float',
},
helpers: {
collection: 'wapphelprecords',
via: 'eventAssoc',
},
bestHelper: {
collection: 'wapphelprecords',
via: 'eventAssoc',
}
},

Getting "model is not associated with other Model" in sequelize when using belongsToMany

I am using sequelize with postgreSQL. I have two schemas namely User and Location. A User can have many Locations and a Location can have many Users.
My User Schema is as follows
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
firstName: {
type: Sequelize.STRING,
require: true
},
middleName: {
type: Sequelize.STRING,
require: false
},
lastName: {
type: Sequelize.STRING,
require: true
},
age: {
type: Sequelize.INTEGER,
require: false
},
email_Id: {
type: Sequelize.STRING,
require: true,
unique: true,
validate: {
isEmail: true
}
}
My Location Schema is as follows:
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
latitude: {
type: Sequelize.DOUBLE,
require: true
},
longitude: {
type: Sequelize.DOUBLE,
require: true
},
locationAddress: {
type: Sequelize.STRING
},
mailBoxNo: {
type: Sequelize.INTEGER
}
I Am using belongsToMany of sequelize and creating a third table name UserLocation where I have mentioned belongsToMany for both User and Location which is as below:
User.belongsToMany(Location, {
through: 'UserLocation'
});
Location.belongsToMany(User, {
through: 'UserLocation'
});
My requirement is to get all the locations for a given user id. My Code is as follows:
var param = req.body;
var options = {};
if (param.where) {
options.where = param.where;
}
options.include = [{
model: User //User Model
}, {
model: Location //Location Model
}];
//Here userLocation refers to UserLocation Schema
userLocation.findAll(options).then(function (response) {
//Some Logic
}).catch(function (err) {
//Error handling
});
While executing the above code, I getting the following error:
User Model is not associated with UserLocation Model.
I am unable to understand why I am getting the following error. Can somebody help me out with this?
You can use this for getting all the locations of a given user;
User
.findOne({
"where": {
"id": param.where
},
"include": [Location]
})
.then(function(user) {
// should get this user
console.log(user);
// should get all locations of this user
console.log(user.Locations);
})
.catch(function(error) {
// error handling
});

sails.js One to many associations -- TypeError: Cannot convert null to object

I am getting this strange error recently. It was not there earlier and I don't remember changing much.
error: Error (E_UNKNOWN) :: Encountered an unexpected error
TypeError: Cannot convert null to object
at hasOwnProperty (native)
at utils.object.hasOwnProperty (/home/mandeep/freelance/hellos/node_modules/sails-postgresql/node_modules/waterline-sequel/sequel/lib/utils.js:28:14)
at /home/mandeep/freelance/hellos/node_modules/sails-postgresql/node_modules/waterline-sequel/sequel/where.js:259:11
at Array.forEach (native)
at WhereBuilder.complex (/home/mandeep/freelance/hellos/node_modules/sails-postgresql/node_modules/waterline-sequel/sequel/where.js:177:36)
at complexWhere (/home/mandeep/freelance/hellos/node_modules/sails-postgresql/node_modules/waterline-sequel/sequel/index.js:244:16)
at find (/home/mandeep/freelance/hellos/node_modules/sails-postgresql/node_modules/waterline-sequel/sequel/index.js:85:23)
at Cursor.populateBuffers [as $populateBuffers] (/home/mandeep/freelance/hellos/node_modules/sails-postgresql/lib/adapter.js:539:31)
at Cursor.run (/home/mandeep/freelance/hellos/node_modules/sails-postgresql/node_modules/waterline-cursor/cursor/cursor.js:45:8)
at runJoins (/home/mandeep/freelance/hellos/node_modules/sails-postgresql/node_modules/waterline-cursor/index.js:51:10)
Details: TypeError: Cannot convert null to object
The error goes away when I remove the one to many association from user model. Here are the models for reference:
Underlying database is postgres
User.js
module.exports = {
tableName: "users",
attributes: {
name: {
type: "string",
required: false
},
permission: {
type: "integer",
defaultsTo: 2
},
primary_phone: {
model: "phone",
required: true
},
phone: {
collection: "phone",
via: "id"
},
primary_email: {
model: "email",
required: true
},
email: {
collection: "email",
via: "id"
}
}
};
Phone.js
module.exports = {
attributes: {
number: {
type: "string",
required: true
},
owner: {
model: "user"
}
}
};
Email.js
module.exports = {
attributes: {
email: {
type: "email",
required: true
},
owner: {
model: "user"
},
verified: {
type: "boolean",
defaultsTo: false
}
}
};
I don't think you can do via: "id" - the id field doesn't refer back to the User model. You should create a new attribute for both the Email and Phone model and link those back to the User model.
For example,
User.js:
...
phone: {
collection: "phone",
via: "nonPrimaryPhoneOwner"
},
email: {
collection: "email",
via: "nonPrimaryEmailOwner"
}
...
Email.js:
...
nonPrimaryEmailOwner: {
model: "user"
}
...
Phone.js:
...
nonPrimaryPhoneOwner: {
model: "user"
}
...