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.
Related
I have 2 models Prophet and Task and they have a m:n relationship:
Prophets
const prophet = sequelize.define('prophets', {
name: {
type: Sequelize.STRING,
primaryKey: true,
unique: true
}
});
prophet.relationship = function(task) {
prophet.belongsToMany(task, {
through: 'prophetTasks',
as: 'tasks',
foreignKey: 'prophetName'
});
};
Tasks
const task = sequelize.define('tasks', {
name: {
type: Sequelize.STRING,
primaryKey: true,
unique: true
}
});
task.relationship = function(prophet) {
task.belongsToMany(prophet, {
through: 'prophetTasks',
as: 'prophets',
foreignKey: 'taskName'
});
};
EDITED:
my problem is sometimes I have to update a prophet which might remove some relationships with tasks, but I cant figure out how to delete the tasks that have no more relationship with any prophets.
I believe I should find all tasks that doesnt belong in prophetTasks table anymore, but I dont know how to query that with sequelize
You can use ON DELETE CASCADE
So whenever the row is deleted, data containing it as a foreign key will be deleted.
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.
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.
I am using sequelize in my application. I have postgres as underlying database.
But when I tried to save instances I got following error
[error: missing dimension value]
I have the following model
module.exports = function(sequelize, DataTypes) {
var Mymodel = sequelize.define('Mymodel', {
id: {type : DataTypes.INTEGER, autoIncrement : true, primaryKey: true},
title: {
type: DataTypes.STRING(128),
validate: {
notNull: true,
notEmpty: true
}
},
tags: DataTypes.ARRAY(DataTypes.TEXT)
});
return Mymodel;
}
I am sending http post request as
{
"title":"Test challenge",
"tags" : "['JAVA','REST','API']"
}
I am saving object like this
Mymodel.create(model).success(function(model) {
callback(null, challenge);
}).error(function(err) {
callback(err, null);
});
I tried sending over your model object as you stated and did get the error SequelizeValidationError: "['JAVA','REST','API']" is not a valid array. Perhaps you got a different error on an older version of Sequelize. Then, I made sure the tags value was a JavaScript array instead of a string and it worked.
Mymodel.create({
title: 'Test challenge',
tags: ['JAVA','REST','API']
}).then(function() {});