Why is Mongoose replacing key/value pair in an object with _id? - javascript

I am creating my first backend project with Node.js, Express.js and Mongoose. I have a user, with a list of stocks objects [{symbol: amount}].
When the user wants to buy a stock, they send a POST request with stock, an amount, and a verb in this case 'buy'. In the Post, I take the stock and amount from the request body and add it to the User's stock list.
A request with
{stock: 'F', amount: '2', verb: 'buy'}
should add
{'F': '2'}
to the user's stocks. The problem is when I create and push the stock with
stockObject[stock] = amount;
user.stocks.push(stockObject);
user.stocks becomes [{ _id: 54be8739dd63f94c0e000004 }] instead of [{'F': '2'}], but when I make
stockObject={'symbol':stock, 'amount': amount}
and push that I will get
[{'symbol': 'F', 'amount': '2', _id: 54be8739dd63f94c0e000004}]
Why will Mongoose replace my data in the first case, but keep it in the second?
var UserSchema = new Schema({
id: String,
stocks: [{
symbol: String,
amount: Number
}]
});
router.route('/user/:user_id/action')
.post(function(req, res) {
User.findOne({
id: req.params.user_id
}, function(err, user) {
if (err) res.send(err);
var stock = req.body.stock;
var amount = req.body.amount;
var stockObject = {};
if (req.body.verb === 'buy') {
stockObject[stock] = amount;
}
user.stocks.push(stockObject);
user.save(function(err) {
res.json({
stocks: user.stocks
});
});
});
})

The issue is that the first object you're trying to save:
console.log(stockObject);
// { 'F': '2' }
Doesn't match the Schema you've defined for it:
{
symbol: String,
amount: Number
}
Mongoose normalizes objects it saves based on the Schema, removing excess properties like 'F' when it's expecting only 'symbol' and 'amount'.
if(req.body.verb === 'buy') {
stockObject.symbol = stock;
stockObject.amount = amount;
}
To get the output as [{"F": "2"}], you could .map() the collection before sending it to the client:
res.json({
stocks: user.stocks.map(function (stock) {
// in ES6
// return { [stock.symbol]: stock.amount };
var out = {};
out[stock.symbol] = stock.amount;
return out;
});
});
Or, use the Mixed type, as mentioned in "How do you use Mongoose without defining a schema?," that would allow you to store { 'F': '2' }.
var UserSchema = new Schema({
id: String,
stocks: [{
type: Schema.Types.Mixed
}]
});

Related

greet is not a function (However It declared in mongoose file as a Schema method) (JS) -

this is the mongoose file that I run through Node:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/shopApp')
.then(()=>{
console.log('CONNECTION OPEN!!!')
})
.catch(err=>{
console.log('HO NO ERROR!!!')
console.log(err)
})
// alternative syntax for Schema, now we can add additional information. the require.
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
maxlength: 20 // Schema options
},
price: {
type: Number, // number character inside a string will be accepted and the
opposite also!!!!!!!!!!
min: [0, 'Price must be positive'] // minimum character from SchemaTypes options
and then error msg
},
onSale: {
type: Boolean,
default: false
},
categories: {
//[String] which means: it should be an array only consisting of strings
type: [String]
}, //
qty: {
online: {
type: Number,
default: 0
},
inStore: {
type: Number,
default: 0
}
},
size: {
type: String,
enum: ['S', 'M', 'L'] // can be only one of this values, else it return an error
}
})
const product = mongoose.model('product', productSchema);
//instance methods
// someSchema.methods.findSimilarType = function() {
//...
//}
productSchema.methods.greet = function(){
console.log('HELLO!!! HI!!! HOWDY!!!')
} // we want to use regular function
/*const bike = new product({name: 'Cycling Jersy', price: 18.50, categories: ['Cycling',
' Safety'], size: 'XL'}) // the mongo will ignore the color property
bike.save()
.then(data=>{
console.log('IT WORKED!')
console.log(data)
})
.catch(err=>{
console.log('HO NO ERROR!!!')
console.log(err)
})*/
product.findOneAndUpdate({name: 'Tire pump'}, {price: 1}, {new: true, runValidators:
true}) // Schema constraints don't work with updating ( it below minimum price),
// you need to set ---> runValidators: true!!!!!
.then(data=>{
console.log('IT WORKED!')
console.log(data)
})
.catch(err=>{
console.log('HO NO ERROR!!!')
console.log(err)
})
in node.js I loaded this mongoose file and created an instance of the model and tried to run the function greet on it.
const p = new product({name: 'Big Bag', price: 10})
p.greet()
In the background mongod and mongo are running both on the prompt.
I get a message 'greet is not a function'.

