Mongoose seems to fail quietly - javascript

So have a completely separate issue (I think) which is saving an array to a mongo document. So I took out that part, created a data structure that would have the same issue to try to resolve it. But now my test code seems to finish before the save function returns, just not certain how to resolve either issue, or if they potentially are connected.
The mongoose schema:
var mongoose = require('mongoose');
ObjectId = mongoose.Schema.Types.ObjectId;
var offerSchema = mongoose.Schema({
closeDate: Date,
settleDate: Date,
schemaVersion: Number,
_offered: [{ type: ObjectId, ref: 'User'}], //Ids of thos offered
_offerDate: { type: Date },// The date they where offered
_accepted: [{ type: ObjectId, ref: 'User'}],//Ids of those accepted
_acceptedDate: [{ type: Date }], //Dates when accepted
});
// create the model for users and expose it to our app
module.exports = mongoose.model('offer', offerSchema);
So I have written the code below to recreate the issue, the below code should work but not store a value in _offered.
var offer = require('../models/offerSchema.js')
var indata = {"offer":{"closeDate":"2015-08-31T13:26:36.512Z","settleDate":"2015-08-31T13:26:36.512Z","type":1,"_offered":[{"id":"55dc7994ed0fcf4a58d4a689"},{"id":"55dcd30915e3be545a51bebd"}],"_offerDate":"2015-08-31T13:26:36.512Z"}}
var thisOffer = indata.offer
for ( var i in thisOffer ){
console.log("Got "+ i +" is " + thisOffer[i])
}
var myOffer = new offer(thisOffer);
myOffer._offered = undefined
var promise =
myOffer.save(function(err){
if(err){
console.log('Got an error: ' + err)
}
console.log('Got an id: ' + myOffer._id)
return 0;
}).then(function() {
console.log("I get here and quit?");
})
However, the code seems to complete before the save have completed, and I am actually not certain how to avoid this.

You are mixing logic with callbacks and promises. So you do not need the callback, just act on the promise returned:
myOffer._offered = undefined
myOffer.save()
.then(function() {
console.log("I get here and quit?");
})
.then(null,function(err) {
console.log(err);
});
I noticed you made another attempt using Bluebird promises, but it is not necessary as if you implement as shown here any errors will be appropriately routed.
Threre are notes that mongooose 5.x is slated to have a more standardised approach to promises and/or directly use Bluebird promises when configured

OK, found the issue. mongoose save is not promisified (or at least that is what I recon the issue is), changed the first part to use bluebird for promises, then changed the save to use saveAsync as below
Schema (and connection, and bluebird)
var mongoose = require('mongoose');
var Promise = require('bluebird');
Promise.promisifyAll(mongoose);
mongoose.connect('mongodb://webbetcha:M0t0rWrl3d#localhost/betchadb');
ObjectId = mongoose.Schema.Types.ObjectId;
var offerSchema = mongoose.Schema({
closeDate: Date,
settleDate: Date,
schemaVersion: Number,
_offered: [{ type: ObjectId, ref: 'User'}], //Ids of thos offered
_offerDate: { type: Date },// The date they where offered
_accepted: [{ type: ObjectId, ref: 'User'}],//Ids of those accepted
_acceptedDate: [{ type: Date }], //Dates when accepted
});
// create the model for users and expose it to our app
module.exports = mongoose.model('offer', offerSchema);
And the code
var offer = require('../models/offerSchema.js');
var indata = {"offer":{"closeDate":"2015-08-31T13:26:36.512Z","settleDate":"2015-08-31T13:26:36.512Z","type":1,"_offered":[{"id":"55dc7994ed0fcf4a58d4a689"},{"id":"55dcd30915e3be545a51bebd"}],"_offerDate":"2015-08-31T13:26:36.512Z"}}
var thisOffer = indata.offer
for ( var i in thisOffer ){
console.log("Got "+ i +" is " + thisOffer[i])
}
var myOffer = new offer(thisOffer);
myOffer._offered = undefined
myOffer.saveAsync()
.then(function(doc){
console.log('Got an id: ' + myOffer._id)
})
.catch(function(err){
if(err){
console.log('Got an error: ' + err)
}
});

Related

Sequelize "object" does not have increment function

