Sequelize many to many documentation - node.js

I am trying to create a many to many association between projects and tasks using node and sequelize. I cannot figure out how to create new 'Tasks' with variable names that can be accumulated as an array for the project.setTasks function as the documentation here gives as an example with ALOT of detail missing. http://sequelizejs.com/documentation#associations-many-to-many
here is the example they give:
Project.hasMany(Task)
Task.hasMany(Project)
Project.create()...
Task.create()...
Task.create()...
// save them... and then:
project.setTasks([task1, task2]).success(function() {
// saved!
})

I'm not sure what you mean by "variable names" exactly.
However, here's an example from the sequelize test suite, as copied below with my comments:
// Define the models
this.User = this.sequelize.define('User', { username: DataTypes.STRING });
this.Task = this.sequelize.define('Task', { title: DataTypes.STRING, active: DataTypes.BOOLEAN });
// Setup associations
this.User.hasMany(this.Task);
this.Task.hasMany(this.User);
// using promises, sync the database, then create a user and two tasks
return this.sequelize.sync({ force: true }).then(function() {
return Promise.all([
self.User.create({ username: 'John'}),
self.Task.create({ title: 'Get rich', active: true}),
self.Task.create({ title: 'Die trying', active: false})
]);
}).spread(function (john, task1, task2) {
// This line below is only relevant to the tests.
self.tasks = [task1, task2];
// Using the created (and saved) tasks and user, set the tasks and return the promise
return john.setTasks([task1, task2]);
});
Edit I agree that the sequelize documentation is still too light, but I've found it a good framework so far to use. I recommend when in doubt either reading the unit tests, or writing your own tests to confirm behaviours.
Edit Note you can also use .addTask and .removeTask instead of simply setting them all. See the docs

Related

MongoDB - Populate document field recursively

I've got a Page:
const PageSchema = new Schema({
children: [{type: mongoose.Schema.Types.ObjectId, ref: 'Page'}]
});
As you can see each Page has an array of children which is also a Page.
Now what I need to do is fetch all "main" pages and populate their children array, easy enough except for the fact that I need to do this recursively since a Page contains an array of Pages.
MongoDB doesn't have any out of the box support for this, it only supports a 2 level deep population.
Here's my current query (removed all extra stuff for readability) without using the current .populate method (since it's not gonna work anyway):
Page.find(query)
.exec((err, pages) => {
if (err) {
return next(err);
}
res.json(pages);
});
I looked at this question which is similar but not exactly what I need:
mongoose recursive populate
That seems to use a parent to populate recursively and it also starts from just 1 document, rather than my scenario which uses an array of documents since I'm using .find and not .findOne for example.
How can I create my own deep recursive populate function for this?
Sidenote:
I am aware that the solution I need isn't recommended due to performance but I've come to the conclusion that it is the only solution that is going to work for me. I need to do recursive fetching regardless if it's in the frontend or backend, and doing it right in the backend will simplify things massively. Also the number of pages won't be big enough to cause performance issues.
You can recursively populate a field also like:
User.findOne({ name: 'Joe' })
.populate({
path: 'blogPosts',
populate: {
path: 'comments',
model: 'comment',
populate: {
path: 'user',
model: 'user'
}
}
})
.then((user) => {});
Please note that for first population, you don't need to specify model attribute, as it is already defined in your model's schema, but for next nested populations, you need to do that.
The answer actually lied in one of the answers from the previous questions, although a bit vague. Here's what I ended up with and it works really well:
Page.find(query)
.or({label: new RegExp(config.query, 'i')})
.sort(config.sortBy)
.limit(config.limit)
.skip(config.offset)
.exec((err, pages) => {
if (err) {
return next(err);
}
// takes a collection and a document id and returns this document fully nested with its children
const populateChildren = (coll, id) => {
return coll.findOne({_id: id})
.then((page) => {
if (!page.children || !page.children.length) {
return page;
}
return Promise.all(page.children.map(childId => populateChildren(coll, childId)))
.then(children => Object.assign(page, {children}))
});
}
Promise.all(pages.map((page) => {
return populateChildren(Page, page._id);
})).then((pages) => {
res.json({
error: null,
data: pages,
total: total,
results: pages.length
});
});
});
The function itself should be refactored into a utils function that can be used anywhere and also it should be a bit more general so it can be used for other deep populations as well.
I hope this helps someone else in the future :)

