A model.js file contains this model :
exports.Conversations = db.sequelize.define('conversations', {
room_id: {
type: db.Sequelize.STRING
},
user_id: {
type: db.Sequelize.STRING
},
friend_id: {
type: db.Sequelize.STRING
},
}, {
timestamps: true,
createdAt:'created_at',
updatedAt:'updated_at',
deletedAt:'deleted_at',
freezeTableName: true // Model tableName will be the same as the model name
});
In query.js I have the following function :
exports.checkRoom = function(user_id,friend_id) {
models.Conversations.findOne({ where: { $or: [{user_id: user_id , friend_id: friend_id}, {user_id: friend_id , friend_id: user_id}] }} ).then(function(conversation) {
return conversation;
});
}
equivalent to:
SELECT
"id", "room_id", "user_id", "friend_id", "created_at", "updated_at"
FROM
"conversations" AS "conversations"
WHERE
(("conversations"."user_id" = '127' AND "conversations"."friend_id" = '124')
OR ("conversations"."user_id" = '124' AND "conversations"."friend_id" = '127'))
LIMIT 1;
When I do a call on that function in my cluster.js
var conversation = query.checkRoom(data.userId,data.friendId));
I get that conversation is undefined.
Found a couple of solutions to catch the object Promise but didnt worked.
Looking forward to your answers.
EDIT
Managed to do that but when calling the query I want to add that answer to a var so I can use it later on. If now i`m doing something like var
conversationId = query.checkRoom(data.userId, data.friendId).then(function(conversation) { return conversation.dataValues.id; })
I get that my var conversationId is [object Promise] .
How can I get and use that Promise outside .then() function ?
You're trying to use checkRoom() as a synchronous function, which it isn't (like most functions dealing with I/O in Node).
Instead, you should return the promise returned by findOne() and handle the resolving (and rejection) of that promise in your calling code:
// query.js
exports.checkRoom = function(user_id, friend_id) {
return models.Conversations.findOne({ where: { $or: [{user_id: user_id , friend_id: friend_id}, {user_id: friend_id , friend_id: user_id}] }} );
}
// calling code
query.checkRoom(data.userId, data.friendId).then(function(conversation) {
// do something with the database result
...
})
Related
I want to write a virtual (get) method for my MongoDb collection (Parts) which needs to access a different schema: I want it to assert if a document is 'obsolete' according to a timestamp available in a different (Globals) collection:
const partsSchema = new Schema({
...
updatedAt: {
type: Date,
},
...
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true },
});
partsSchema.virtual('obsolete').get(async function() {
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' }).exec();
return this.updatedAt < timestamp.value;
});
But when I do a find, I always get a {} in the obsolete field, and not a boolean value...
const p = await parts.find();
...
"obsolete": {},
...
Is there some way to accomplish my goal?
You can do this, but there are a few obstacles you need to hurdle. As #Mohammad Yaser Ahmadi points out, these getters are best suited for synchronous operations, but you can use them in the way you're using them in your example.
So let's consider what's happening here:
partsSchema.virtual('obsolete').get(async function() {
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' }).exec();
return this.updatedAt < timestamp.value;
});
Since the obsolete getter is an async function, you will always get a Promise in the obsolete field when you query your parts collection. In other words, when you do this:
const p = await parts.find();
You will get this:
...
"obsolete": Promise { <pending> },
...
So besides getting the query results for parts.find(), you also need to resolve the obsolete field to get that true or false result.
Here is how I would write your code:
partsSchema.virtual('obsolete').get(async function() {
const Globals = mongoose.model('name_of_globals_schema');
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' });
return this.updatedAt < timestamp.value;
});
Then when querying it...
parts.findOne({_id: '5f76aee6d1922877dd769da9'})
.then(async part => {
const obsolete = await part.obsolete;
console.log("If obsolete:", obsolete);
})
I'm using mongoose to handle my DB queries. I'm trying to update a set of records entirely using this method. Mode code looks like this:
// prepare database query
const filter = { type: 'company' };
const update = req.body.payload; // payload contains the array of objects (i.e. updated records)
const options = { new: true, runValidators: true }
// find and update the taxonomy record
await Taxonomy.updateMany(filter, update, options);
But whenever I run this query I'm getting following error in the console:
Error [MongooseError]: Invalid update pipeline operator: "_id"
I suppose there is something wrong in my update payload. The req.body.payload looks like this:
[
{
_id: '5ef3d08c745428001d92f896',
type: 'company',
name: 'Company Size',
__v: 0
},
{
_id: '5ef3cdc5745428001d92f893',
type: 'company',
name: 'Company Industry',
__v: 0
}
]
Can you please tell me what actually is wrong here?
This is not the right usage of updateMany() - it is aimed to update many documents with a single change.
To update many documents use bulkwrite() (docs) :
async function myUpdateMany(Model, objectsArray) {
try {
let ops = []
for (let obj of (objectsArray || [])) {
ops.push({
updateOne: {
filter: { platformId: obj.platformId },
update: obj,
upsert: false, // set "true" if you want to add a new document if it doesn't exist
}
})
}
Model.bulkWrite(ops, { ordered: false });
} catch (err) {
throw Error("myUpdateMany error: " + err)
}
}
Regarding runValidators, according to this, it seems to work by default.
I am wondering what would be the best approach to make schema functions using mongoose. I have never used this so the way I think is somewhat limited, same goes for looking for docs, without knowing what's available, is not very efficient.
Through docs I found that either using findOneAndUpdate might solve the problem; but there are some constraints.
Here is the code I am planning to run:
models/Bookmark.js
const mongoose = require('mongoose')
const bookmarkItemSchema = new mongoose.Schema({
restaurantId: String,
cachedAttr: {
name: String,
latitude: Number,
longitude: Number,
},
})
const bookmarkListSchema = new mongoose.Schema({
listName: String,
items: [bookmarkItemSchema],
})
const bookmarkSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
lists: [bookmarkListSchema],
})
// const add = (lists, userId) => {
// let bookmark = Bookmark.findOne({userId})
// bookmark.lists.listName === lists.listName //current, new
// ? bookmark.lists.items.push(lists.items)
// : bookmark.lists.push(lists)
// return bookmark
// }
mongoose.model('Bookmark', bookmarkSchema)
Routes/bookmark.js
router.post('/bookmarks', async (req, res) => {
const {lists} = req.body
console.log(lists)
if (!lists) {
return res.status(422).send({error: 'You must provide lists'})
}
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
try {
// const bookmark = Bookmark.add(lists, req.user._id, obj)
// await bookmark.save()
// res.send(bookmark)
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){ // THIS IS UNDEFINED. How to get this object?
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
} catch (e) {
res.status(422).send({error: e.message})
}
})
The req.body looks like this:
{
"lists": {
"listName": "My Saved List",
"items": {
"restaurantId": "abcdefg",
"cachedAttr": {
"name": "abcdefg",
"latitude": 200,
"longitude": 200
}
}
}
}
Basically what I commented out in the models/Bookmark.js file is what I would really like to do.
If the userId's list name already exists, then I would like to just add an item to the list.
Otherwise, I would like to add a new list to the object.
What is the best approach for doing this? Is there a straight forward mongoose api that I could use for this problem? or do I need to make two separated function that would handle each case and make that as schema methods and handle it in the routes file?
var config = require('config.json');
var mongo = require('mongoskin');
var db = mongo.db(config.connectionString, { native_parser: true });
module.exports.getNextSequence = function (name) {
var temp;
db.collection("counters").findAndModify(
{ _id: name }, // query
[], // represents a sort order if multiple matches
{ $inc: { seq: 1 } }, // update statement
{ new: true }, // options - new to return the modified document
function (err, doc) {
temp = doc.value.seq;
console.log(temp); // <-- here the temp is getting printed correctly
}
);
return temp;
}
Using the above code, I am not able to return the value of doc.value.seq. When doing console.log(obj.getNextSequence) it prints undefined.
I want the function to return the value of doc.value.seq.
I'm not familiar with mongoskin so I'm not positive this is correct, but a database query is typically asynchronous, so you need to access the queried value via a callback.
I'm guessing your "getNextSequence" function is returning the "temp" variable before the database query completes (i.e. before the "temp = doc.value.seq" statement).
Try something like this:
module.exports.getNextSequence = function (name, callback) {
var temp;
db.collection("counters").findAndModify(
{ _id: name }, // query
[], // represents a sort order if multiple matches
{ $inc: { seq: 1 } }, // update statement
{ new: true }, // options - new to return the modified document
function (err, doc) {
temp = doc.value.seq;
callback(temp);
}
);
}
Then access "temp" from within the callback passed to getNextSequence.
findAndModify is an asynchronous function. Your console.log line will run after you return temp, which will therefore be undefined. In order to get this to work, you'll want to use an asynchronous approach of your own. There are two available approaches in your situation.
Callbacks:
You're already using a callback, which you provide as the final argument to findAndModify. You could extend this approach and feed this into a callback of your own, as follows:
module.exports.getNextSequence = function (name, callback) {
db.collection("counters").findAndModify(
{ _id: name },
[],
{ $inc: { seq: 1 } },
{ new: true },
function (err, doc) {
if (err) {
return callback(err);
}
callback(null, doc.value.seq);
}
);
}
Of course, this will require you to pass a callback into getNextSequence and follow the callback pattern upstream. You might also want to handle the error from mongoskin and do some handling of your own.
Promises:
If you don't provide a callback to findAndModify, it will return a promise, which you can chain on to, as follows:
module.exports.getNextSequence = function (name) {
return db.collection("counters").findAndModify(
{ _id: name },
[],
{ $inc: { seq: 1 } },
{ new: true }
).then(function (doc) {
return doc.value.seq;
});
}
Again, this will require you to follow the promise pattern upstream. You'll want to read up on promises if you choose this approach, so that you can correctly handle errors, which I have not addressed in the example above.
I am new to NodeJs and MongoDB, i want to insert row with auto increment primary key 'id'. also defined a function called getNextSequence on mongo server.
this is working perfect on Mongodb server
> db.user.insert({
"id" : getNextSequence('user_id'),
"username" : "test",
"email" : "test#test.com",
"password" : "test123"
})
now i want to insert from NodeJs.I have tried this but not working
db.collection('user').insertOne({
id : "getNextSequence('user_id')",
username : query.name,
email: query.email,
password: query.pass
}, function(err, result) {
assert.equal(err, null);
console.log("row insterted ");
callback();
});
Assuming that getNextSequence is a server-script function (i.e. a method you defined and saved via db.system.js.save), it is not callable outside of the server. One way to go is to use eval, which forces the server to evaluate a string as a js code, even though it is not a good practice. Here is an example:
db.eval('getNextSequence(\'user_id\')', function(err, result) {
db.collection('users').insert({
"id" : result,
"username" : "test",
"email" : "test#test.com",
"password" : "test123"
});
});
Another way is to follow the mongo tutorial and to implement the getNextSequence directly in NodeJS. The syntax is pretty much the same:
function getNextSequence(db, name, callback) {
db.collection("counters").findAndModify( { _id: name }, null, { $inc: { seq: 1 } }, function(err, result){
if(err) callback(err, result);
callback(err, result.value.seq);
} );
}
You then use it in your nodeJS code like:
getNextSequence(db, "user_id", function(err, result){
if(!err){
db.collection('users').insert({
"_id": result,
// ...
});
}
});
Note: of course, you need to have set the counters collection as explained in the docs.
You can also use "mongoose-auto-increment".
The code has just 4 lines
var mongoose = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');
autoIncrement.initialize(mongoose.connection);
userSchema.plugin(autoIncrement.plugin, 'user');
example :
npm i mongoose-auto-increment
connections.js :
const mongoose = require('mongoose');
require("dotenv").config;
const uri = process.env.MONGOURL;
mongoose.connect(uri, { useNewUrlParser: true }, (err) => {
if (!err) { console.log('MongoDB Connection Succeeded.') }
else { console.log('Error in DB connection : ' + err) }
});
require('../schema/userSchema');
userSchema.js :
var mongoose = require('mongoose'); // 1. require mongoose
var autoIncrement = require('mongoose-auto-increment'); // 2. require mongoose-auto-increment
var userSchema = new mongoose.Schema({
name: { type: String },
password: { type: String },
email: { type: String, unique: true, required: 'This field is required.' },
});
autoIncrement.initialize(mongoose.connection); // 3. initialize autoIncrement
userSchema.plugin(autoIncrement.plugin, 'user'); // 4. use autoIncrement
mongoose.model('user', userSchema);
To accomplish this, we will create a function that will keep trying to save the document untill it will have been saved with incremented _id
async function retryUntilSave(db, task) {
try {
const index = await db.collection('tasks').find().count() + 1;
const result = await db.collection('tasks').insertOne(Object.assign(task, { _id: index }))
} catch (error) {
if (error.message.includes("_id_ dup key")) {
console.log("ID already exists!")
console.log("Retrying...");
retryUntilSave(db, task)
} else {
console.log(error.message);
}
}
}
We can use task._id: index instead of Object.assign()
finally you can test this by making some concurrent requests
for (let index = 0; index < 20; index++) {
setTimeout(async () => {
await retryUntilSave(db, { title: "Some Task" })
}, 1000);
}
This function will handle easily if two or more tasks submitted at the same time because mogod throws error when we try to insert a document with duplicate _id, then we will retry saving the document again with incremented _id and this process will run until we save the document successfully !
You can also use "mongodb-autoincrement" module of node js. For example:
var autoIncrement = require("mongodb-autoincrement");
exports.yourMethod = function(newData, callback) {
autoIncrement.getNextSequence(db, your-collection-name, function (err, autoIndex) {
newData.id = autoIndex;
//save your code with this autogenerated id
});
}
You can use the below package on a model schema to auto-increment your collection field.
mongoose-auto-increment //you can download it from npm
Here I am not focusing on how to connect MongoDB. I just focus on how you can integrate auto increment in your model/collection/table.
const mongoose = require("mongoose"); //
const autoIncrement = require("mongoose-auto-increment");
const post_schema = new mongoose.Schema({
title: {
type: String,
required: true,
min: 3,
max: 225,
},
slug: {
type: String,
required: true,
},
});
autoIncrement.initialize(mongoose.connection);
post_schema.plugin(autoIncrement.plugin, {
model: "post", // collection or table name in which you want to apply auto increment
field: "_id", // field of model which you want to auto increment
startAt: 1, // start your auto increment value from 1
incrementBy: 1, // incremented by 1
});
module.exports = mongoose.model("post", post_schema);