Validating req.params with express validator

I want the user to be able to write a specific account number in the endpoint, been trying to validate the endpoint param if it exists in my database. I couldn't get it to work, please what am I doing wrong?
My validation
const validateReq: [
param('accountNumber').exists().custom(acctNo => accountNumberExist(acctNo)),]
My accountNumberExist function
accountNumberExist(inputAcct) {
const isfound = accounts.find(account => account.accountNumber === inputAcct);
if (isfound === undefined) throw new Error('Account Number not found');
}
My accounts file
const accounts = [
{
id: 1,
accountNumber: 1234567890,
createdOn: new Date(),
owner: 1,
type: 'current',
balance: 23444.43,
status: 'active',
},
{
id: 2,
accountNumber: 1234167890,
createdOn: new Date(),
owner: 1,
type: 'savings',
balance: 2233444.43,
status: 'active',
},
{
id: 3,
accountNumber: 9987654321,
createdOn: new Date(),
owner: 2,
type: 'saving',
balance: 73444.43,
status: 'active',
},
];
But this is always throwing the 'Account Number not found' error even though, the req.param exists in my accounts database.
Params are parsed as string by express middleware. Say I make a req to path defined below like /some/1000
app.get('/some/:path', (req, res, next) => {
console.log(typeof req.param.path)
// outputs string
})
So you need to parse the incoming parameter to integer (Number) since you've stored accountNumber as integer. So adding toInt to chain like below should solve it:
const validateReq: [
param('accountNumber').exists().toInt().custom(acctNo => accountNumberExist(acctNo)),
]
accountNumber inside accounts array is a number whereas req.params.accountNumber is a string. You need to convert the data type. You can do it as
accountNumberExist(inputAcct) {
const isfound = accounts.find(account => account.accountNumber.toString() === inputAcct);
if (isfound === undefined) throw new Error('Account Number not found');
}
I think that the problem is your query. find method runs in an asynchronous way, that's why isfound property does not contain the data you expect. Here is a simple approach using promises which works pretty well for me.
// Here is your function.
accountNumberExist(inputAcct) {
return accounts.find({accountNumber: inputAcct})
.then(result => {
if (result.length == 0) {
console.log("Account Number not found");
return Promise.reject('Account Number not found');
}
return Promise.resolve();
});
}

Connect mongoose-array-values to a unique ID

