I'm working on a MEAN app and trying to implement transactions like behavior to MongoDB using FAWN. I'm using mongoose library. When I use fawn, then data is sort of skipping schema conditions like min: 1 for sellingPrice. However, I'm using { useMongoose: true } in run as well.
var mongoose = require('mongoose');
var Fawn = require('fawn');
Fawn.init(mongoose);
//Function code is as follow
var task = Fawn.Task();
let item = await Item.findById(req.params.id);
task.update('items', {_id: item._id}, {type: req.body.type, name: req.body.name, sku: req.body.sku, openingStock: req.body.openingStock, availableStock: req.body.availableStock, purchasePrice: req.body.purchasePrice, sellingPrice: req.body.sellingPrice, profitMargin: req.body.profitMargin, reorderLevel: req.body.reorderLevel, preferredVendor: req.body.preferredVendor});
task.update('items', {_id: item._id}, {sellingPrice: -50});//This line should fail because min value for sellingPrice is 1
task.run({ useMongoose: true })
.then(function(){res.json(item);})
.catch(function(error){res.json({error: {"message": error}});});
I had a number of similar issues with the update, but in the end, the following worked:
try {
await new Fawn.Task()
.update('collection',
{ _id: mongoose.Types.ObjectId(req.params.id) } ,
{ name: 'test', desc: 'description })
.run({useMongoose: true});
}
catch(err) { console.log(err); }
Related
Mongoose newbe here. I got the following function to update the references (deleting them) in the document Post when a Tag is deleted. When I call my GraphQl API this is what I got:
message": "posts.save is not a function"
The function in my gql resolver:
async deleteTag(root, { id }, context) {
const posts = await Post.find();
const tag = await Tag.findById(id);
if(!tag){
const error = new Error('Tag not found!');
error.code = 404;
throw error;
}
posts?.forEach(async (post) => {
await post.tags.pull(id);
})
await posts.save()
await Tag.findByIdAndRemove(id);
return true;
}
This is the Post model:
const PostSchema = new Schema({
body: {
type: String,
required: true
},
tags: {
type: [Schema.Types.ObjectId],
ref: 'Tag',
required: false
},
});
and this is the Tag model:
const TagSchema = new Schema(
{
name: {
type: String,
required: true
},
},
{ timestamps: true }
);
Looks like I can't call the method save() on the array of objects returned by Exercise.find()
I used the same pattern in other functions, the difference is that there I used .findById()
Any solution? Advice and best practice advide are super welcome.
You have to save the posts individually:
posts?.forEach(async (post) => {
await post.tags.pull(id);
await post.save();
})
Or use Model.updateMany() combined with the $pull operator.
FWIW, you should probably limit the number of matching Post documents by selecting only documents that have the specific tag listed:
await Post.find({ 'tags._id' : id });
I am trying to push a subdocument(ApplicationSchema) into my Job schema. But it doesn't seem to work.
Following is my Job Schema :
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
var ApplicationSchema = require('./Application');
const Job = new Schema({
skills : {
type : Array
},
active : {
type : Boolean,
default : false
},
applications: [ApplicationSchema],
userId : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
},{timestamps : true});
export default mongoose.model("Job", Job)
This is subdocument(ApplicationSchema). I have 5 more subdocuments in this schema.
I am pushing an object with a key-value pair of talentId and its value. But it doesn't work.
I get a new object in the array but the object I'm trying to push is not pushed.
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
var notesSchema = require('./notesSchema');
var documentSchema = require('./documentSchema');
var assessmentSchema = require('./assessmentSchema');
var interviewScheduleSchema = require('./interviewScheduleSchema');
var referenceSchema = require('./referenceSchema')
const ApplicationSchema = new Schema({
talentId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Talent'
},
applicationType: {
type: Number
}
notes: [notesSchema],
documents: [documentSchema],
assessment: [assessmentSchema],
interviewSchedule: [interviewScheduleSchema],
references: [referenceSchema]
},{
timestamps: true
});
export default ApplicationSchema;
Following is my code in the API endpoint
.post((req, res, next) => {
Job.findById(req.params.jobId)
.then((job) => {
if (job != null) {
job.applications.push(req.body);
job.save()
.then((job) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(job);
})
}
else {
err = new Error('Job ' + req.params.jobId + 'not found')
err.status = 404;
return next(err);
}
}, (err) => next(err))
.catch((err) => next(err));
})
req.body contains following object
{ talentId: '5a813e1eb936ab308c4cae51' }
If you already have the id of the job document then you can push application object direct by doing the following:
Job.update(
{ _id: req.params.jobId },
{ $push: { applications: req.body} },
callback
);
or you can use promise to handle this. and if you are only saving id of the application then you may want to change your job schema to store Id of the applications instead of whole application schema.
Please read the documentation carefully as this is very basic update query.
You have,
talentId: {type: mongoose.Schema.Types.ObjectId,
ref: 'Talent'}
But your req.body contains:
{ talentId: '5a813e1eb936ab308c4cae51' }
It should be:
{ talentId: mongoose.Types.ObjectId('5a813e1eb936ab308c4cae51') }
Turns out there was nothing wrong with code.
I was using import and export default syntax which didn't seem work well with this.
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
and
export default ApplicationSchema;
I replaced them with Common JS syntax and everything worked fine.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
and
module.exports = ApplicationSchema;
I did this for Job document file and every subdocument file and the code worked.
I have a mongoDB database which is generated using a script that uses only the node.js mongoDB driver without mongoose. Later on, in the application, I want to use mongoose to load a document and have a reference be populated automatically; however, this only ever returns null.
Imagine a task which contains sub-items which each have a title and an assigned person. The assigned person, in this case, is the reference I want to have populated, so the reference lives in an object inside an array in the task schema.
The following code (requiring npm install mongodb mongoose) reproduces the problem (watch out, it destroys a local database named test if you have one already):
const mongodb = require('mongodb');
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
(async () => {
// Step 1: Insert data. This is done using the mongodb driver without mongoose.
const db = await mongodb.MongoClient.connect('mongodb://localhost/test');
await db.dropDatabase();
await db.collection('persons').insertOne({ name: 'Joe' });
const joe = await db.collection('persons').findOne({ name: 'Joe' });
await db.collection('tasks').insertOne({ items: [{ title: 'Test', person: joe._id }] });
await db.close();
// ================
// Step 2: Create the schemas and models.
const PersonSchema = new Schema({
name: String,
});
const Person = mongoose.model('Person', PersonSchema);
const TaskSchema = new Schema({
items: [{
title: String,
person: { type: Schema.Types.ObjectId, ref: 'Person' },
}],
});
const Task = mongoose.model('Task', TaskSchema);
// ================
// Step 3: Try to query the task and have it populated.
mongoose.connect('mongodb://localhost/test');
mongoose.Promise = Promise;
const myTask = await Task.findOne({}).populate('items.person');
// :-( Unfortunately this prints only
// { _id: "594283a5957e327d4896d135", items: [ { title: 'Test', person: null } ] }
console.log(JSON.stringify(myTask, null, 4));
mongoose.connection.close();
})();
The expected output would be
{ _id: "594283a5957e327d4896d135", items: [ { title: 'Test', person: { _id: "594283a5957e327d4896d134", name: "Joe" } } ] }
I have verified that the two _ids actually match using mongo shell:
> db.persons.find({})
{ "_id" : ObjectId("594283a5957e327d4896d134"), "name" : "Joe" }
> db.tasks.find({})
{ "_id" : ObjectId("594283a5957e327d4896d135"), "items" : [ { "title" : "Test", "person" : ObjectId("594283a5957e327d4896d134") } ] }
What am I doing wrong when attempting to populate person? I am using mongoose 4.10.6 and mongodb 2.2.28.
The answer to this problem lies in the fact that the collection name mongoose automatically infers from the model Person is people and not persons.
The problem can be solved either by writing to the people collection in the first part or by forcing mongoose to use the collection name persons:
const Person = mongoose.model('Person', PersonSchema, 'persons');
mongoose plans to remove pluralization in the collection name anyway, see #1350 on Github.
(SOLUTION IN COMMENTS)
I'm trying to add an array of strings into my DB however each time I try i keep getting the following - i.e. it saves the entire array rather than each item separately:
Also, my mongoose schema looks like this:
var personSchema = new mongoose.Schema({
tags: [{type: String}],
createdBy: String
});
Any ideas where I'm going wrong?
Update - as requested in comments more complete code:
So in backend api:
var newPerson = new Person ({
tags: req.body.tags, //coming in as ["test", "test2"]
createdDate: new Date()
});
newPerson.save(function(err) {
if (err) {
res.json({ success: false, message: 'Error '});
} res.json({ success: true, message: 'All OK'});
});
And then in my Angular front end:
function postService($http, API, auth) {
var self = this;
self.post = function(tags) {
return $http.post(API + '/person', {
tags: JSON.stringify(tags) //note have tried just tags
})
}
}
I have been trying this with the built in inheritance features of mongoose (rather than the extend plugin) but haven't been having much luck so far. This is a simplified example of code I am trying to use which exhibits the same problem. This is based on an expanded version of the mongoose documentation for schema inheritance using discriminators - http://mongoosejs.com/docs/api.html#model_Model.discriminator
var util = require('util');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/problem');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
function BaseSchema() {
Schema.apply(this, arguments);
this.add({
name: String,
createdAt: Date
});
}
util.inherits(BaseSchema, Schema);
var BossStatusSchema = new Schema({
status: String
});
var BossStatus = mongoose.model('BossStatus', BossStatusSchema);
var PersonSchema = new BaseSchema();
var Person = mongoose.model('Person', PersonSchema);
var BossSchema = new BaseSchema({
department: String,
bossStatus: {
type: ObjectId,
ref: 'BossStatus'
}
});
var Boss = Person.discriminator('Boss', BossSchema);
Example code to add the documents:
var superBoss = new BossStatus({
status: 'super'
});
var normalBoss = new BossStatus({
status: 'normal'
});
var andy = new Person({
name: 'Andy'
});
var billy = new Boss({
name: 'Billy',
bossStatus: superBoss._id
});
var callback = function(err, result) {
console.dir(err);
console.dir(result);
};
superBoss.save(callback);
normalBoss.save(callback);
andy.save(callback);
billy.save(callback);
So when finding a record without populate:
Person
.findOne({
name: 'Billy'
})
.exec(callback);
The result is as expected, the bossStatus refers to an _id from the bossstatuses collection:
null
{ name: 'Billy',
bossStatus: 52a20ab0185a7f4530000001,
_id: 52a20ab0185a7f4530000004,
__v: 0,
__t: 'Boss' }
When adding the populate call:
Person
.findOne({
name: 'Billy'
})
.populate('bossStatus')
.exec(callback);
The resulting bossStatus property of the Person result is null:
null
{ name: 'Billy',
bossStatus: null,
_id: 52a20ab0185a7f4530000004,
__v: 0,
__t: 'Boss' }
EDIT:
Ok I've just put together what is probably a better example of what I'm trying to achieve, the schema structure lends itself more to a relational DB but hopefully makes the problem clearer.
var util = require('util');
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/problem');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
function BaseSchema() {
Schema.apply(this, arguments);
this.add({
name: {
type: String,
unique: true,
required: true
}
});
}
util.inherits(BaseSchema, Schema);
var DeviceSchema = new BaseSchema();
var LocalDeviceSchema = new BaseSchema({
driver: {
type: ObjectId,
ref: 'Driver'
}
});
var RemoteDeviceSchema = new BaseSchema({
networkAddress: {
type: ObjectId,
ref: 'NetworkAddress'
}
});
var DriverSchema = new Schema({
name: {
type: String,
unique: true,
required: true
}
});
var NetworkHostSchema = new Schema({
host: {
type: String,
unique: true,
required: true
}
});
var NetworkAddressSchema = new Schema({
networkHost: {
type: ObjectId,
ref: 'NetworkHost'
},
port: {
type: Number,
min: 1,
max: 65535
}
});
var Driver = mongoose.model('Driver', DriverSchema);
var NetworkHost = mongoose.model('NetworkHost', NetworkHostSchema);
var NetworkAddress = mongoose.model('NetworkAddress', NetworkAddressSchema);
var Device = mongoose.model('Device', DeviceSchema);
var LocalDevice = Device.discriminator('LocalDevice', LocalDeviceSchema);
var RemoteDevice = Device.discriminator('RemoteDevice', RemoteDeviceSchema);
var networkHost = new NetworkHost({
host: '192.168.2.1'
});
var networkAddress = new NetworkAddress({
networkHost: networkHost._id,
port: 3000
});
var remoteDevice = new RemoteDevice({
name: 'myRemoteDevice',
networkAddress: networkAddress._id
});
var driver = new Driver({
name: 'ftdi'
});
var localDevice = new LocalDevice({
name: 'myLocalDevice',
driver: driver._id
});
var callback = function(err, result) {
if(err) {
console.log(err);
}
console.dir(result);
};
/*
// Uncomment to save documents
networkHost.save(function() {
networkAddress.save(function() {
remoteDevice.save(callback);
});
});
driver.save(function() {
localDevice.save(callback);
});
*/
var deviceCallback = function(err, device) {
if(err) {
console.log(err);
}
switch(device.__t) {
case 'LocalDevice':
console.log('Would create a local device instance passing populated result');
break;
case 'RemoteDevice':
console.log('Would create a remote device instance passing populated result');
break;
}
};
Device
.findOne({name: 'myLocalDevice'})
.populate('driver')
.exec(deviceCallback);
The LocalDevice and RemoteDevice schemas could (and probably would) include other differences..
The switch would for example use a DeviceFactory or something to create the instances. My thinking was it should be possible to search the devices table for a device by 'name' and populate the collection references (if this is the correct terminology?) without having to specify the collection to search in - this was my understanding of the point of schema inheritance - or have I completely misunderstood?
Thanks for replies so far!
You are looking for a Boss, not a Person:
Boss
.findOne({
name: 'Billy'
})
.populate('bossStatus')
.exec(callback);
Looks like a bug. With debugging active, this is what's being shown for the population query:
Mongoose: people.findOne({ name: 'Billy' }) { fields: undefined }
Mongoose: people.find({ _id: { '$in': [ ObjectId("52a221ee639cc03d71000001") ] } }) { fields: undefined }
(the ObjectId shown is the one stored in bossStatus)
So Mongoose is querying the wrong collection (people instead of bossstatuses).
As #regretoverflow pointed out, if you're looking for a boss, use the Boss model and not the Person model.
If you do want to populate bossStatus through the Person model, you can explicitly state a model that needs to be searched for population:
.populate({
path : 'bossStatus',
model : 'BossStatus'
})
// or shorter but less clear:
// .populate('bossStatus', {}, 'BossStatus')
EDIT: (with your Device examples)
driver is part of LocalDeviceSchema, but you're querying the Device model, which has no notion of what driver is and populating driver within the context of a Device instance doesn't make sense to Mongoose.
Another possibility for populating each instance is to do it after you retrieved the document. You already have the deviceCallback function, and this will probably work:
var deviceCallback = function(err, device) {
if(err) {
console.log(err);
}
switch(device.__t) { // or `device.constructor.modelName`
case 'LocalDevice':
device.populate('driver', ...);
break;
case 'RemoteDevice':
device.populate('networkAddress', ...);
break;
}
};
The reason is that the document is already cast into the correct model there, something that apparently doesn't happen when you chain populate with the find.