How to filter a query based on collection from many-to-many - sails.js

I have two model objects. Doctors and Hospitals. The model definitions look like:
module.exports = {
schema: true,
autoUpdatedAt: true,
autoCreatedAt: true,
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
hospitals: {
collection: 'hospital',
via: 'doctors',
dominant: true,
},
}
};
and
module.exports = {
schema: true,
autoUpdatedAt: true,
autoCreatedAt: true,
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
doctors: {
collection: 'doctor',
via: 'hospitals',
},
}
};
How can I query doctors that are mapped to certain hospitals? I read a couple posts about through keyword, but I wasn't able to get records to persist to the through/join table. Seems like if I could query the automatic join table, I could get it to work, but I'm curious if there is an "official" way to accomplish this type of query.
My current query looks like: Doctor.find().where({'hospitals': ['548303dcf49435ec4a01f2a2','548303cbf49435ec4a01f2a0']}).populate('hospitals').exec(function (err, doctors) { ... });
The underlying db is mongo, if that matters.

I did cheat a bit but things seem to be working. That said, I am interested if there's a better way to accomplish this type of query.
I created a model object that maps to the auto created join table. So in this case, my additional model object looks like:
module.exports = {
schema: true,
autoUpdatedAt: true,
autoCreatedAt: true,
tableName: 'doctor_hospitals__hospital_doctors',
attributes: {
doctor: {
model: 'doctor',
columnName: 'doctor_hospitals'
},
hospital: {
model: 'hospital',
columnName: 'hospital_doctors'
}
}
};
Now, I query the join table directly and use the results for a sub query:
DoctorHospital.find().where({'hospital': ['548303dcf49435ec4a01f2a2','548303cbf49435ec4a01f2a0']}).exec(function(err, doctorHospitals) {
if(err) return next(err);
Doctor.find().where({'id': _.pluck(doctorHospitals, 'doctor')}).populate('hospitals').exec(function (err, doctors){
if(err) return next(err);
return res.view({
doctors: doctors
});
});
});

Related

Mongoose populate depending on conditions

My service uses MongoDB and Mongoose. I have two DBs: Users and Posts. In Posts schema I have parameters:
"author", that contains userId from Users DB
"anonymous", a boolean-parameter that shows if the post is anonymous or not.
I can't solve the problem: when I request data from Posts DB I want to populate author in the "author" parameter only for non-anonymous posts, for anonymous ones I'd like to return null or not to return this parameter at all.
I've tried to use "match", but it doesn't work.
How can I solve this problem?
Thank you.
Code example:
const postSchema = mongoose.Schema(
{
author: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User',
},
anonymous: {
type: Boolean,
required: true,
default: false,
},
content: {
type: String,
required: true,
},
date: {
type: Date,
required: true,
default: Date.now,
},
},
{
timestamps: true,
}
);
For population I use pre:
postSchema.pre(/^find/, function (next) {
this.populate({
path: 'author',
select: '_id login',
});
next();
});

SailsJs/Postgresql - How to create a one way association or one-to-many relation via an unique field

I have two models:
PdfAnnotation.js:
module.exports = {
tableName: "pdf_annotations",
primaryKey: "pk_id",
attributes: {
pk_id: {
type: "number",
autoIncrement: true
},
annotation_id: {
type: "string",
unique: true,
required: true,
},
comments: {
collection: "pdfcomments",
via: "fk_annotation_id"
}
}
};
PdfComments.js:
module.exports = {
tableName: "pdf_comments",
primaryKey: "pk_id",
attributes: {
pk_id: {
type: "number",
autoIncrement: true,
},
fk_annotation_id: {
model: "pdfannotations",
},
comment_content: {
type: "string",
},
}
};
When I run these codes:
PdfAnnotations.create({
annotation_id: "test3",
});
PdfComments.create({
fk_annotation_id: 'test3',
comment_content: 'test',
});
I got this error:
I have followed the documentation: https://sailsjs.com/documentation/concepts/models-and-orm/associations/one-to-many.
The difference between my implementation and the docs is: the constraint I used for PdfComments to PdfAnnotations via an unique field annotation_id(string) not the primary key pk_id(number), so that I got the error.
For some reasons I don't want to use annotation_id as a primary key (such as its type is string)
I'm not familiar with Sails and its ORM, hope to see your help.
Try something like this:
const pdfannotation = await PdfAnnotations.create({
annotation_id: 'test3',
}).fetch();
const pdfcomment = await PdfComments.create({
fk_annotation_id: pdfannotation.id,
comment_content: 'test',
});

Sequelize Unique Constraint Across Two Tables

I have three tables: survey, survey_owners (join table), users. Surveys naturally have titles and are owned by users. A user can own multiple surveys and a survey can be owned by multiple users (many-to-many relationship).
I have the unique constraint setup on the survey_owners table so there are no duplicates, but now need to figure out how to enforce a unique constraint to address the following: A user should not be able to own multiple surveys with the same title.
That being said, a unique constraint CANNOT be placed on the 'title' column of the survey table because the uniqueness should be only be applied if a user already owns a survey with an identical name.
Any ideas how to implement this in the Sequelize migration and/or model(s)?
Current migration file for survey_owners
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('survey_owners', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
surveys_id: {
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
references: {
model: 'surveys',
key: 'id'
}
},
users_id: {
type: Sequelize.INTEGER,
references: {
model: 'users',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
field: "created_at"
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
field: "updated_at"
}
})
.then(() => {
return queryInterface.addConstraint('survey_owners', ['surveys_id', 'users_id'], {
type: 'unique',
name: 'survey_owners'
});
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('survey_owners');
}
};
Unfortunately I could not find a way inside Sequelize to handle this constraint so I am handling the logic on the submit action and checking in a JS method. Not the best way but had to move on and this is working.