Sequelize how not to redefine models every time server starts

In Sequelize tutorials, it is said that a single model is generated in this way:
const User = sequelize.define('user', {
firstName: {
type: Sequelize.STRING
},
lastName: {
type: Sequelize.STRING
}
});
And than saved (i.e. create table) like this :
User.sync().then(() => {
// do whatever
});
But I expect to do that just once, I need to create tables just once. So the next time I run the script how to just retrieve models (i.e. tables) that were defined before with the above code.
in sync method you can pass an option to avoid sync of database tables every time. This option will make sure that your application checks for table in database, if it exist then it will not create otherwise it will create table.
User.sync(
{force: false}).then(() => {
// do whatever
});
Let me know if you still face issue. I am using sequalize and i am not getting this issue.

Sequelize automatically save association objects when parent is saved

Given the following model:
var Project = sequelize.define('project', {/* ... */})
var Task = sequelize.define('task', {/* ... */})
Project.hasMany(Task, {as: 'Tasks'});
Task.belongsTo(Project);
How can make changes to tasks and have the database updated when I save the project.
Project.findById(1, {include: Task, as: 'tasks'}).then(function(project) {
project.tasks.each(function(task) {
task.status = 'done';
});
project.status = 'closed';
project.updatedBy = 'user';
// ...many more statements that modify the project and tasks for example
project.save(); // << I want this to save the whole hierarchy to the database, similar to how Hibernate does it, if some people are familiar
});
When project.save() is called, the tasks are not updated. Why?
If you use find or findAll you can take advantage of eager-loading as described in Eager loading.
To do this, you need to use the attribute include like: include: [Task]
This way you have an inner join and don't need to query for the tasks.
Based on your code above, you can have:
(I can't test this code right now, sorry if it's not working perfectly):
Project.find({ where: {id: 1}, include: [{model: Task, as: 'task'}]})
.then(function(project) {
project.task[0].updateAttributes({
status = 'done';
}).then(function() {
//done!
});
});
This answer may be helpful too.

Get related items in keystone

Working on a project in KeystoneJS and I'm having trouble figuring out the mongoose relationship bit.
According to the keystone docs, let's say we have the following models: User and Post. Now a post has a relationship to a user, so I'll write:
Post.add({
author: { type: Types.Relationship, ref: 'User' }
});
and then:
User.relationship({ path: 'posts', ref: 'Post', refPath: 'author' });
Now, I want to be able to see all posts related to that User without having to query for both a User and Posts. For example, if I queried for a user object I would like to be able to do user.posts and have access to those related posts. Can you do this with mongoose/keystone?
As far as I understand, keystone's List Relationship has nothing to do with mongoose and querying. Instead, it is used by keystone's admin UI to build out the relationship queries before rendering them in the view. This said I would forget User.relationship(...); solving your problem, although you want it for what I just mentioned.
The following should work fine based on your schema, but only populates the relationship on the one side:
var keystone = require('keystone');
keystone.list('Post').model.findOne().populate('author', function (err, doc) {
console.log(doc.author.name); // Joe
console.log(doc.populated('author')); // '1234af9876b024c680d111a1' -> _id
});
You could also try this the other way, however...
keystone.list('User').model.findOne().populate('posts', function (err, doc) {
doc.posts.forEach(function (post) {
console.log(post);
});
});
...mongoose expects that this definition is added to the Schema. This relationship is added by including this line in your User list file:
User.schema.add({ posts: { type: Types.Relationship, ref: 'Post', many: true } })
After reading the keystone docs, this seems to be logically equivalent the mongoose pure way, User.schema.add({ posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }] });. And now you are now maintaining the relationship on both lists. Instead, you may want to add a method to your keystone list.
User.schema.methods.posts = function(done){
return keystone.list('Post').model.find()
.where('author', this.id )
.exec(done);
};
By adding a method to your User list, it saves you from persisting the array of ObjectIds relating the MongoDB document back to the Post documents. I know this requires a second query, but one of these two options look to be your best bet.
I found this on their github https://github.com/Automattic/mongoose/issues/1888, check it for context, but basically says to use the keystone populateRelated() method. I tested it and does work
// if you've got a single document you want to populate a relationship on, it's neater
Category.model.findOne().exec(function(err, category) {
category.populateRelated('posts', function(err) {
// posts is populated
});
});
I'm aware the question is old but this has to be out there for further reference

