How to update with Sequelize with 'NOW()' on a timestamp? - node.js

I'm trying to do something like the following:
model.updateAttributes({syncedAt: 'NOW()'});
Obviously, that doesn't work because it just gets passed as a string. I want to avoid passing a node constructed timestamp, because later I compare it to another 'ON UPDATE CURRENT_TIMESTAMP' field and the database and source could be running different times.
Is my only option to just make a database procedure and call that?

You can use Sequelize.fn to wrap it appropriately:
instance.updateAttributes({syncedAt: sequelize.fn('NOW')});
Here's a full working example:
'use strict';
var Sequelize = require('sequelize');
var sequelize = new Sequelize(/*database*/'test', /*username*/'test', /*password*/'test',
{host: 'localhost', dialect: 'postgres'});
var model = sequelize.define('model', {
syncedAt: {type: Sequelize.DATE}
});
sequelize.sync({force: true})
.then(function () {
return model.create({});
})
.then(function () {
return model.find({});
})
.then(function(instance){
return instance.updateAttributes({syncedAt: sequelize.fn('NOW')});
})
.then(function () {
process.exit(0);
})
.catch(function(err){
console.log('Caught error! ' + err);
});
That produces
UPDATE "models" SET "syncedAt"=NOW(),"updatedAt"='2015-02-09 18:05:28.989 +00:00' WHERE "id"=1

Worth mentioning (for people coming here via search) that NOW() isn't standard and doesn't work on SQL server - so don't do this if you care about portability.
sequelize.literal('CURRENT_TIMESTAMP')
may work better

you can use: sequelize.literal('CURRENT_TIMESTAMP').
Example:
await PurchaseModel.update( {purchase_date : sequelize.literal('CURRENT_TIMESTAMP') }, { where: {id: purchaseId} } );

Related

How can I retrieve documents' properties from a pre hook?

I posted this question yesterday because I didn't know how to solve my problem.
Change variable value in document after some time passes?
I was told I need to use a pre hook. I tried to do it, but "this" would refer to the query, not to the document. So I couldn't retrieve the documents to check if the 4 weeks passed. (check the question, you will get it)
Because I don't know how to make this .pre('find') to use variables from each of my document (so it checks if the 4 weeks passed) I was thinking about looping through all of them and checking if 4 weeks passed.
router.get('/judet/:id([0-9]{2})', middleware.access2, function(req, res)
{
var title = "Dashboard";
Somer.find({}, function(err, someri)
{
if(err)
{
console.log(err);
}
else
{
res.render("dashboard", {title: title, id:req.params.id, someri:someri});
}
});
}); ///get route
var someriSchema = new mongoose.Schema({
nume: {type: String, required: true},
dateOfIntroduction: {type:Date, default: Date.now, get: formatareData},
});
someriSchema.pre('find', function(next) {
console.log(this.dateOfIntroduction); <- this will return undefined, because this refers to the query, actually
next();
});///schema and the pre hook. I thought I could use it like this, and inside the body of the pre hook I can check for the date
Here's what I am talking about:
router.get('/judet/:id([0-9]{2})', middleware.access2, function(req, res)
{
var title = "Dashboard | Best DAVNIC73";
Somer.find({}, function(err, someri)
{
if(err)
{
console.log(err);
}
else
{
someri.forEach(function(somer)
{
///check if 4 weeks passed and then update the deactivate variable
})
res.render("dashboard", {title: title, id:req.params.id, someri:someri});
}
});
});
but I think this will be very bad performance-wise if I will get many entries in my DBs and I don't think this is the best way to do this.
So, if I was told correctly and I should use a pre hook for obtaining what I've said, how can I make it refer to the document?
Ok, I think I understood your requirements. this is what you could do:
/*
this will always set a documents `statusFlag` to false, if the
`dateOfIntroduction` was before Date.now()
*/
const mongoose = require('mongoose')
someriSchema.pre('find', function(next) {
mongoose.models.Somer.update(
{ datofIntroduction: { $lte: new Date() }},
{ statusFlag : false})
.exec()
.then((err, result) => {
// handle err and result
next();
});
});
The only problem I see, is that you are firing this request on every find.
in query middleware, mongoose doesn't necessarily have a reference to
the document being updated, so this refers to the query object rather
than the document being updated.
Taken straight from the documentation of mongoose
I pointed you yesterday to their documentation; but here is a more concrete answer.
someriSchema.post('find', function(res) {
// res will have all documents that were found
if (res.length > 0) {
res.forEach(function(someri){
// Do your logic of checking if 4 weeks have passed then do the following
someri.deactivated = true
someri.save()
})
}
})
What this basically do is for every found schema you would update their properties accordingly, your res can have only 1 object if you only queried 1 object. your second solution would be to do the cron
EDIT: This is what you would do to solve the async issue
const async = require('async')
someriSchema.post('find', function(res) {
async.forEach(res, function(someri, callback) {
// Do your logic of checking if 4 weeks have passed
// then do the following - or even better check if Date.now()
// is equal to expiryDate if created in the model as suggested
// by `BenSow`
// Then ONLY if the expiry is true do the following
someri.deactivated = true
someri.save(function (err) {
err ? callback(err) : callback(null)
})
}, function(err){
err ? console.log(err) : console.log('Loop Completed')
})
})

