Adding a new property to mongo after 'save' in Keystone - mongodb

I recently found out how to change the value of an existing property and saving it to the mongo database when using Keystone JS (How to alter a value before storing it to the database in Keystone JS).
Now I need to add a new property and save it to the database during the same pre('save') phase.
The aims is to say, if the result (existing property) of a game is 'Won', then add a new property 'won' which is a boolean (true). If it matters, the reason I want this is because in a handlebars template I want to say {{#if won}}class="success"{{/if}}
Game.schema.pre('save', function(next) {
if (this.isModified('result')) {
if (this.result === 'Won') {
this.won = true;
}
}
next()
});
But nothing happens. I read that you can't add properties unless they've been set in the schema. So I tried adding Game.schema.set('won', false); above that, but still nothing.
Is there a simple way to do this?

You could look at Mongoose virtuals which are properties that you can get and set but that do not get persisted to the database:
Game.schema.virtual('won').get(function() {
return this.result === 'Won'
})
http://mongoosejs.com/docs/guide.html#virtuals
If you just want to use it in your template then you could also set a specific property on locals in your view.
Perhaps something like this:
...
exports = module.exports = function(req, res) {
var view = new keystone.View(req, res)
var locals = res.locals
locals.games = []
view.on('init', function(next) {
var query = {} // Add query here
Game.model.find(query).exec(function(err, games) {
// Handle error
games.forEach(function(game) {
game.won = game.result === 'Won'
})
locals.games = games
})
})
}
...

Related

Feather.js + Sequelize + postgres 11 : How to patch a jsonb column?

I would like to update several row of my db with the same object.
let say I have a column customText type jsonb which contains an array of object
here my sequelize model :
customText: {
type: DataTypes.JSONB,
allowNull: true,
field: "custom_text"
}
Now from client I send an object:
const obj = {}
const data = {
textid: "d9fec1d4-0f7a-2c00-9d36-0c5055d64d04",
textLabel: null,
textValue: null
};
obj.customText = data
api.service("activity").patch(null, obj).catch(err => console.log(err));
Like the documentation from feathers.js said if I want to replace multiple record, I send an id equal to null.
So now here come the problem, if I do that my column customText will contain the new object only but I want an array of object, so I want to push the new data in the array. How can I patch the data?
My guess is to use a hook in feathers.js and a raw query with sequelize. But I'm not sure how to do that.
I'm not really sure of my answer but this hook work :
module.exports = function() {
return async context => {
debugger;
const sequelize = context.app.get("sequelizeClient");
const customText = JSON.stringify(context.data.customText[0]);
console.log(customField);
let query =
"UPDATE activity SET custom_text = custom_text || '" +
customText +
"' ::jsonb";
console.log(query);
await sequelize
.query(query)
.then(results => {
console.log(results);
context.results = results;
})
.catch(err => console.log(err));
return context;
I still have a problem because after this hook in feathers, the patch continue so it will update my db again.. so i put a disallow() hook.
Also, with this hook i lost the abilities to listening to event
Also i have a concern with the query, i'm not sure if it's better to use :jsonb_insert over ||

Mongoose Error - Mongoose models with same model name

I am working on a NodeJs application and I am using mongoose node package.
Sample Code
I am using following method to create dynamic collections and these collections sometimes fail to persist the data in database -
const Mongoose = require("mongoose");
const Schema = new Mongoose.Schema({
// schema goes here
});
module.exports = function (suffix) {
if (!suffix || typeof suffix !== "string" || !suffix.trim()) {
throw Error("Invalid suffix provided!");
}
return Mongoose.model("Model", Schema, `collection_${suffix}`);
};
I am using this exported module to create dynamic collections based on unique ids passed as suffix parameter. Something like this (skipping unnecessary code) -
const saveData = require("./data-service");
const createModel = require("./db-schema");
// test 1
it("should save data1", function (done) {
const data1 = [];
const response1 = saveData(request1); // here response1.id is "cjmt8litu0000ktvamfipm9qn"
const dbModel1 = createModel(response1.id);
dbModel1.insertMany(data1)
.then(dbResponse1 => {
// assert for count
done();
});
});
// test 2
it("should save data2", function (done) {
const data2 = [];
const response2 = saveData(request2); // here response2.id is "cjmt8lm380006ktvafhesadmo"
const dbModel2 = createModel(response2.id);
dbModel2.insertMany(data2)
.then(dbResponse2 => {
// assert for count
done();
});
});
Problem
The issue is, test 2 fails! It the insertmany API results in 0 records failing the count assert.
If we swap the the order of the tests, test 1 will fail.
If I run the two tests separately, both will pass.
If there are n tests, only first test will pass and remaining will fail.
Findings
I suspected the mongoose model creation step to be faulty as it is using the same model name viz. Model while creating multiple model instances.
I changed it to following and the tests worked perfectly fine in all scenarios -
return Mongoose.model(`Model_${suffix}`, Schema, `collection_${suffix}`);
Questions
This leaves me with following questions -
Am I following correct coding conventions while creating dynamic collections?
Is suspected code the actual cause of this issue (should the model name also be unique)?
If yes, why is it failing? (I followed mongoose docs but it doesn't provide any information regarding uniqueness of the model name argument.
Thanks.
I you are calling insertMany method on dbModel1, where you variable is declared to dbModel2.
Change your test 2 from:
dbModel1.insertMany(data2)
.then(dbResponse1 => {
// assert for count
done()
});
To:
dbModel2.insertMany(data2)
.then(dbResponse1 => {
// assert for count
done()
});

new Model() that has fields set as select:false are not available

This is my Schema:
var userScheme = mongoose.Schema({
aField:String,
info: {
type:{
local: {
email:String
}
},
select:false
}
});
When I try to create a new user this works fine:
var newUser = new User()
newUser.aField="Something"
newUser.save()
But when I try to access the field that has select:false, I can't access the data. so this doesn't work:
var newUser = new User()
newUser.aField="something"
newUser.info.local.email="email#domain.com"
newUser.save()
The error I get is:
TypeError: Cannot read property 'local' of undefined
My guess is that the new Model is returned without the info field becuase it is set to select:false.
How can I make the new Model() return all the fields including those set to 'select:false'?
Thanks!
Turns out the select:false had nothing to do with it.
The culprit was the fact that info has no values by default and there for was not included in the model at all.
The solution was to create a new schema just for info, and to include it in the user schema like this:
var userScheme = mongoose.Schema({
aField:String,
info: {
type:infoSchema,
default:infoSchema, //Without this, the new document will still not include an 'info' document,
select:false
}
});
Hope this helps anyone. This was just 3 hours of my life.

How to return and update a table in bookshelf knex

I am using postgresql, knex, and bookshelf to make queries to update my users table. I would like to find all users who didn't sign in during a specific time and then update their numAbsences and numTardies field.
However it appears that when running a raw sql query using bookshelf.knex the result that I get for users is an array of objects rather than an array of bookshelf objects of objects because I can't save the objects directly to the database when I try to use .save(). I get the exception user.save is not a function.
Does anyone know how I can update the values in the database for the users? I've seen the update function but I need to also return the users in absentUsers so I select them currently.
// field indicates whether the student was late or absent
var absentUsers = function(field){
// returns all users who did not sign in during a specific time
if (ongoingClasses){
return bookshelf.knex('users')
.join('signed_in', 'signed_in.studentId', '=', 'users.id')
.where('signed_in.signedIn', false)
.select()
.then(function(users){
markAbsent(users, field);
return users;
});
}
}
var markAbsent = function(users, field){
users.forEach(function(user){
user[field]++;
user.save();
})
}
I've solved my problem by using another sql query in knex. It seemed there was no way to use a sql query and then use standard bookshelf knex methods since the objects returned were not bookshelf wrapper objects.
var absentUsers = function(field){
// returns all users who did not sign in during a specific time
if (ongoingClasses){
return bookshelf.knex('users')
.join('signed_in', 'signed_in.studentId', '=', 'users.id')
.where('signed_in.signedIn', false)
.select()
.then(function(users){
markAbsent(users, field);
});
}
}
var markAbsent = function(users, field){
users.forEach(function(user){
var updatedUser = {};
updatedUser[field] = user[field]+1;
bookshelf.knex('users')
.where('users.id', user.id)
.update(updatedUser).then(function(){
});
});
}
With your code bookshelf.knex('users') you leave the "Bookshelf world" and are in "raw knex world". Knex alone doesn't know about your Bookshelf wrapper objects.
You may use Bookshelf query method to get the best of both worlds.
Assuming your model class is User, your example would look approximately like
User.query(function(qb) {
qb.join('signed_in', 'signed_in.studentId', 'users.id')
.where('signed_in.signedIn', false);
})
.fetchAll()
.then(function(bookshelfUserObjects) {
/*mark absent*/
return bookshelfUserObjects.invokeThen('save'); // <1>
});
<1> invokeThen: Call model method on each instance in collection

bookshelf js save() command not updating rows in postgresql db

JS beginner trying to get a PostgreSQL DB talking to express.js through bookshelf.js.
github: https://github.com/duskyshelf/bookers-academy/blob/master/booker.js
var knex = require('knex')({
client: 'pg',
connection: "postgres://localhost/bookers"
});
var bookshelf = require('bookshelf')(knex);
var User = bookshelf.Model.extend({
tableName: 'users'
});
var bob = new User({id: 2});
bob.save()
bookshelf.js seems unable to add any content to the db.
Current error message is: "Unhandled rejection CustomError: No Rows Updated'
When you create your model providing your own id, like in
var bob = new User({id: 2});
Bookshelf assumes it is an update operation, not an insertion. It sets the internal isNew attribute to false, and when save() is invoked, instead of INSERT INTO user(id, ...) VALUES (2, ...);, it executes UPDATE user ... WHERE id = 2;.
If there is no user with id = 2 the update will almost silently DO NOTHING.
To force an insert you must change the save() to:
bob.save(null, {method: 'insert'});
Bookshelf save() documentation describes this behavior.
Did you create a User table using knex? One potential problem I can think of is that you do not have any logic that actually creates the table for your Postgres DB. Here is some sample code that will create a table in your database if it doesn't yet exist.
bookshelf.knex.schema.hasTable('User').then(function(exists) {
if(!exists) {
bookshelf.knex.schema.createTable('User'), function(user) {
user.increments('id').primary();
user.timestamps();
}).then(function(table){
console.log('Created Table:', table);
});
}
});
new User({id: 2}).
save().
then((model) => {
res.json({ success: true });
});
No no! new User returns a PROMISE! You can't use it like that.
Try
new User().save()