Creating mongoose.js object with multiple 'ref' fields. (Or: how to switch to a nosql, event driven mindset)

First of all, I'm coming from years of using Django and relational databases, but want to try something new - so this is probably a conceptual problem on my part. I'm just not sure where it is. Hope the question is clear enough, even though I'm not sure what I should be asking...
So. I'm setting up an application that contains Teams, and Matches between these teams.
Here are my models:
var TeamSchema = new Schema({
name: String,
code: String
});
TeamSchema.statics = {
list: function(options, cb) {
var criteria = options.criteria || {};
this.find(criteria)
.exec(cb);
},
load: function(id, cb) {
this.findOne({_id: id})
.exec(cb);
},
};
var MatchSchema = new Schema({
home_team: { type: Schema.ObjectId, ref: 'Team' },
away_team: { type: Schema.ObjectId, ref: 'Team' },
datetime: Date,
slug: String,
home_score: Number,
away_score: Number
});
Where I run into problems is when I want to populate these models programmatically. The teams are easy enough:
// Clear old teams, then add default teams
Team.find({}).remove(function() {
Team.create({
name: 'Brazil',
code: 'bra'
}, {
name: 'Cameroon',
code: 'cmr'
}, {
name: 'Spain',
code: 'esp'
}, {
....
But then I want to create matches, which requires getting the _ids for the teams. In my way of thinking this is pretty easy: Create the Teams, then before creating the Matches fetch the appropriate Teams and assign to variables, and finally create a Match using these variables to populate the home_team and away_team fields.
This of course doesn't work here, since there's no guarantee the Teams have been populated when the Match creating function is called. So I moved that code to the callback of the Team creation code. But still that didn't work, since when the Match is created the Team variables are still not populated. I sort of got this to work by putting the match creation code as a callback to the Team lookup (which itself is a callback to the Team creation code). But that only works for ONE of the Teams, how do I ensure I have both the _ids available before creating a Match?
As I said in the beginning, I feel like I'm thinking about this the wrong way - so I'm not necessarily looking for working code, more for pointers about where I'm going wrong in my thinking.
That said, this is the working code I have for setting one of the teams:
// Clear old teams, then add default teams
Team.find({}).remove(function() {
Team.create({
name: 'Brazil',
code: 'bra'
}, {
name: 'Croatia',
code: 'hrv'
},
function() {
console.log('finished populating teams');
// Clear old matches, then add default matches
Match.find({}).remove(function() {
Team.findOne({code: 'bra'}, function(err, obj) {
if (!err) {
console.log(obj.code);
Match.create({
datetime: "2014-06-01T22:00:00.000Z",
slug: 'bra-hrv',
home_team: obj._id
});
} else {
console.log(err);
}
});
});
});
});
Edit: Found one way around it. Since code is unique to each team, I switched the team _id field to String and removed code. That way I can easily create the Match objects without having to look up the _id - I already know what it is.
Doesn't really solve my underlying problem, but it does get the work done.
If you use new and save to create your Team docs you'll have immediate access to their _id values because they're assigned client-side:
var brazil = new Team({
name: 'Brazil',
code: 'bra'
});
brazil.save();
var croatia = new Team({
name: 'Croatia',
code: 'hrv'
});
croatia.save();
Match.create({
datetime: "2014-06-01T22:00:00.000Z",
slug: 'bra-hrv',
home_team: brazil._id,
away_team: croatia._id
});

Resources