Waterline ORM equivalent of insert on duplicate key update - sql

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

Related

Where should I use computed and methods in Vue js? (need proper guideline)

Look at the image below and please explain where should I use computed instead of methods and vice versa? It confuses me.
As a rule of thumb: a computed is a simple getter (though they can be setters, but that's not something you'd typically use) that is dependent on one or more properties. It'll update automatically when those properties change. You cannot pass it parameters. You would use a method when you need to pass a parameter and/or need to perform an action or mutation.
data() {
firstName: 'Bert',
lastName: 'Ernie'
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
This will return "Bert Ernie" and will update automatically when either firstName or lastName change.
Now if you need to change something, or for example select something from a list using a parameter, you would use a method.
data() {
users: [
{ id: 1, name: 'Bert' }.
{ id: 2, name: 'Ernie' }
]
},
methods: {
getUser(userid) {
return this.users.find(user => user.id === userid);
},
setUserName(userid, newName) {
const user = this.users.find(user => user.id === userid);
if (user) {
user.name = newName;
}
}
}

How to loop through an array containing objects and do comparison

I am using ionic 4. I get the result from the API then get the result show like this
[
{"name":John,"age":20},
{"name":Peter,"age":35},
{"name":Alex,"age":15}
]
But I want to get the name only to check whether have same name with my condition or not. But I cannot straight a way get the result from the API, I need to hard code to do comparison. Here is my code:
this.http.get(SERVER_URL).subscribe((res) => {
const data = [
{ name: John, age: 21 },
{ name: Thomas, age: 25 },
];
const ppl= data.find(people=> people.name === 'alex');
console.log(ppl);
});
So, My first question is How to get the name from the API directly, not like now I hard code the result from API. My Second Question is when I do comparison I want to show the result 'already exist' or 'can use this name'. Because if I write my code like this I will get the error Type 'void' is not assignable to type 'boolean':
const ppl= data.find((people)=> {
if(people.name === 'alex') {
this.text = 'already exist'
} else {
this.text = 'can use this name'
}});
console.log(ppl);
Anyone can help me? Thank you very much
Instead of defining data, use the contents of the response; res will have the exact same contents that you are declaring in data.
this.http.get(SERVER_URL).subscribe(res => {
// If successful, res is an array with user data like the following
// [
// {name: "John", age: 21},
// {name: "Thomas", age: 25},
// ...
// ]
if (res.find(user => user.name === 'alex')) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}
});
Taken from the MDN docs on Array.prototype.find():
The find() method returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.
In that case
res.find(user => user.name === 'alex')
will return a user object if any of the usernames match alex, or undefined if none of the user.name attributes match alex.
undefined evaluates to false and a user object evaluates to true in the conditional.
Keep in mind that you are comparing strings with ===, so, for example, Alex will not match alex, if you want to look into other ways to compare strings, have a look at this question.
You also might want to handle errors, how you handle them is up to you, and it will depend on the response, but you can access the error inside your subscribe like this:
this.http.get(SERVER_URL).subscribe(res => {
if (res.find(user => user.name === 'alex')) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}
}, error => {
console.log(error);
}, () => {
// There is also a 'complete' handler that triggers in both cases
});
Edit. API returns Object not array
If your API returns an Object instead of an array like in your question, you can still iterate over the properties
this.http.get(SERVER_URL).subscribe(res => {
// If successful, res is an array with user data like the following
// {
// key1: {name: "John", age: 21},
// key2: {name: "Thomas", age: 25},
// ...
// }
let match = false;
Object.keys(res).forEach(key => {
if (res[key].name === 'alex') {
match = true;
}
});
if (match) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}
});
Instead of Object.keys() you could use Object.values() to get an array with user objects, then use find() as before, but that seems less efficient, something like this:
if (Object.values(res).find(user => user.name === 'alex')) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}

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),
});

Transaction with Sequelize doesn't work

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

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
}