recently I discovered this on the sequelize documentation where you can create using include. Now I trying to do it on my program but only creates the records of the "parent" model and not for the children.
This is my model and my controller.
var MainMenu = sequelize.define('MainMenu', {
Name: {
type: DataTypes.STRING(50)
},
Day: {
type: DataTypes.DATE
},
RecordStatus:{
type: DataTypes.BOOLEAN,
defaultValue: true
},
DeletedAt: {
type: DataTypes.DATE
}
},
{
associate: function(models){
models.MainMenu.hasMany(models.MainMeal, {as: 'Menu'});
}
}
);
exports.createIn = (req, res) => {
let Menu = {
Name: 'MenuTest',
MainMeal: [{
Type: 'Breakfast',
Name: 'MealTest1'
}, {
Type: 'Lunch',
Name: 'MealTest2'
}]
};
db.MainMenu.create(Menu, {
include: [{
model: db.MainMeal,
as: 'Menu'
}]
})
.then( mainmenu => {
if (!mainmenu) {
return res.send('users/signup', {
errors: 'Error al registrar el mainmenu.'
});
} else {
return res.jsonp(mainmenu);
}
})
.catch( err => {
console.log(err);
return res.status(400)
.send({
message: errorHandler.getErrorMessage(err)
});
});
};
On my case it only creates the MainMenu record and not the MainMeal records. What am I doing wrong?
Change your menu object, and include Menu array and not MainMeal
You have to give the aliased name in the object
let mainMenu = {
Name: 'MenuTest',
Menu: [{
Type: 'Breakfast',
Name: 'MealTest1'
}, {
Type: 'Lunch',
Name: 'MealTest2'
}]
};
Now,
db.MainMenu.create(mainMenu, {
include: [{
model: db.MainMeal,
as: 'Menu'
}]
})
.then( mainmenu => {
if (!mainmenu) {
return res.send('users/signup', {
errors: 'Error al registrar el mainmenu.'
});
} else {
return res.jsonp(mainmenu);
}
})
.catch( err => {
console.log(err);
return res.status(400)
.send({
message: errorHandler.getErrorMessage(err)
});
});
The main thing is of course the naming of Menu should be within the data passed to .create() itself, along with the arguments presented there and if you really need to specify the alias "twice", which you do not. But there are some other things to be aware of.
I'd personally prefer storing the association as it's own export and including that within the statement. This generally becomes a bit clearer when you understand the usage of that association later.
I would also strongly encourage that when you are "writing" things across multiple tables, then you implement transactions to ensure all related items are actually created and not left orphaned should any errors arise.
As a brief listing based on the example:
const Sequelize = require('sequelize');
const sequelize = new Sequelize('sqlite:menu.db',{ logging: console.log });
const MainMeal = sequelize.define('MainMeal', {
Type: { type: Sequelize.STRING(50) },
Name: { type: Sequelize.STRING(50) }
});
const MainMenu = sequelize.define('MainMenu', {
Name: { type: Sequelize.STRING(50) }
});
MainMenu.Meals = MainMenu.hasMany(MainMeal, { as: 'Menu' });
(async function() {
try {
await sequelize.authenticate();
await MainMeal.sync({ force: true });
await MainMenu.sync({ force: true });
let result = await sequelize.transaction(transaction =>
MainMenu.create({
Name: 'MenuTest',
Menu: [
{ Type: 'Breakfast', Name: 'MealTest1' },
{ Type: 'Lunch', Name: 'MealTest2' }
]
},{
include: MainMenu.Meals,
transaction
})
);
} catch(e) {
console.error(e);
} finally {
process.exit();
}
})();
Which would output something like:
Executing (default): SELECT 1+1 AS result
Executing (default): DROP TABLE IF EXISTS `MainMeals`;
Executing (default): CREATE TABLE IF NOT EXISTS `MainMeals` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `Type` VARCHAR(50), `Name` VARCHAR(50), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `MainMenuId` INTEGER REFERENCES `MainMenus` (`id`) ON DELETE
SET NULL ON UPDATE CASCADE);
Executing (default): PRAGMA INDEX_LIST(`MainMeals`)
Executing (default): DROP TABLE IF EXISTS `MainMenus`;
Executing (default): CREATE TABLE IF NOT EXISTS `MainMenus` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `Name` VARCHAR(50), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);
Executing (default): PRAGMA INDEX_LIST(`MainMenus`)
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): BEGIN DEFERRED TRANSACTION;
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): INSERT INTO `MainMenus` (`id`,`Name`,`createdAt`,`updatedAt`) VALUES (NULL,'MenuTest','2018-04-14 08:08:17.132 +00:00','2018-04-14 08:08:17.132 +00:00');
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): INSERT INTO `MainMeals` (`id`,`Type`,`Name`,`createdAt`,`updatedAt`,`MainMenuId`)
VALUES (NULL,'Breakfast','MealTest1','2018-04-14 08:08:17.152 +00:00','2018-04-14 08:08:17.152 +00:00',1);
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): INSERT INTO `MainMeals` (`id`,`Type`,`Name`,`createdAt`,`updatedAt`,`MainMenuId`)
VALUES (NULL,'Lunch','MealTest2','2018-04-14 08:08:17.153 +00:00','2018-04-14 08:08:17.153 +00:00',1);
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): COMMIT;
The important part there being the transaction BEGIN and COMMIT wrapping all of those INSERT statements as data is created. Even without the transaction implemented, you still see both items being created along with the related "parent". But the point of the argument is this is where you "should" be implementing transactions.
Also note that the "aliased" Menu as used in the data creation and for subsequent access, is not actually "required" to be included within the .create() method on the include option. It's "optional" and is already defined under the .hasMany() arguments, so you don't really need to do it again.
Even if you did, then that part would still be the "association" as used with the model argument:
{
include: {
model: MainMenu.Meals,
as: 'Menu'
},
transaction
}
So that's not to be confused with the original name of the model for the "table" which is referenced, which also might be another point of confusion.
Related
I need to retrieve just some columns of relations in typeorm query.
I have an entity Environment that has an relation with Document, I want select environment with just url of document, how to do this in typeorm findOne/findAndCount methods?
To do that you have to use a querybuilder, here's an example:
return this.createQueryBuilder('environment') // use this if the query used inside of your entity's repository or getRepository(Environment)...
.select(["environment.id","environment.xx","environment.xx","document.url"])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getOne();
Sorry I can't add comment to post above. If you by not parsed data mean something like "environment.id" instead of "id"
try this:
return this.createQueryBuilder("environment")
.getRepository(Environment)
.select([
"environment.id AS id",
"environment.xx AS xx",
"document.url AS url",
])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getRawOne();
Here is the code that works for me, and it doesn't require using the QueryBuilder. I'm using the EntityManager approach, so assuming you have one of those from an existing DataSource, try this:
const environment = await this.entityManager.findOne(Environment, {
select: {
document: {
url: true,
}
},
relations: {
document: true
},
where: {
id: environmentId
},
});
Even though the Environment attributes are not specified in the select clause, my experience is that they are all returned in the results, along with document.url.
In one of the applications that I'm working on, I have the need to bring back attributes from doubled-nested relationships, and I've gotten that to work in a similar way, shown below.
Assuming an object model where an Episode has many CareTeamMembers, and each CareTeamMember has a User, something like the code below will fetch all episodes (all attributes) along with the first and last name of the associated Users:
const episodes = await this.entityManager.find(Episode, {
select: {
careTeamMembers: {
id: true, // Required for this to work
user: {
id: true,
firstName: true,
lastName: true,
},
}
},
relations: {
careTeamMembers: {
user: true,
}
},
where: {
deleted: false,
},
});
For some reason, I have to include at least one attribute from the CareTeamMembers entity itself (I'm using the id) for this approach to work.
I have two models, Article and ArticleGroup:
sequelize.define('article', {
GROUP_CODE: {
type: DataTypes.STRING,
},
...
}, {
classMethods: {
associate: function(models) {
this.belongsTo(models.articleGroup);
},
},
});
...
sequelize.define('articleGroup', {
GROUP_CODE: {
type: DataTypes.STRING,
},
...
});
I'm working with legacy data where the connection between those two is made with the GROUP_CODE field. I've added a foreign key to the article model. The problem is when I try to update that foreign key. This is the code with which I'm trying to accomplish that:
let db = require('../models');
db.articleGroup.findAll().then((groups) => {
groups.forEach((group) => {
db.article.update({groupArticleId: group.id}, {where: {GROUP_CODE: group.GROUP_CODE}});
});
});
With this code I get a bunch of:
...
Executing (default): UPDATE `article` SET `updatedAt`='2017-08-05 12:43:02' WHERE `GROUP_CODE` = '6'
Executing (default): UPDATE `article` SET `updatedAt`='2017-08-05 12:43:02' WHERE `GROUP_CODE` = '11'
Executing (default): UPDATE `article` SET `updatedAt`='2017-08-05 12:43:02' WHERE `GROUP_CODE` = '1'
...
It's updating the updatedAt but not the groupArticleId field. How can I accomplish that?
The code that did the association was the following:
Object.keys(db).forEach(function(modelName) {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
Which was never done, because none of my models had the associate property. Changing the models from:
sequelize.define('article', {
...
}, {
classMethods: {
associate: function(models) {
this.belongsTo(models.articleGroup);
},
},
});
to:
let Article = sequelize.define('article', {
...
});
Article.associate = function(models) {
this.belongsTo(models.articleGroup);
}
return Article;
Fixed my issue since now the models have the associate property and can be properly associated e.g. Sequelize will create getters and setters for the foreign keys.
I'm trying to esatblish a One-To-Many relationship between the tables: Exam and Exam_Questions, using Sequelize.
Even though the tables are created properly and I can see them in PhpMyAdmin, I keep getting the following error in console:
Error: exam_question is not associated to exam!
exam.js
...
const ExamQuestion = require('./exam-question');
...
const Exam = sequelizeInstance.define("exam", {
name: { type: Sequelize.STRING },
date: { type: Sequelize.DATE }
});
// Build the model relations
Exam.hasMany(ExamQuestion, { as: "Questions" });
exam-question.js
const ExamQuestion = Sequelize.db.define("exam_question", {
correct_answer: {
type: Sequelize.STRING
},
text: {
type: Sequelize.STRING
}
});
module.exports = ExamQuestion;
To solve the error, I tried:
ExamQuestion.belongsTo(Exam);
But that doesn't change anything.
The query is:
Exam.findAll({
include: [ExamQuestion]
})
How to fix this problem and get the Exam objects including their questions?
TL;DR
For some very non-intuitive reason this seems to be happening because of the as property. To fix the problem, simply remove the as property:
Exam.hasMany(ExamQuestion);
Fixing the methods
By default, after removing the as property, Sequelize will automagically add the following methods: getExam_questions, addExam_question and so on.
They look quite bad: camel and snake cases mixed up together.
To solve that, we can easily define the singular and plural names in the ExamQuestion model options (the third argument):
const ExamQuestion = Sequelize.db.define("exam_question", {
correct_answer: {
type: Sequelize.STRING
},
text: {
type: Sequelize.STRING
}
}, {
name: {
singular: "question",
plural: "questions"
}
});
This will dictate Sequelize to create methods such as getQuestions and addQuestion instead of getExam_questions and addExam_question.
I got three models with one-to-many relationships. Simple tree. What I need is a simple, efficient way to query a structured relationship tree, preferably similar to mongoose's .populate() which I cant't use since I don't have id's on the parent model. I suppose keeping children ids on parent would be efficient, but Keystone doesn't provide this functionality by default and I am unable to write an update callback to control relational changes. I tried and wasted too much time, finding myself astray while maybe what I'm trying to achieve is much easier, but I just can't see it.
Here's the stripped code:
Category model
Category.add({
name: { type: String}
});
Category.relationship({ path: 'sections', ref: 'Section', refPath: 'category' });
Section model, child of a category
Section.add({
name: { type: String, unique: true, required: true}
category: { type: Types.Relationship, ref: 'Category', many: false}
});
Section.relationship({ path: 'articles', ref: 'Article', refPath: 'section'});
Article model, child of the Section
Article.add({
name: { type: String, required: true}
section: { type: Types.Relationship, ref: 'Section', many: false }
});
I want to get a structured view of a category with all children and their respective sub-children like this:
[ { _id: 57483c6bad451a1f293486a0,
name: 'Test Category',
sections: [
{ _id: 57483cbbad451a1f293486a1,
name: 'Test Section',
articles: [
{ _id: 57483c6bad451a1f293486a0,
name: 'Test Category' }
]
]
} ]
So that's how I did it. Not at all efficient but at least it's working. I didn't put anything in first-level parent since I need only one.
// Load current category
view.on('init', function (next) {
var q = keystone.list('Category').model.findOne({
key: locals.filters.category
});
q.exec(function (err, result) {
if (err || !results.length) {
return next(err);
}
locals.data.category = result;
locals.section = locals.data.category.name.toLowerCase();
next(err);
});
});
// Load sections and articles inside of them
view.on('init', function (next) {
var q = keystone.list('Section').model.find().where('category').in([locals.data.category]).sort('sortOrder').exec(function(err, results) {
if (err || !results.length) {
return next(err);
}
async.each(results, function(section, next) {
keystone.list('Article').model.find().where('section').in([section.id]).sort('sortOrder').exec(function(err, articles){
var s = section;
if (articles.length) {
s.articles = articles;
locals.data.sections.push(s);
} else {
locals.data.sections.push(s);
}
});
}, function(err) {
next(err);
});
next(err);
});
});
But now I'm getting another issue. I'm using Jade 1.11.0 for templates and sometimes it doesnt't show the data in the view.
I will post another question for this issue.
Just a simple query, for example with a double ref in the model.
Schema / Model
var OrderSchema = new Schema({
user: {
type : Schema.Types.ObjectId,
ref : 'User',
required: true
},
meal: {
type : Schema.Types.ObjectId,
ref : 'Meal',
required: true
},
});
var OrderModel = db.model('Order', OrderSchema);
Query
OrderModel.find()
.populate('user') // works
.populate('meal') // dont works
.exec(function (err, results) {
// callback
});
I already tried something like
.populate('user meal')
.populate(['user', 'meal'])
In fact only one of the populates works.
So, how do is get two populates working ?
You're already using the correct syntax of:
OrderModel.find()
.populate('user')
.populate('meal')
.exec(function (err, results) {
// callback
});
Perhaps the meal ObjectId from the order isn't in the Meals collection?
UPDATE:
This solution remains for the version 3.x of Mongoose http://mongoosejs.com/docs/3.8.x/docs/populate.html but is no longer documented for >= 4.x versions of Mongoose and so the answer from #JohnnyHK is the only valid one for now on.
ORIGINAL POST
If you're using Mongoose >= 3.6, you can pass a space delimited string of the path names to populate:
OrderModel.find()
.populate('user meal')
.exec(function (err, results) {
// callback
});
http://mongoosejs.com/docs/populate.html
This has probably been resolved already, but this is my take on multiple & deep population in Mongodb > 3.6:
OrderModel.find().populate([{
path: 'user',
model: 'User'
}, {
path: 'meal',
model: 'Meal'
}]).exec(function(err, order) {
if(err) throw err;
if(order) {
// execute on order
console.log(order.user.username); // prints user's username
console.log(order.meal.value); // you get the idea
}
});
There are probably other ways to do this, but this makes very readable code for beginners (like me)
The best solution in my opinion is arrays when you are populating more than one foreign field on the same level. My code shows that I have multiple populates for different levels.
const patients = await Patient.find({})
.populate([{
path: 'files',
populate: {
path: 'authorizations',
model: 'Authorization'
},
populate: {
path: 'claims',
model: 'Claim',
options: {
sort: { startDate: 1 }
}
}
}, {
path: 'policies',
model: 'Policy',
populate: {
path: 'vobs',
populate: [{
path: 'benefits'
}, {
path: 'eligibility',
model: 'Eligibility'
}]
}
}]);
As you can see, wherever I needed more than one field of a document populated, I encased the populate key in an array and provided an array of objects, each object having a different path. Most robust and concise way to do it, in my opinion.
You can use array syntax:
let results = await OrderModel.find().populate(['user', 'meal']);
You can also select which properties you want from each populate:
let results = await OrderModel.find().populate([{path: 'user', select: 'firstname'}, {path: 'meal', select: 'name'}]);
Latest mongoose v5.9.15
has ability to take array of populate fields
so you can do,
.populate([ 'field1', 'field2' ])
You can try:
OrderModel.find()
.populate('user')
.populate('meal')
.exec(function (err, results) {
// callback
});
or with array options
OrderModel.find()
.populate([
{
path: "path1",
select: "field",
model: Model1
},
{
path: "path2",
select: "field2",
model: Model2
}
])
.exec(function (err, results) {
// callback
});
In model file do something like:-
doctorid:{
type:Schema.Types.ObjectId, ref:'doctor'
},
clinicid:{
type:Schema.Types.ObjectId, ref:'baseClinic'
}
In js file for adding operator use Something like:-
const clinicObj = await BaseClinic.findOne({clinicId:req.body.clinicid})
const doctorObj = await Doctor.findOne({ doctorId : req.body.doctorid}) ;
**and add data as:-**
const newOperator = new Operator({
clinicid:clinicObj._id,
doctorid: doctorObj._id
});
Now, while populating
apiRoutes.post("/operator-by-id", async (req, res) => {
const id = req.body.id;
const isExist = await Operator.find({ _id: id }).populate(['doctorid','clinicid'])
if (isExist.length > 0) {
res.send(isExist)
} else {
res.send("No operator found");
}
});
i have same problem , but my mistake not in populate , i have an error in Model
if you do this
uncorrected
user: {
type: [Schema.Types.ObjectId],
ref: 'User'
}
correct
user: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
you must put array around of object like this
To populate multiple fields with array of objects in controller/action function, model of both is already referred in schema of post
post.find({}).populate('user').populate('comments').exec(function (err,posts)
{
if(err)
{
console.log("error in post");
}
return res.render('home',{
h1:"home Page",
posts:posts,
});
});
I think you are trying to the nested population you can visit official docs
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});