Creating unique index via mongodb native driver

There is a code:
const { MongoClient } = require('mongodb')
const db = MongoClient.connect('mongodb://172.17.0.2:27017/test')
db
.then(
async dataBase => {
eduDb = dataBase.db('edu-service-accounts')
const accounts = eduDb.collection('accounts')
await accounts.createIndex({ email: 1 }, { unique: true })
accounts.insertOne({ email: '123' })
}
)
Code above creates an index, but that is no unique. I already read official docs for native mongoDB driver, but can't handle it.
And yes, I've deleted all old indexex before testing that code.
Can someone please show a code that really create an index with unique.
I mean not part of official doc, or something like that - I need code that works.
NOTE: I tested that code with local db and mlab - the same result.
Like the documentation says: db.createIndex(collectionname, index[, options], callback) the creation returns an index. Try to log the result of the callback. Maybe you are getting an error from the db.
Try something like:
// your connection stuff
accounts.createIndex({ email: 1 }, { unique: true }, function(err, result) {
if(err) {
console.log(err);
} else {
console.log(result);
}
});
After that please provide us the logs.

Sequelize query inside then()

Trying to:
get a list of users
from the user details get the trips created by the users
and based on the output performing some actions
The following is the code I am trying to run.
models.user.findAll({})
.then(function (users) {
for (var i = 0; i < users.length; i++) {
var userName = users[i].name;
var userEmail = users[i].email;
models.trip.findOne({ attributes: [ [userName, 'name'], [userEmail, 'email'], 'id' ], where: { userId: users[i].id } })
.then(function (trip) {
if (trip == null) {
//Send Emails
}
});
}
})
.catch(function (error){ // enter code here
console.log(">>>>>",error);
});
Due to call back the second Sequelize does not run correctly.
Can you please advice on how to approach this issue? Is it using asyncawait/coyield?
You should debug this by using console.log for example. First you should try to print your first callback result, may be the database connection is not properly configured, may be the table is empty. there are many reasons. also it's more comfortably to use .forEach method instead of 'for' loop
array.forEach((item, i, arr)=>{
///....
})
Don't use the async method in for and any loop. It is better to use Promise.all or any async library.
The code will be like that.
var tasks = []
users.forEach(function (user) {
tasks.push(models.trip({/*some attrs*/}).then(function (trip){
if (trip) return Promise.resolve()
return sendEmailPromise(user)
}))
})
Promise.all(tasks).then(function() {
//done
}).catch(errorHandler)
If the models had associations, such that you could just include the other model and add where clause to it.
Sequelize Docs
User.hasMany(Trips, {foreignKey: 'userId'});
User.findAll({
include: {
model: Trips,
where: {userId: id}
}
});

Mongoose.js: isModified flag for attribute with default value