This may seem like a vague question, but I'm going to try to explain the best I can. As a side note, I'm quite new to using mongoose :)
I have a mongoose-schema storing different values for each user, like so...
let userSchema = mongoose.Schema({
user: { type: String, required: true, unique: true },
pass: { type: String, required: true },
files: [{ type: String, required: false }],
});
The "files"-key contains an array of values, lets say for example:
userSchema.files = [value1, value2, value3]
And I want each value to be connected to some kind of ID, so that when I call the specified ID, I get the specified value. Just for demonstrating purposes, it could look something like this:
userSchema.files = [{value:value1, id: id1},
{value:value2, id: id2},
{value:value3, id: id3}]
Then I want to find the specified id, and return it's "value"-key in a request:
router.route("/home/:id")
.get(restrict, function(req, res) {
User.findOne({ user: req.session.Auth.username }, function(error, data) {
data.files.forEach(function(file) {
if (file.id === req.params.id) {
response.render("../home", file.value)
}
}
});
});
How can I do this? Tried pushing an object to files, but that didn't work as expected. Read something about ObjectId, but couldn't quite understand it. Any tips?
I think you simply need to create a separate model for File and connect it to your User model using the 'ref' keyword :
let fileSchema = mongoose.Schema({
_id : Number,
value : String
});
let userSchema = mongoose.Schema({
user: { type: String, required: true, unique: true },
pass: { type: String, required: true },
files: [{ type: Number, ref: 'File' }]
});
let User = mongoose.model('User', userSchema);
let File = mongoose.model('File', fileSchema);
let f1 = new File({ _id: 1, value: 'File 1'});
let f2 = new File({ _id: 2, value: 'File 2'});
let f3 = new File({ _id: 3, value: 'File 3'});
let user1 = new User({user:'chuck', pass:'norris'});
user1.files.push(f1);
user1.files.push(f2);
user1.files.push(f3);
user1.save(function(err){ });
Now to get the data back:
User
.findOne({ user: 'chuck' })
.populate('files') // only works if we pushed refs to children
.exec(function (err, user) {
if (err) return handleError(err);
console.log(user);
//you can now loop through user.files and compare _id
user.files.forEach(function(file) {
if (file._id === req.params.id) {
response.render("../home", file.value)
}
}
});
You can read about mongoose reference population here: http://mongoosejs.com/docs/populate.html

How to add object to collection inside another collection in MongoDB using Node.js

I know how to add object to collection in MongoDB using Node.js, for example:
router.post('/addProduct', function (req, res) {
Partner.findByIdAndUpdate({ _id: req.body.partnerId }, { $push: { "products": { name: req.body.dataProduct.name } } }, { safe: true }, function (err, response) {
if (err) throw err;
res.json(response);
});
});
but what if in product will be another table? How can I simply add object there?
Let's say this is my schema:
var partnerSchema = new mongoose.Schema({
name: String,
products: [
{
name: String,
campaignList: [
{
name: String,
type: String,
startDate: Date,
endDate: Date,
paymentMethod: String,
partnerPayout: Number,
ourPayout: Number
}
]
}]
});
ID in each partner and product are default ._id eg. partner._id and product._id. That's why aren't in schema above. However I sending them from FrontEnd to BackEnd as a req.parameter - normally thing but i wanted to say it for sure :)
Your best bet would bet to define the Schema & Model for the campaign on its own, and add it to the Partner by reference using the _id
var partnerSchema = new mongoose.Schema({
name: String,
products: [
{
name: String,
campaignList: [
{ type : mongoose.Schema.Types.ObjectId, ref : 'campaignModel' }
]
}]
});
var campaignSchema = new mongoose.Schema({
name: String,
type: String,
startDate: Date,
endDate: Date,
paymentMethod: String,
partnerPayout: Number,
ourPayout: Number
});
var campaignModel = mongoose.model('campaignModel', campaignSchema);
var partnerModel = mongoose.model('partnerSchema', partnerSchema);
A good practice is to look for times where you're trying nest semi-complex data, or objects with more than two or three keys, and extract them into their own collection. Not only does it make it easier to search for those documents, it makes it easier to use them in conjunction with other objects.
Be sure to call .populate() during your query so that MongoDB knows to nest the documents from the other collections, otherwise, you'll just have an array of ObjectId.
First match the required products array position. You can confirm this by testing a simple find like:
Partner.find({_id: req.body.partnerId), 'products.name': req.body.dataProduct.name }, { 'products.$': 1})
Use the positional $ operator to push the new object into the array in the matched product element:
Partner.update({_id: req.body.partnerId), 'products.name': req.body.dataProduct.name }, { $push: { 'products.$.campaignList': { name: 'new campaign' }}})
Reference https://docs.mongodb.com/manual/reference/operator/update/positional/
try this:
router.post('/addProduct', function (req, res) {
Partner.findOneAndUpdate({ _id: req.body.partnerId }, { $push: { "products": { name: req.body.dataProduct.name, $push: {"campaignList": {name: req.body.name}} } } }, { safe: true }, function (err, response) {
if (err) throw err;
res.json(response);
});
});
i hope it helps you

mongoose relation between models

I am using mongoose and I have two models: Item and Hashtag.
Hashtag model should contain only name and Item model should contain a list of hashtags (represented by ids).
This is what I've done:
var ItemSchema = new Schema({
hashtags: [ { type: Schema.ObjectId, 'default': null, ref: 'Hashtag' } ],
});
var HashtagSchema = new Schema({
name: { type: String, 'default': '', trim: true },
items: [{ type: Schema.ObjectId, ref: 'Page' }]
});
This is how I try to create an Item:
var item = new Item({
hashtags: ['a', 'b', 'c']
});
item.save(function (err, item) {
if (err) return res.json({ error: err });
res.json(item);
});
Unfortunately I get this error:
CastError: Cast to ObjectId failed for value "a,b,c" at path "hashtags"
How can I solve this?
Since you are using references instead of subdocuments, you need to create the hashtag objects first:
var tagnames = ['a','b','c'];
var hashtags = {}; //for referencing quickly by name later
for (var h in tagnames){
var tag = new Hashtag({
name: tagnames[h]
});
tag.save(function (err, item) {
if (err) console.log('error:',err);
hashtags[item.name] = item;
});
}
Once you have the hashtags created, you can reference them::
var item = new Item({
hashtags: [hashtags.a._id,hashtags.b._id,hashtags.c._id]
});
item.save(function (err, item) {
if (err) return res.json({ error: err });
res.json(item);
});
Then you can use populate to automatically turn the object ids into documents:
Item.find({})
.populate('hashtags')
.exec(function (err, items) {
if (err) return handleError(err);
//items are populated with hashtags
});
If you are just doing simple tagging, then subdocuments may be a better fit. They allow you to declare and save child documents all in one step. The trade-off is that subdocuments belong exclusively to their parent documents. They are not references so any aggregations on them must be done manually.

Categories