I am using generate sequelize tables from Django generated models with sequelize-auto in my project. so far so good. I wrote the code to update the rank if I see a url again.
const findLink = async (link) =>
Link.findOne({
where: {
url: link,
},
raw: true,
});
// eslint-disable-next-line no-use-before-define
const insertEvent = async (link, tweetText) => {
// Sync Database table before reading
await sequelize.sync();
findLink(link).then((url) => {
if (url) {
url.increment("rank", {by: 1}).then(()=>{
Event.create({
url_id: url.id,
tweet_text: tweetText,
created_at: new Date(),
updated_at: new Date(),
})
});
} else {
Link.create({
url: link,
rank: 0,
created_at: new Date(),
updated_at: new Date(),
}).then((newLink) => {
Event.create({
url_id: newLink.id,
tweet_text: tweetText,
created_at: new Date(),
updated_at: new Date(),
});
});
}
});
};
But the problem is that when It execute url.increment("rank", {by: 1}) it says that url does not have increment function.
But according to documentation it is clearly stated here. Please let me know if I am doing something wrong? I have searched the internet but I could not find any thing relative. I can update the value with duplicate look up but I am looking for a way If I could update the already found object instead of searching it again.
You are using raw queries
const findLink = async (link) =>
Link.findOne({
where: {
url: link,
},
raw: true,
});
It doesn't return an instance of the Model
See https://sequelize.org/master/manual/raw-queries.html
Edit:
By default the function will return two arguments - a results array, and an object containing metadata (such as amount of affected rows, etc).
A second option is the model. If you pass a model the returned data
will be instances of that model.
// Callee is the model definition. This allows you to easily map a query to a predefined model
const projects = await sequelize.query('SELECT * FROM projects', {
model: Projects,
mapToModel: true // pass true here if you have any mapped fields
});
// Each element of `projects` is now an instance of Project
So the mapToModel option might also work

Obtain Dexie values without using toArray()

I have the following code:
(function () {
var gameDataLocalStorageName = "myTest3";
var defaultUserSettings = {
volumes: {
musicVolume: 0.3,
sfxVolume: 0.5,
voicesVolume: 1
}
};
var savedGames = [
{
screenshot: "data uri here",
day: "1",
month: "1",
year: "1",
time: "1",
gameData: {
fonts: [{
id: 123,
name: "Arial"
}],
globalSpeeches: {
anotherVal: "something"
}
}
}
];
console.log(gameDataLocalStorageName);
console.log(defaultUserSettings);
console.log(savedGames);
/* Create db START */
var db = new Dexie(gameDataLocalStorageName);
db.version(1).stores({
usersData: ""
});
db.usersData.put(defaultUserSettings, 'userSettings');
db.usersData.put(savedGames, 'savedGames');
}());
/* Create db END */
/* Recall db START */
setTimeout(function(){
var db2 = new Dexie("myTest3");
db2.version(1).stores({
usersData: "userSettings,savedGames"
});
db2.usersData.toArray().then(function (results) {
console.log("User settings is: ", results[1]);
console.log("Saved games is: ", results[0]);
});
}, 3000);
Which runs great. However how can I obtain the data again without having to render out as an array toArray(). Currently to obtain them I have to hardcode results[0] and results[1] which is also not in the same order as I entered them into the db.
Ideally I want to do something like:
db2.get('usersData.userSettings');
db2.get('usersData.savedGames');
The sample show you are changing primary key which is not supported:
The first declaration specifies a table "usersData" with outbound primary keys:
db.version(1).stores({
usersData: ""
});
Then in the setTimout callback, you redeclare it with:
db2.version(1).stores({
usersData: "userSettings,savedGames"
});
...which means you want an inbound primary key from the property "userSettings" and and index on property "savedGames".
There are three errors here:
You cannot change declaration without incrementing version number which is not done here.
You cannot change primary key on an existing database.
Promises are not catched so you do not see the errors.
It seems what you really intend is so use Dexie as a key/value store, which is perfectly ok but much simpler to do than the sample shows.
If you put() (or add()) a value using a certain key, you retrieve the same using get().
If so, try the following:
db.version(1).stores({
usersData: "",
});
And don't forget to catch promises or await and do try/catch.
(async ()=>{
await db.usersData.put(defaultUserSettings, 'userSettings')
await db.usersData.put(savedGames, 'savedGames');
// Get using key:
const userSettings = await db.usersData.get('userSettings');
console.log("User settings is: ", userSettings);
const savedGames = await db.usersData.get('savedGames');
console.log("User settings is: ", savedGames);
})().catch(console.error);
However, putting entire arrays as values in a key/value store is not very optimal.
Maybe only have two tables "userSettings" and "savedGames" where each saved game would be its own row? Will you support multiple users or just one single user? If multiple, you could add an index "userId" to your tables.
If so, try the following:
db.version(2).stores({
userSettings: "userId" // userId is primary key
savedGames: "++gameId, userId" // incremented id and userId is foreign key
});
(async ()=>{
await db.userSettings.put({...defaultUserSettings, userId: "fooUser"});
await db.savedGames.bulkPut(savedGames.map(game =>
({...game, userId: "fooUser"}));
// Get user settings:
const userSettings = await db.usersData.get('fooUser');
console.log("User settings is: ", userSettings);
const savedGames = await db.usersData.where({userId: "fooUser"}).toArray();
console.log("Saved games for fooUser are: ", savedGames);
})().catch(console.error);

Mongoose : Cast to ObjectId failed for value "Some String" at path "_id"

New to MongoDB, Javascript stack and need help understanding cause of this error.
I have my model created :
const
Mongoose = require('mongoose');
Schema = Mongoose.Schema,
Model = Mongoose.model;
module.exports = Model('Project',
new Schema({
icon : String,
name : String,
state : String,
number : String
})
);
This is my MongoDB document :
[![MongoDB Document][1]][1]
I am attempting to receive all the documents in the collection when I call the API so therefore as per the Mongoose document I am using the find() method.
Here is my API Implementation:
const Project = require('../../models/project');
router.get('/projects/:page?/:limit?',
function(req, res, next){
const page = Math.max(req.params.page || 1, 1) - 1;
const limit = Math.max(req.params.limit || 20, 20);
//Verified : I am hitting the API
console.log("Reached API /projects");
Project.find()
.populate('icon')
.populate('name')
.populate('state')
.populate('number')
.limit(limit).skip(page * limit).exec(
function(err, project)
{
if (err) { return next(err); }
res.send(project);
}
); //End of exec()
}//End of unction
);
I am successful in making the API call using fetch() but I am receiving "Cast to ObjectId failed error" for all the String values.
I believe there is something really simple within my Mongo DB document that I might be missing. Please help me understand and solve this issue.
**EDIT ---
The error seems to point at the string values of the keys:
**
Thank you
Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). So you're Id cast is not valid, because of string, you need to have ObjectId, some changes need to be made before it, Let's debug:
const alldata = await Project.find()
console.log(alldata) // ?
does this return something, I'm using async await here if it return data then the problem is with your populate because your Id case isn't valid as you save in schema string and you're referring here populate, example of using populate:
module.exports = Model('Project',
new Schema({
icon : [{ type: Schema.ObjectId, ref: 'your icon document' }],
name : [{ type: Schema.ObjectId, ref: 'you name document' }],
state : [{ type: Schema.ObjectId, ref: 'state document' }],
number : [{ type: Schema.ObjectId, ref: 'number document' }]
})
);
but it seems to me that you don't need to use the populate because you have simple data, name, number... so you should be good to go with the above example
Resources: mongoose fetching data, using populate, relation

