jsparks jsparks - 4 months ago 135
Node.js Question

Updating attributes in associated models using Sequelize

Is it possible to update attributes on both the parent model and the associated models all in one go? I am having trouble getting it to work and haven't been able to find any full examples. I'm not sure if it's something wrong with my code or if it wasn't intended to work the way I would expect. I tried adding the onUpdate : 'cascade' to my hasMany definition, but that didn't seem to do anything.

Models:

module.exports = function( sequelize, DataTypes ) {
var Filter = sequelize.define( 'Filter', {
id : {
type : DataTypes.INTEGER,
autoIncrement : true,
primaryKey : true
},
userId : DataTypes.INTEGER,
filterRetweets : DataTypes.BOOLEAN,
filterContent : DataTypes.BOOLEAN
},
{
tableName : 'filter',
timestamps : false
}
);

var FilteredContent = sequelize.define( 'FilteredContent', {
id : {
type : DataTypes.INTEGER,
autoIncrement : true,
primaryKey : true
},
filterId : {
type : DataTypes.INTEGER,
references : "Filter",
referenceKey : "id"
},
content : DataTypes.STRING
},
{
tableName : "filteredContent",
timestamps : false
}
);

Filter.hasMany( FilteredContent, { onUpdate : 'cascade', as : 'filteredContent', foreignKey : 'filterId' } );
sequelize.sync();

return {
"Filter" : Filter,
"FilteredContent" : FilteredContent
};
}


Retrieving the filter and trying to update an attribute on the associated FilteredContent object:

Filter.find({ where: { id: 3 },
include: [ { model : FilteredContent, as : 'filteredContent' } ]
}).success ( function( filter ) {
var filteredContent = FilteredContent.build( {
filterId : filter.id,
id : 2,
content : 'crap'
});
filter.save();
});


This results in only attributes in the Filter object being updated. How do I get it to also update the attributes in FilteredContent?

Also, is the sequelize.sync() necessary after defining my models? I'm not clear on what exactly it is supposed to do. I am able to retrieve my object with associations without it. I added it to my code in desperation to get the updates working, but I'm not sure if it's actually necessary.

Thanks

Answer

To your question:

When you eagerly load FilteredContent (using include), a model instance is already built, so there is no reason to call build. Something along the lines of this should do what you want:

Filter.find({
  where: { id: 3 }, 
  include: [ { model : FilteredContent, as : 'filteredContent' } ] 
}).then ( function( filter ) {
  return filter.filteredContent[0].updateAttributes({
    content: 'crap'
  })
}).then(function () {
  // DONE! :)
});

A couple of pointers about the code you posted as a whole:

  • sequelize.sync creates database tables for your models, if they don't already exist. It is not required for what you want to do if your tables already exist
  • sequelize.sync is an async operation, so doing sequelize.sync without attaching a callback is not advisable. Furthermore it looks like you are doing sync in a model definition - you should only do it once, preferrably in the place where you define your models.
  • It looks like you defining several models in one file - you should only define one in each file. The associations could be set up by doing sequelize.import([path to filter model) in your FilterContent file, or by doing all associations in the place you import your models into your app.

edit to answer your comment:

You cannot do a single function call that will update both filter and filteredcontent, but you don't have to do the updates in sequence either. You can issue all the update commands without waiting for them to complete.

Filter.find({
  where: { id: 3 }, 
  include: [ { model : FilteredContent, as : 'filteredContent' } ] 
}).then ( function( filter ) {
  return Promise.all([
    filter.updateAttributes({}),
    filter.filteredContent.map(fc => fc.updateAttributes({}))
  ]);
}).spread(function (filter, filteredContents) {

})

In this way all queries will run in parallel, and your then function will be called when all of them have completed. Notice that I've used spread here to turn the array returned from Promise.all into separate arguments.

Comments