Sequelize delete instance with n:m and 1:m associations and update Model

I have 2 models in my postgresql db and using sequelize and node:
Users
Transactions
and are associated like this:
UserModel.hasMany(TransactionModel, { as: 'sentTransactions', foreignKey: 'senderId' });
UserModel.hasMany(TransactionModel, { as: 'receivedTransactions', foreignKey: 'receiverId' });
UserModel.belongsToMany(TransactionModel, { as: 'transactionLikes', through: 'UserLike', foreignKey: 'userId' });
TransactionModel.belongsTo(UserModel, { as: 'receiver' });
TransactionModel.belongsTo(UserModel, { as: 'sender' });
TransactionModel.belongsToMany(UserModel, { as: 'likers', through: 'UserLike', foreignKey: 'transactionId' });
Which means a user has many received and sent transactions and each user can "like" many transactions.
How can I delete a transaction and remove all associations (receiver, sender, liker)? I don't want to delete the users too.
I would also like to update the User Model which is defined like this, in order to add an "email" property:
const UserModel = db.define('user', {
id: { type: Sequelize.STRING, unique: true, primaryKey: true },
firstName: { type: Sequelize.STRING },
lastName: { type: Sequelize.STRING },
username: {
type: Sequelize.STRING,
unique: {
args: true,
msg: USERNAME_IS_TAKEN,
},
}
How can I update the model? What will happen to the existing instances?
Thank you in advance for your help!
According to this tutorial your M:N relation should work as you expect it out of the box:
For n:m, the default for both is CASCADE. This means, that if you delete or update a row from one side of an n:m association, all the rows in the join table referencing that row will also be deleted or updated.
Further more, to enforce the CASCADE behavior you may also pass onDelete option to the association calls. Something like this should do the trick:
TransactionModel.belongsToMany(UserModel, { as: 'likers', through: 'UserLike', foreignKey: 'transactionId', onDelete: 'CASCADE' });
Adding an email property to the User Model should be as easy as that:
const UserModel = db.define('user', {
id: {
type: Sequelize.STRING,
unique: true,
primaryKey: true
},
firstName: { type: Sequelize.STRING },
lastName: { type: Sequelize.STRING },
username: {
type: Sequelize.STRING,
unique: {
args: true,
msg: USERNAME_IS_TAKEN,
}
},
email: { type: Sequelize.STRING }
});

Populate does not retrieve the whole referenced object just the ids

I've been reading a few answers regarding this and yet I still can't get it to work.
My model objects aren't deeply nested and are quite simple. It's events that have a list of users attending them and users that have a list of events they've attended. like so:
let DinnerSchema = new mongoose.Schema({
date: {
type: Date,
unique: true,
timestamps: true,
required: true
},
title:{type: String, require: true},
attending: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
})
and the users:
let UserSchema = new mongoose.Schema({
email: {
type: String,
lowercase: true,
unique: true,
required: true
},
name:{ type: String, require: true },
password: {type: String ,required: true},
dinners: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Dinner'
}]
})
And for clarity here's the entire route that's using populate:
userpage.get('/', authCheck, (req, res) => {
const options = { _id: '57ebbf48bd6914036f99acc7' }
return Dinner
.findOne(options)
.populate('User', 'name') //I'VE TRIED ADDING 'name' BASED ON SOME ANSWERS I SAW
.exec((err, newDinner) => {
if (err) {
console.log(err)
res.status(400).end()
}
console.log(newDinner) // SHOW'S USERS ID'S BUT NO OTHER FIELDS
return res.json({
sucsess: true,
data: newDinner
})
})
})
If I understand correctly in the database itself there should only be a reference to the other model and not actually all of it's fields and the join happens with the populate. My db structure show's just the reference so that's ok.
I've tried specifying the name of the fields i'm after (the name field in this case) but that didn't work.
My population result always looks like the following and doesn't show any other fields except for the _id one:
{
_id: 57ebbf48bd6914036f99acc7,
date: 2016-09-27T22:00:00.000Z,
title: '1',
__v: 0,
attending: [ 57ebbcf02c39997f9cf26891, 57ebbdee098d3c0163db9905 ]
}
What am I screwing up here?
In mongoose populate receives 4 parameters.
path
selection(fields to be return) ,
condition
options (like {limit:10})
In your case you are not passing right path to populate. It should be
userpage.get('/', authCheck, (req, res) => {
const options = { _id: '57ebbf48bd6914036f99acc7' }
return Dinner
.findOne(options)
.populate('attending', 'name')
.exec((err, newDinner) => {
if (err) {
console.log(err)
res.status(400).end()
}
console.log(newDinner) // SHOW'S USERS ID'S BUT NO OTHER FIELDS
return res.json({
sucsess: true,
data: newDinner
})
})
})
Now it will return all the names of attending users.
you need to populate attending - that's your user reference in the dinner schema