sequelizejs update doesn't ignore null values - node.js

code to update :
return db.models.sprout.update({
first_name: args.input.patch.first_name,
last_name: args.input.patch.last_name,
date_of_birth: args.input.patch.date_of_birth,
image_url: args.input.patch.image_url,
updated_by: args.input.user_id
},{ where: { sprout_id : args.input.sprout_id }}).then((rowsUpdated) => {
none of the fields have not null constraint in table.
this should ideally update only, values which are provided, in the args.input.patch i provided only image_url
i get this error.
notNull Violation: sprout.first_name cannot be null,\nnotNull Violation: sprout.last_name cannot be null
weird, the insert works the way i want, only inserts whichever is present
return db.models.sprout.create({
sprout_id: uuidv4(),
first_name: args.input.sprout.first_name,
last_name: args.input.sprout.last_name,
date_of_birth: args.input.sprout.date_of_birth,
image_url: args.input.sprout.image_url,
created_by: args.input.user_id
})
if i dont give image_url or any other field , insert works fine, and ignore the null, but the update doesn't .
How can i make update to ignore the null values.

There is such thing in sequelize: omitNull, you should set it to true. Please check it in the docs: http://docs.sequelizejs.com/class/lib/model.js~Model.html
However here: https://github.com/sequelize/sequelize/issues/2352 engineer from their team said that "omitNull was a dirty hack, there are better ways to solve it now." and he didn't recommend the right approach.
Personally, I recommend you to clear null fields from your object before passing it to sequelize. Please look at this thread: Remove blank attributes from an Object in Javascript

I suggest only passing the fields you actually want to update. This will make the code a lot more readable.
Pass the fields into a function like below. This function will return a new object only containing the fields that are not null
const getFieldsToUpdate = (fields) => {
return {
...fields.first_name && { first_name: fields.first_name },
...fields.last_name && { last_name: fields.last_name },
...fields.date_of_birth && { date_of_birth: fields.date_of_birth },
...fields.image_url && { image_url: fields.image_url },
...fields.user_id && { updated_by: fields.user_id },
}
};
const fieldsToUpdate = getFieldsToUpdate(args.input.patch);
return db.models.sprout.update(fieldsToUpdate, {
where: { sprout_id : args.input.sprout_id }
})
.then((rowsUpdated) => {...

The error is seems to be due to the parameter allownull which is set as true...

good point ashish. WoW, very good understanding, it was because of the allownull being set to false on certain fields. i tried after removing it, and it worked as expected. Both ashish and Michael solution worked. I prefer Ashish solution, since it is more native, and filtering is done by sequelize as opposed to custom code (thanks Michael for the code) , i saw in sequilze code, they do similar thing

Related

How can I add a Unique rule using indicative?

I am using Indicative in my project to validate my controller, but, Indicative don't have a "Unique" rule in "Validation Rules", but the framework Adonis have a rule call "unique" that does exactly what i need.
My project is made in Adonis, but i prefer to use "Indicative" and not "Validator" in Adonis, because i think is more easy and beautiful write the code direct in the Controller
code: 'required|string|max:255',
description: 'required|string|max:255|unique:tabela',
authors: 'string|max:255',
status: 'boolean',
user_id: 'integer',
created_at: [
importValidate.validations.dateFormat(['YYYY-MM-DD HH:mm:ss'])
],
updated_at: [
importValidate.validations.dateFormat(['YYYY-MM-DD HH:mm:ss'])
]
}
In the example above, I need the "code" to be "Unique" and return an error message and a response status. How can I do this?
The unique method of Validator will automatically search in the database. I don't think it's possible to do it with Indicative
I propose this solution (in your controller):
const { validate } = use('Validator')
...
const rules = {
code: 'unique:<table_name>,<field_name>'
}
const messages = {
'code.unique': '...'
}
const validation = await validate({ code: ... }, rules, messages)
if (validation.fails()) {
...
}
To use this command it is necessary to use Validator. I don't think there's an equivalent with Indicative

Mongoose FindOneandUpdate - Only update Specific fields leave others alone

Maybe I'm not wording this properly but hopefully I can find some help with this. I've been playing around with mongoose schema's and boy its doing my head in with this issue..
I want to update a document on a collection under a specific ID, I've got all that working however. I'm running into an issue where since I never supplied values for others I dont want updated it makes them either all Null or deletes them if omitUndefined is turned on.
router.put('/api/playerinfo/:player_steamID/main_server', passport.authenticate('basic', {session: false}),
function(req, res){
const params = {
main_server: {
game_stats:{
kills: req.body.main_server.game_stats.kills,
deaths: req.body.main_server.game_stats.deaths,
}
}
};
PlayerInfo.findOneAndUpdate({player_steamID: req.body.player_steamID }, {$set: params}, { upsert: true, new: true }).then(playerinfo =>{
res.json(playerinfo)
});
});
For example:
Current Stored information
{
"name": "Jimbo",
"steamID": "123456",
"main_server": {
"game_stats": {
"kills": 3,
"deaths":0
}
}
}
Then if we used the above code to modify the fields.. But we did not supply a death value for that field, it would null out the bottom or remove it entirely if omit is true.
Sending postman update with the following:
{
"name": "Jimbo",
"steamID": "123456",
"main_server": {
"game_stats": {
"kills": 34
}
}
}
Updated Information
{
"name": "Jimbo",
"steamID": "123456",
"main_server": {
"game_stats": {
"kills": 34,
"deaths": null
}
}
}
What I would like to know is the best way to modify multiple fields without it "changing" the last value. if a field is missing. Is that possible?
IE: If i only supply kills value and leave json update for deaths blank it would retain the old value from deaths.
Thanks.
-- Fixed.
created a object inside $set then to update old values without replacing you need to wrap them in quotes. 'main_server.game_stats.kills' : req.body.....kills etc.
Use $set to update only certain fields.
PlayerInfo.findOneAndUpdate({steamID: req.body.steamID },{ $set: {'yourcoll.$.someField': 'Value'} }, { upsert: true, new: true }).then(playerinfo =>{
res.json(playerinfo)
});
Documentation:
https://docs.mongodb.com/manual/reference/operator/update/set/
Fixed. created a object inside $set then to update old values without replacing you need to wrap them in quotes. 'main_server.game_stats.kills' : req.body.....kills etc.
I also did a for loop in the object and assigned props to handle multiple values or single if passed through from json.
You can try just put part of model, which contains specific field for update
_userModelRepository.updateUser( { email: req.user.email }, { specificField: specificValue});
Here's schema implementation
updateUser = function(query, params){
return _userModelRepository.findOneAndUpdate(query, params, function(err, result) {
if (err) {
throw err;
}
});
}
What I did was
retrieve the original value in hidden input field using Ejs something like:
<input type="hidden" name="nameOf_thefield" value="<%= variable %>" />
Pass the value req.body.nameOf_thefield to the variable which you wish not to change

Value to place inside a WHERE clause in Sequelize, PostgreSQL to get everything

I have this piece of code:
return User.findAll({where: {}, include:[
{model: UserInfo, where: {
gender: genderPreference
}}
]
I want to pass such a value in genderPreference so that it gives me all the values. Currently I'm passing either "Male" or "Female" which gives me that data from database accordingly; but if there is no preference for any of the genders then i have no idea on what to do.
I have tried null, " " , "" ; but none of them work. Is there any solution to this?
Thanks.
I actually solved this with a different approach. Placing:
gender: null
actually searches if the gender field in the database is null or not, so I had to solve it with a different approach. I wanted "all the data" in the database.
So, this pretty much solved it:
if(req.body.genderPreference == "none"){
conditionalUserInfo = {model: UserInfo, where:{}};
} else {
conditionalUserInfo = {model: UserInfo, where: {gender: req.body.genderPreference}};
}
return User.findAll({where: {}, include:[
conditionalUserInfo
]
GenderPreference parameter contains either 'Male' or 'Female' or 'none'.
There's a much better way to achieve what you need here without the if/else clause
return User.findAll({where: {}, include:[
{
model: UserInfo,
where: req.body.genderPreference ? { gender: req.body.genderPreference } : {}
}
]
This allows the database search if the body is present, and returns all gender preference if the body isn't present. I believe this is a much cleaner code.
Just don't use quotes on null, it'll work fine
return User.findAll({where: {}, include:[
{model: UserInfo, where: {
gender: null
}}
]
See the tutorial for more information

String + autoincrement number on mongoose

I want to create a String + number every time a value is inserted on database (the number must be autoincrement).
Is it possible to do in Schema? Or do I need to do that before the value's inserted on database?
'question' -> String followed by an autoincrement number
var random = [{
question: 'question1'
},{
question: 'question2'
},{
question: 'question3'
},{
question: 'question4'
}];
const RandomSchema = new Schema({
question: {
type: String,
unique: true
}
})
Autoincrement fields in Mongodb, don't work exactly the same way that they do in an RDBMS. There is a lot more overhead involved. Never the less, creating an auto field is a solved problem. There is also a third party mongoose-autoincrement package that make it a lot easier.
Extending, from that. Your problem is a solved problem too. Because
the string part will always be the same
Simply use string concatenation to prepend 'question' to the auto increment field at display time.
Here is what I implemented with one of the approaches #e4c5 pointed out, without the use of a third-party package.
Define add.js as below:
db.counters.insert(
{
_id: "itemid",
seq: 0
}
)
function getNextSequence(id) {
var ret = db.counters.findAndModify(
{
query: { _id: id },
update: { $inc: { seq: 1 } },
new: true
}
);
return ret.seq;
}
db.randoms.insert(
{
question: "question"+getNextSequence("itemid"),
}
)
db.randoms.insert(
{
question: "question"+getNextSequence("itemid"),
}
)
After starting a mongod instance, do mongo < add.js.

Sails.js - Get an object (model) using multiple join

I am new to node.js and newer to Sails.js framework.
I am currently trying to work with my database, I don't understand all the things with Sails.js but I manage to do what I want step by step. (I am used to some PHP MVC frameworks so it is not too difficult to understand the structure.)
Here I am trying to get a row from my database, using 2 JOIN clause. I managed to do this using SQL and the Model.query() function, but I would like to do this in a "cleaner" way.
So I have 3 tables in my database: meta, lang and meta_lang. It's quite simple and a picture being better than words, here are some screenshots.
meta
lang
meta_lang
What I want to do is to get the row in meta_table that match with 'default' meta and 'en' lang (for example).
Here are Meta and Lang models (I created them with sails generate model command and edited them with what I needed):
Meta
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'meta'
}
}
};
Lang
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'lang'
}
}
};
And here is my MetaLang model, with 3 functions I created to test several methods. The first function, findCurrent, works perfectly, but as you can see I had to write SQL. That is what I want to avoid if it is possible, I find it more clean (and I would like to use Sails.js tools as often as I can).
module.exports = {
tableName: 'meta_lang',
attributes: {
title : { type: 'string' },
description : { type: 'text' },
keywords : { type: 'string' },
meta:{
model:'Meta',
columnName: 'meta_id'
},
lang:{
model:'Lang',
columnName: 'lang_id'
}
},
findCurrent: function (metaCode, langCode) {
var query = 'SELECT ml.* FROM meta_lang ml INNER JOIN meta m ON m.id = ml.meta_id INNER JOIN lang l ON l.id = ml.lang_id WHERE m.code = ? AND l.code = ?';
MetaLang.query(query, [metaCode, langCode], function(err, metaLang) {
console.log('findCurrent');
if (err) return console.log(err);
console.log(metaLang);
// OK this works exactly as I want (I would have prefered a 'findOne' result, only 1 object instead of an array with 1 object in it, but I can do with it.)
});
},
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs').exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
// I get what I expected (though not what I want): my meta + all metaLangs related to meta with code "default".
// What I want is to get ONE metaLang related to meta with code "default" AND lang with code "en".
});
},
findCurrentOthertest: function (metaCode, langCode) {
MetaLang.find().populate('meta', {where: {code:metaCode}}).populate('lang', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentOthertest');
if (err) return console.log(err);
console.log(metaLang);
// Doesn't work as I wanted: it gets ALL the metaLang rows.
});
}
};
I also tried to first get my Meta by code, then my Lang by code, and MetaLang using Meta.id and Lang.id . But I would like to avoid 3 queries when I can have only one.
What I'm looking for would be something like MetaLang.find({meta.code:"default", lang.code:"en"}).
Hope you've got all needed details, just comment and ask for more if you don't.
Do you know what populate is for ? its for including the whole associated object when loading it from the database. Its practically the join you are trying to do, if all you need is row retrieval than quering the table without populate will make both functions you built work.
To me it looks like you are re-writing how Sails did the association. Id suggest giving the Associations docs another read in Sails documentation: Associations. As depending on your case you are just trying a one-to-many association with each table, you could avoid a middle table in my guess, but to decide better id need to understand your use-case.
When I saw the mySQL code it seemed to me you are still thinking in MySQL and PHP which takes time to convert from :) forcing the joins and middle tables yourself, redoing a lot of the stuff sails automated for you. I redone your example on 'disk' adapter and it worked perfectly. The whole point of WaterlineORM is to abstract the layer of going down to SQL unless absolutely necessary. Here is what I would do for your example, first without SQL just on a disk adapter id create the models :
// Lang.js
attributes: {
id :{ type: "Integer" , autoIncrement : true, primaryKey: true },
code :"string"
}
you see what i did redundantly here ? I did not really need the Id part as Sails does it for me. Just an example.
// Meta.js
attributes: {
code :"string"
}
better :) ?
// MetaLang.js
attributes:
{
title : "string",
desc : "string",
meta_id :
{
model : "meta",
},
lang_id :
{
model : "lang",
}
}
Now after simply creating the same values as your example i run sails console type :
MetaLang.find({meta_id : 1 ,lang_id:2}).exec(function(er,res){
console.log(res);
});
Output >>>
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Now if you want to display what is meta with id 1 and what is lang with id 2, we use populate, but the referencing for join/search is just as simple as this.
sails> Meta_lang.find({meta_id : 1 ,lang_id:2}).populate('lang_id').populate('meta_id').exec(function(er,res){ console.log(res); });
undefined
sails> [ {
meta_id:
{ code: 'default',
id: 1 },
lang_id:
{ code: 'En',
id: 2 },
title: 'My blog',
id: 2 } ]
At this point, id switch adapters to MySQL and then create the MySQL tables with the same column names as above. Create the FK_constraints and voila.
Another strict policy you can add is to set up the 'via' and dominance on each model. you can read more about that in the Association documentation and it depends on the nature of association (many-to-many etc.)
To get the same result without knowing the Ids before-hand :
sails> Meta.findOne({code : "default"}).exec(function(err,needed_meta){
..... Lang.findOne({code : "En"}).exec(function(err_lang,needed_lang){
....... Meta_lang.find({meta_id : needed_meta.id , lang_id : needed_lang.id}).exec(function(err_all,result){
......... console.log(result);});
....... });
..... });
undefined
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Have you tried:
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
});
},

Resources