Transaction with Sequelize doesn't work - sql

I want to build a simple webform where you can enter a persons firstname, lastname and select multiple groups for this person (but one for now)
I'm using node.js and sequelize to store the person in a MariaDB -Database.
Sequelize created the tables Persons, Groups and GroupsPersons according to the defined models.
var Sequelize = require("sequelize");
var sequelize = new Sequelize(config.database, config.username, config.password, config);
var Group = sequelize.define("Group", {
name: {
type: DataTypes.STRING,
allowNull: false
}
}
var Person = sequelize.define("Person", {
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING,
allowNull: false
}
}
Person.belongsToMany(Group, {as: 'Groups'});
Group.belongsToMany(Person, {as: 'Persons'});
Because creating the person and assigning it into a group should be handled atomically in one step I decided to use a transaction, shown in the docs here:
http://sequelize.readthedocs.org/en/latest/docs/transactions/#using-transactions-with-other-sequelize-methods
var newPerson = {
firstName: 'Hans',
lastName: 'Fischer'
}
var id = 3 // group
sequelize.transaction(function (t) {
return Person.create(newPerson, {transaction: t}).then(function (person) {
return Group.find(id, {transction: t}).then(function(group){
if (!group) throw Error("Group not found for id: " + id);
return person.setGroups( [group], {transction: t});
})
});
}).then(function (result) {
// Transaction has been committed
// result is whatever the result of the promise chain returned to the transaction callback is
console.log(result);
}).catch(function (err) {
// Transaction has been rolled back
// err is whatever rejected the promise chain returned to the transaction callback is
console.error(err);
});`
But for some reason neither function (result) {.. for success nor the function in catch gets called. However, the complete SQL queries of the transaction were generated except COMMIT, so nothing was inserted into the db.
If I put it like this
return person.setGroups( [], {transction: t});
the transactions succeeds, but with no inserts into GroupsPersons of course.
Any ideas or suggestions?
Thanks for help!

{transaction: t} was misspelled, it works now

Related

Mongoose update gets no errors, but does not update

Cant figure out what I'm missing, and I havent had this issue before on any of my other updates. I expanded a collection and want to be able to update certain fields depending on where in the app the user is interacting. I've had no issue working with subdocs using separate calls, but with this particular nested field I'm getting no errors, and getting the correct document returned without the update. (I have another nested field that is updating fine - "personalInfo" while the "medical" field is the one giving me trouble)
The model looks like this:
const clientSchema = new Schema({
fullName: String,
firstName: String,
lastName: String,
enrollment: {
enrolled: Boolean,
enrollDates: [
{
begin: Date,
end: Date
}
]
},
personalInfo: {
dateOfBirth: Date,
phone: String,
email: String,
address: {
addressLineOne: String,
addressLineTwo: String,
city: String,
state: String,
zip: String
},
regionalCenter: String,
serviceCoordinator: String,
serviceCoordinatorPhone: String,
rcId: String,
emergencyContact: String,
livingSituation: String,
ihss: {
provider: String,
hours: Number,
services: String
}
},
medical: {
primaryIns: String,
primaryInsId: String,
secondaryIns: String,
secondaryInsId: String,
hasMediCal: Boolean,
mediCalId: String,
mediCalEnroll: Date,
hasMedicare: Boolean,
medicareId: String,
medicareEnroll: Date,
logs: {type: [logSchema], default: []},
},
contracts: {type: [contractSchema], default: []},
visits: [{ type: Schema.Types.ObjectId, ref:'Visit' }],
users: [{ type: Schema.Types.ObjectId, ref: 'User' }],
servicePlans: [{ type: Schema.Types.ObjectId, ref: 'ServicePlan'}],
currentPlan: String,
income: {type: [incomeSchema], default: []},
contacts: {type: [contactSchema], default: []}
}
The route:
router.route("/clients/:clientId").patch(updateClient)
And the controller... since I want to keep the controller as restful as possible, but conditionally set the fields depending on the api call, I conditionally set the different aspects and then pass in the body an additional field to tell the controller which aspect to update (so the personalInfo section has a field "personalInfo": "personalInfo" and the medicalInfo field has its own. The personalInfo object updates fine (I commented out the initial line since it was stated in another post that these calls work better doing a findOneAndUpdate- but that hasnt yielded any progress, and the personalInfo update worked without issue).
exports.updateClient = async (req, res) => {
try {
//const client = await Client.findOne({ _id: req.params.clientId })
if (req.body.firstName) {
client.firstName = req.body.firstName
}
if (req.body.lastName) {
client.lastName = req.body.lastName
}
if (req.body.personalInfo === 'personalInfo') {
client.updateOne({$set: {personalInfo: req.body}}, {new: true}, function(err, updatedDoc){
if(err){
console.log("error updating personal info: ", err)
}
})
}
if (req.body.enrollment === 'enrollment') {
client.updateOne({$set: {enrollment: req.body}}, {new: true}, function(err, updatedDoc){
if(err){
console.log("error updating personal info: ", err)
}
})
}
if(req.body.medicalInfo === 'medicalInfo'){
console.log("medInfo: ", req.body)
let clientId = req.params.clientId
// const client = await Client.findById(clientId)
// console.log("Client ", client)
// client.medical.set(req.body)
Client.findById(clientId)
.then((client) => {
client.medical.set(req.body
// hasMediCal: req.body.hasMediCal,
// hasMedicare: req.body.hasMedicare,
// mediCalId: req.body.mediCalId,
// medicareId: req.body.medicareId,
// mediCalEnroll: req.body.mediCalEnroll,
// medicareEnroll: req.body.medicareEnroll,
// primaryIns: req.body.primaryIns,
// primaryInsId: req.body.primaryInsId,
// secondaryIns: req.body.secondaryIns,
// secondaryInsId: req.body.secondaryInsId
);
client.save();
res.send(client)
})
// Client.findOneAndUpdate(
// { _id: req.params.clientId},
// {$set: {medical: req.body}},
// {new: true},
// function(err, updatedDoc){
// if(err){
// console.log("error updating personal info: ", err)
// }
// })
// client.markModified('medical');
}
// await client.save()
// res.send(client)
} catch (error) {
res.status(404)
res.send({ error: "Client not updated: ", error})
}
}
Finally, the body being sent:
{
"hasMediCal": false,
"hasMedicare": false,
"mediCalEnroll": "2005-04-22T08:00:00",
"mediCalId": "91234567A",
"medicalInfo": "medicalInfo",
"medicareEnroll": "2005-04-03T08:00:00",
"medicareId": "9FHS-ASU-95F8",
"primaryIns": "Molina",
"primaryInsId": "91234567A",
"secondaryIns": "SilverScript - Rx",
"secondaryInsId": "08dfA8d8"
}
Whether I've tried findOneAndUpdate, or findOne and then setting the field on the result, or setting each subfield in the object specifically, I keep getting the correct document returned, just not updated, and with no errors. I thought possibly it was because I was attempting to set the update within the conditionals, so I created a separate update controller but that got the same results as well. Really lost as how else to pursue this.
Please let me know if you see anything missing or where I'm going wrong. Much appreciated.
So after running around on this for hours, I came to a working solution, which essentially is no different, other than setting the query as a variable rather than writing it out. If anyone has any guess as to why this works when the multiple other methods didnt, I'd be grateful for your thoughts.
if(req.body.medicalInfo === 'medicalInfo'){
console.log("medInfo: ", req.body)
let clientId = req.params.clientId
let query = {_id: clientId};
Client.findOneAndUpdate(query, {$set: {medical: req.body}}, {new: true, upsert: true}, function(err, doc){
if(err) return res.status(500).send( {error:err});
return res.send(doc)
})
}

Sequelize: Avoid duplicates when create with manyToMany associations

I'm developing a small e-commerce to sell cinema tickets. I'm using Sequelize to design all models and their associations. The issue I'm facing is related with the route defined to create the final order:
Order.create({
userId: req.body.userId, // Number
sessionId: req.body.sessionId, // Number
seats: req.body.seats, // Array of objects
offsiteProducts: req.body.offsiteProducts // Array of objects
},
{
include: [
{
model: Seat,
attributes: ['area', 'number', 'roomId']
},
{
model: OffsiteProduct,
attributes: ['name', 'unitPrice']
}
]
}).then(order => {
res.json(order);
})
The relation between models is as follows:
User.hasMany(Order);
Order.belongsTo(User);
Session.hasMany(Order);
Order.belongsTo(Session);
Seat.belongsToMany(Order, { through: "reserved_seats" });
Order.belongsToMany(Seat, { through: "reserved_seats" });
OffsiteProduct.belongsToMany(Order, { through: ReservedOffsiteProduct });
Order.belongsToMany(OffsiteProduct, { through: ReservedOffsiteProduct });
For the "one-to-many" relationships, passing a foreign key is enough for Sequelize to associate models properly.
But for "many-to-many" associations ("belongsToMany" in Sequelize) it would duplicate the data entered for seats and offsite products and create it both as part of the order and as a new independent seat and offsite product respectively.
How can I avoid this behaviour and include the arrays of seats and offsite products only inside the final order? Thanks.
One way to do this is to execute everything in a transaction. That way, it will be atomic, i.e. "all or nothing".
Sequelize provides some methods for many-to-many associations. In this case, a separate call could be made for each junction table.
With this in mind, the following should accomplish the inserts without any duplicates:
let t = await sequelize.transaction()
try {
let order = await Order.create({
userId: req.body.userId,
sessionId: req.body.sessionId
}, {
transaction: t
})
let seats = await Seat.findAll({
where: {
id: {
[Op.in]: req.body.seatIds
}
}
})
let offsiteProducts = await OffsiteProducts.findAll({
where: {
id: {
[Op.in]: req.body.offsiteProductIds
}
}
})
await order.addSeats(seats, { transaction: t })
await order.addOffsiteProducts(offsiteProducts, { transaction: t })
await t.commit()
} catch (err) {
if (t) {
await t.rollback()
}
}
Alternatively, if the primary keys for the rows in the OffsiteProducts and Seats tables are already known, the above could be shortened to:
let t = await sequelize.transaction()
try {
let order = await Order.create({
userId: req.body.userId,
sessionId: req.body.sessionId
}, {
transaction: t
})
await order.addSeats(req.body.seatIds, { transaction: t })
await order.addOffsiteProducts(req.body.offsiteProductIds, { transaction: t })
await t.commit()
} catch (err) {
if (t) {
await t.rollback()
}
}
There's a bit more explanation for passing arrays of primary keys to the .addSeats and .addOffsiteProducts methods in the docs here.

How to omit fields when serializing Mongoose models in MEAN [duplicate]

I have the following simple shema:
var userSchema = new Schema({
name : String,
age: Number,
_creator: Schema.ObjectId
});
var User = mongoose.model('User',userSchema);
What I want to do is create the new document and return to client, but I want to exclude the 'creator' field from one:
app.post('/example.json', function (req, res) {
var user = new User({name: 'John', age: 45, _creator: 'some ObjectId'});
user.save(function (err) {
if (err) throw err;
res.json(200, {user: user}); // how to exclude the _creator field?
});
});
At the end I want to send the new created user without _creator field:
{
name: 'John',
age: 45
}
Is it possible to make without extra find request to mongoose?
P.S:It's preferable to make it by
Another way to handle this on the schema level is to override toJSON for the model.
UserSchema.methods.toJSON = function() {
var obj = this.toObject()
delete obj.passwordHash
return obj
}
I came across this question looking for a way to exclude password hash from the json i served to the client, and select: false broke my verifyPassword function because it didn't retrieve the value from the database at all.
The documented way is
UserSchema.set('toJSON', {
transform: function(doc, ret, options) {
delete ret.password;
return ret;
}
});
UPDATE - You might want to use a white list:
UserSchema.set('toJSON', {
transform: function(doc, ret, options) {
var retJson = {
email: ret.email,
registered: ret.registered,
modified: ret.modified
};
return retJson;
}
});
Come across your question when I was trying to find a similar answer with pymongo. It turns out that in mongo shell, with the find() function call, you can pass a second parameter which specifies how the result document looks like. When you pass a dictionary with attribute's value being 0, you are excluding this field in all the document that come out of this query.
In your case, for example, the query will be like:
db.user.find({an_attr: a_value}, {_creator: 0});
It will exclude _creator parameter for you.
In pymongo, the find() function is pretty much the same. Not sure how it translate to mongoose though. I think it's a better solution compare to manually delete the fields afterwards.
Hope it helps.
I would use the lodash utilities .pick() or .omit()
var _ = require('lodash');
app.post('/example.json', function (req, res) {
var user = new User({name: 'John', age: 45, _creator: 'some ObjectId'});
user.save(function (err) {
if (err) throw err;
// Only get name and age properties
var userFiltered = _.pick(user.toObject(), ['name', 'age']);
res.json(200, {user: user});
});
});
The other example would be:
var _ = require('lodash');
app.post('/example.json', function (req, res) {
var user = new User({name: 'John', age: 45, _creator: 'some ObjectId'});
user.save(function (err) {
if (err) throw err;
// Remove _creator property
var userFiltered = _.omit(user.toObject(), ['_creator']);
res.json(200, {user: user});
});
});
You can call toObject() on the document to convert it to a plain JS object that you can freely modify:
user = user.toObject();
delete user._creator;
res.json(200, {user: user});
By following the MongoDB documentation, you can exclude fields by passing a second parameter to your query like:
User.find({_id: req.user.id}, {password: 0})
.then(users => {
res.status(STATUS_OK).json(users);
})
.catch(error => res.status(STATUS_NOT_FOUND).json({error: error}));
In this case, password will be excluded from the query.
font: https://docs.mongodb.com/v2.8/tutorial/project-fields-from-query-results/#return-all-but-the-excluded-field
I am using Mongoosemask and am very happy with it.
It does support hiding and exposing properties with other names based on your need
https://github.com/mccormicka/mongoosemask
var maskedModel = mongomask.mask(model, ['name', 'age']); //And you are done.
You can do this on the schema file itself.
// user.js
var userSchema = new Schema({
name : String,
age: Number,
_creator: Schema.ObjectId
});
userSchema.statics.toClientObject = function (user) {
const userObject = user?.toObject();
// Include fields that you want to send
const clientObject = {
name: userObject.name,
age: userObject.age,
};
return clientObject;
};
var User = mongoose.model('User',userSchema);
Now, in the controller method where you are responding back to the client, do the following
return res.json({
user: User.toClientObject(YOUR_ENTIRE_USER_DOC),
});

Waterline ORM equivalent of insert on duplicate key update

I have a table user_address and it has some fields like
attributes: {
user_id: 'integer',
address: 'string' //etc.
}
currently I'm doing this to insert a new record, but if one exists for this user, update it:
UserAddress
.query(
'INSERT INTO user_address (user_id, address) VALUES (?, ?) ' +
'ON DUPLICATE KEY UPDATE address=VALUES(address);',
params,
function(err) {
//error handling logic if err exists
}
Is there any way to use the Waterline ORM instead of straight SQL queries to achieve the same thing? I don't want to do two queries because it's inefficient and hard to maintain.
The answer above is less than ideal. It also has the method as part of the attributes for the model, which is not correct behavior.
Here is what the ideal native solution looks like that returns a promise just like any other waterline model function would:
module.exports = {
attributes: {
user_id: 'integer',
address: 'string'
},
updateOrCreate: function (user_id, address) {
return UserAddress.findOne().where({user_id: user_id}).then(function (ua) {
if (ua) {
return UserAddress.update({user_id: user_id}, {address: address});
} else {
// UserAddress does not exist. Create.
return UserAddress.create({user_id: user_id, address: address});
}
});
}
}
Then you can just use it like:
UserAddress.updateOrCreate(id, address).then(function(ua) {
// ... success logic here
}).catch(function(e) {
// ... error handling here
});
Make a custom model method that does what you want using Waterline queries isntead of raw SQL. You will be doing two queries, but with Waterline syntax.
Example below (if you don't know about deferred objects then just use callback syntax, but the logic is the same):
var Q = require('q');
module.exports = {
attributes: {
user_id: 'integer',
address: 'string',
updateOrCreate: function (user_id, address) {
var deferred = Q.defer();
UserAddress.findOne().where({user_id: user_id}).then(function (ua) {
if (ua) {
// UserAddress exists. Update.
ua.address = address;
ua.save(function (err) {deferred.resolve();});
} else {
// UserAddress does not exist. Create.
UserAddress.create({user_id: user_id, address: address}).done(function (e, ua) {deferred.resolve();});
}
}).fail(function (err) {deferred.reject()});
return deferred.promise;
}
};
#Eugene's answer is good but it will always run 2 operations: findOne + update or create. I believe we can optimize it further because if the record exists we just need to run update. Example:
module.exports = {
attributes: {
user_id: 'integer',
address: 'string'
},
updateOrCreate: function (user_id, address) {
return UserAddress.update({user_id: user_id}, {address: address})
.then(function(ua){
if(ua.length === 0){
// No records updated, UserAddress does not exist. Create.
return UserAddress.create({user_id: user_id, address: address});
}
});
}
}
BTW, there is an open request to implement .updateOrCreate in waterline: #790

In Rally SDK 2, how do I update a hash field?

In Rally SDK 2, how do I update a hash field, like the Author field for a changeset? I read how to update the Message field, but I can't figure out how to update Author["DisplayName"] hash.
var new_message = settings.message;
Rally.data.ModelFactory.getModel({
type: 'Changeset',
success: function(model) {
model.load( '1234', {
fetch: [ 'Artifacts' ],
callback: function(result, operation) {
if ( operation.wasSuccessful() ){
var message = new_message;
record.set( 'Message', message);
record.save( {
callback: function( resultset, operation ) {
console.log( "After saving:", resultset );
if ( operation.wasSuccessful() ) {
var that = tree.ownerCt.ownerCt.ownerCt.ownerCt;
that._getChangesets();
}
}
} );
}
}
})
}
});
The Author property on Changeset is of type User. Like any other object associations on Rally's WSAPI you just set this property to the ref of the object you'd like to link. You set this the same way as you're currently setting Message in your above code snippet. (Assuming author is writable after the changeset has already been created).
record.set('Author', '/user/123456');
You can probably also avoid the deeply nested structure of your code a little bit by specifying scope on your callbacks and using member functions in your app definition:
_loadChangesetModel: function() {
//If you already have a changeset record you can get the model
//via record.self. Otherwise, load it fresh.
Rally.data.ModelFactory.getModel({
type: 'Changeset',
success: this._onChangesetModelLoaded,
scope: this
});
},
_onChangesetModelLoaded: function(model) {
model.load( '1234', {
fetch: [ 'Artifacts' ],
callback: this._onChangesetLoaded,
scope: this
});
},
_onChangesetLoaded: function(record, operation) {
if ( operation.wasSuccessful() ){
var message = settings.message;
record.set( 'Message', message);
record.save( {
callback: this._onChangesetSaved,
scope: this
} );
}
},
_onChangesetSaved: function( resultset, operation ) {
console.log( "After saving:", resultset );
if ( operation.wasSuccessful() ) {
//You shouldn't need to do this now that the scope is correct.
//I'm guessing 'that' was referring to the app itself?
//var that = tree.ownerCt.ownerCt.ownerCt.ownerCt;
this._getChangesets();
}
},
_getChangesets: function() {
//refresh
}