Mongo: save an array of objects

I'm trying to save an array of objects to my mongo db in one save. I'd like to store each new object into a temporary array first, then send it to the server.js via an ajax post request. But each time it gets to the server, the data becomes just one giant object, and not an array of objects. Is what I'm trying to do possible with Mongo, or am I missing something? Here's my code below.
SCHEMA
var capsuleSchema = new mongoose.Schema({
qa: [{
type: mongoose.Schema.Types.ObjectId, ref: 'Question',
question: String,
answer: String
}],
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User'},
date_of_return: Date,
created_at: { type: Date, default: Date.now }
});
var Capsule = mongoose.model('Capsule', capsuleSchema);
module.exports = Capsule;
APP.JS
var temp_id = id;
var temp_q = quest;
var temp_ans = $('#response').val();
var tempQA = {
id: temp_id,
question: temp_q,
answer: temp_ans
};
saveQuestion(tempQA);
var saveQuestion = function(tempQA) {
answeredQuestions.push(tempQA);
}
var capsuleData = {
questions: answeredQuestions
}
newCapsule(capsuleData);
var newCapsule = function(capsuleData) {
$.ajax({
url: "http://localhost:3000/capsules",
method: "POST",
dataType: 'json',
data: capsuleData
}).done(function(data){
// returns "capsule creation complete"
});
}; // end newCapsule
SERVER.JS
app.post('/capsules', function(req, res) {
var qa_array = [];
var capsule = new Capsule({
qa: req.body.questions,
user: req.cookies.loggedinId,
});
capsule.qa.push(req.body)
capsule.save( function(err, capsule) {
if (err) {
console.log(err);
res.statusCode = 503;
} else {
res.send(capsule);
}; // end if/else
}); // end save
}); // end post time-capsule
UPDATE:
I accidentally put the newCapsule(capsuleData) in the capsule object.--fixed.
I think the problem is in the ajax request. When the Array of objects goes through and gets to the server.js it gets reformatted into and array of one object containing array-innumerated, key:value pairs as strings even before I do anything with it on the server side. ("ie: 'questions[0][id]' : '12345', 'questions[0][question]' : 'how are you?'" etc.
I need it to stay as an array of objects though.
If you want to save multiple documents, try db.collection.insert
Check it out
The below code is not syntactically correct. capsuleData isn't a function its an object literal.
var capsuleData = { questions: answeredQuestions newCapsule(capsuleData); }
The below code attaches a method to the object. You also need to include Capsule() as a javascript function that is accessible so new Capsule() can create the javascript object. But that is still creating an object and not an array. You need to create the array client side and then pass it to the server to be processed.
var capsuleData = { questions: answeredQuestions,
anObjectMethod : new Capsule(capsuleData); }
Review this for more in-depth help https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript

Mongoose - Upserting Documents with Nested Models

I have a basic document with a 'checked_in' flag in my express app:
module.exports = Book= mongoose.model('Book', new Schema({
name : String,
checked_in : Boolean
},{ collection : 'Book' }));
I wanted to keep a log of when books are checked in and out so I came up with another schema:
var action = new Schema({
checked_in: Boolean,
});
module.exports = Activity = mongoose.model('Activity', new Schema({
book_id: String,
actions: [action]
},{ collection : 'Activity' }));
The 'book_id' should be the document id of a book and when I update a book I need to either create or update the activity log for that book with a new item inside of actions:
exports.update = function(req, res){
return Book.findById(req.params.id, function(err, book) {
var activity = new Activity({book_id: book.id});
activity.actions.push({
checked_in: req.body.checked_in,
});
Activity.update({ book_id: book.id}, activity.toObject(), { upsert: true }));
book.checked_in = req.body.checked_in;
return device.save(function(err) {
return res.send(book);
});
});
};
The problem I am having is that nothing gets inserted into the Activity collection. If I use .save() then i just get lots of duplicates in the collection.
UPDATE
I've started re-working things with the advice given below but am still not having any luck with this. Here's what I have now:
module.exports = Activity = mongoose.model('Activity', new Schema({
book_id: Schema.ObjectId,
actions: [new Schema({
checked_in: Boolean,
last_user: String
})]
},{ collection : 'Activity' }));
Here's the update code now:
exports.update = function(req, res){
// TODO: Check for undefined.
return book.findById(req.params.id, function(err, book) {
if(!err) {
// Update the book.
book.checked_in = req.body.checked_in;
book.last_user = req.body.last_user;
book.save();
// If there's no associated activity for the book, create one.
// Otherwise update and push new activity to the actions array.
Activity.findById(book._id, function (err, activity) {
activity.actions.push({
checked_in: req.body.checked_in,
last_user: req.body.last_user
})
activity.save();
});
}
});
};
What I want to end up with is a document for each book with an array of check outs/ins that gets updated each time someone checks a book in or out. i.e:
{
book_id: "5058c5ddeeb0a3aa253cf9d4",
actions: [
{ checked_in: true, last_user: 'ralph' },
{ checked_in: true, last_user: 'gonzo' },
{ checked_in: true, last_user: 'animal' }
]
}
Eventually I will have a time stamp within each entry.
There are a couple problems:
You're trying to find the book's activity doc using findById using the book's id instead of the activity's id.
You're not handling the case where the book's activity doc doesn't exist yet.
Try this instead:
Activity.findOne({book_id: book._id}, function (err, activity) {
if (!activity) {
// No Activity doc for the book yet, create one.
activity = new Activity({book_id: book._id});
}
activity.actions.push({
checked_in: req.body.checked_in,
last_user: req.body.last_user
});
activity.save();
});
I see a few things that can be improved...
The book_id field in the Activity model should be Schema.ObjectId instead of a String. You will then be able to use populate if you wish.
You aren't doing any error checking in exports.update. If the user passes in an invalid id, you will want to check if book is undefined or not, as well as the common if (err) return next(err) (this requires your function params to be res, res, next).
When you create the activity in exports.update, you want to use book._id instead of book.id
All the return statements are not needed
The device variable is not declared anywhere, I'm not sure what you are trying to save... I think you meant book there.
You can then just .save() the activity instead of doing the Activity.update.

Categories