I have a model with a default generated value that doesn't change throughout the document lifetime except in one special case.
A document may get marked as deleted using doc.update({_id: doc._id, deleted_at: new Date()}, {overwrite: true})
In a very special case the document may be "revived" - looked up by it's id and being worked with again afterwards.
In a pre-save hook I need to perform some action (for example store a document in another collection) whenever the document is created or revived.
Consider following simplified code:
'use strict';
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var someSchema = mongoose.Schema({
immutable: {
type: String,
default: function () {
return 'SomeVeryRandomValue';
}
}
});
someSchema.pre('save', function (next) {
if (this.isNew || this.isModified('immutable')) {
console.log('Processing pre-save hook!');
}
next();
});
var SomeModel = mongoose.model('SomeModel', someSchema, 'test');
mongoose.connection.once('open', function (err) {
var testDoc = new SomeModel({});
console.log('New: %j', testDoc.toObject());
testDoc.save(function(err) {
console.log('Initial saved: %j', testDoc.toObject());
testDoc.update({_id: testDoc._id}, {overwrite: true}, function (err) {
// at this point using mongo console:
// > db.test.findOne()
// { "_id" : ObjectId("5617b028bf84f0a93687cf67") }
SomeModel.findById(testDoc.id, function(err, reloadedDoc) {
console.log('Reloaded: %j', reloadedDoc.toObject());
console.log('reloaded isModified(\'immutable\'): %j', reloadedDoc.isModified('immutable'));
reloadedDoc.save(function(err) {
console.log('Re-saved: %j', reloadedDoc);
mongoose.connection.close();
});
});
});
});
});
And the script runtime output:
$ node mongoose-modified-test.js
New: {"_id":"5617b64c5376737b46f6bb98","immutable":"SomeVeryRandomValue"}
Processing pre-save hook!
Initial saved: {"__v":0,"_id":"5617b64c5376737b46f6bb98","immutable":"SomeVeryRandomValue"}
Reloaded: {"_id":"5617b64c5376737b46f6bb98","immutable":"SomeVeryRandomValue"}
reloaded isModified('immutable'): false
Re-saved: {"_id":"5617b64c5376737b46f6bb98","immutable":"SomeVeryRandomValue"}
The immutable is not marked as modified and IMHO it should - original document had no value for that attribute.
A work-around solution is to remove the default value for immutable attribute and define pre-validate hook like this one:
someSchema.pre('validate', function (next) {
if (this.isNew || !this.immutable) {
this.immutable = 'SomeVeryRandomValue';
}
next();
});
This is not exactly what I need because the value won't be generated until I try to validate/save the document. The pre/post-init hooks are not executed on new SomeModel({}) so I can't use those.
Should I open an issue for mongoose.js?
this.$isDefault('immutable') can be used instead.
someSchema.pre('save', function (next) {
if (this.isNew || this.$isDefault('immutable')) {
console.log('Processing pre-save hook!');
}
next();
});
Output of the script with updated pre-save hook:
$ node --harmony mongoose-modified-test.js
New: {"_id":"56276f0c1a2f17ae7e0a03f7","immutable":"SomeVeryRandomValue"}
Processing pre-save hook!
Initial saved: {"__v":0,"_id":"56276f0c1a2f17ae7e0a03f7","immutable":"SomeVeryRandomValue"}
Reloaded: {"_id":"56276f0c1a2f17ae7e0a03f7","immutable":"SomeVeryRandomValue"}
Processing pre-save hook!
Re-saved: {"_id":"56276f0c1a2f17ae7e0a03f7","immutable":"SomeVeryRandomValue"}
Thanks to #vkarpov15 for clarification.

Bookshelf JS Relation - Getting Count

I'm trying to get users count belongs to specific company.
Here is my model;
var Company = Bookshelf.Model.extend({
tableName: 'companies',
users: function () {
return this.hasMany(User.Model, "company_id");
},
users_count : function(){
return new User.Model().query(function(qb){
qb.where("company_id",9);
qb.count();
}).fetch();
},
organization: function () {
return this.belongsTo(Organization.Model, "organization_id");
}
});
method "users" works very well, no problem.
method "users_count" query works well, but cant get value to "company" model.
in routes, i'm using bookshelf models like this;
new Company.Model({id:req.params.id})
.fetch({withRelated:['users']})
.then(function(model){
res.send(model.toJSON())
})
.catch(function(error){
res.send(error);
});
How should i use users_count method, i'm kinda confused (probably because of promises)
Collection#count()
If you upgrade to 0.8.2 you can use the new Collection#count method.
Company.forge({id: req.params.id}).users().count().then(userCount =>
res.send('company has ' + userCount + ' users!');
);
Problem with your example
The problem with your users_count method is that it tries to make Bookshelf turn the result of your query into Models.
users_count : function(){
return new User.Model().query(function(qb){
qb.where("company_id",9);
qb.count();
}).fetch(); // Fetch wanted an array of `user` records.
},
This should work in this instance.
users_count : function(){
return new User.Model().query()
.where("company_id",9)
.count()
},
See relevant discussion here.
EDIT: How to get this in your attributes.
Maybe try something like this:
knex = bookshelf.knex;
var Company = bookshelf.Model.extend({
tableName: 'companies',
initialize: function() {
this.on('fetching', function(model, attributes, options) {
var userCountWrapped = knex.raw(this.getUsersCountQuery()).wrap('(', ') as user_count');
options.query.select('*', userCountWrapped);
}
}
users: function () {
return this.hasMany(User.Model, "company_id");
},
getUsersCountQuery: function() {
return User.Model.query()
.where("company_id",9)
.count();
}
organization: function () {
return this.belongsTo(Organization.Model, "organization_id");
}
});
Check out the bookshelf-eloquent extension. The withCount() function is probably what you are looking for. Your code would look something like this:
let company = await Company.where('id', req.params.id)
.withCount('users').first();
User.collection().query(function (qb) {
qb.join('courses', 'users.id', 'courses.user_id');
qb.groupBy('users.id');
qb.select("users.*");
qb.count('* as course_count');
qb.orderBy("course_count", "desc");
})

Resources