ReferenceField assigning - mongoengine

I have model:
from flask.ext.security import currennt_user
#instance of LocalProxy wrapped model User(db.Document)
class ContactModel(db.Document, SomeMixin):
user = db.ReferenceField(User, verbose_name='User', required=True)
And faced strange behavior of ReferenceField. Why is working following code:
model = ContactModel(user = current_user.pk, ....)
And does not following:
model = ContactModel()
model.user = current_user.pk
The same issue when I trying to do:
model = ContactModel()
model.user = current_user
Last two pieaces of code throws an error: ValidationError: ValidationError (ContactModel:None) (A ReferenceField only accepts DBRef or documents: ['user'])

current_user.pk has ObjectId type (just id, no info about collection).
current_user has LocalProxy type.
You can't save reference as ObjectId because there are no information about reference collection and mongo use for this BDRef.
You can get DBRef object from mongoengine document with Document.to_dbref method.
So mognoenginge check type to get DBRef explicitly or get it from document with to_dbref.
For current_user you can call to_dbref to get DBRef object or _get_current_object() to get real User object.

Related

How to aggregate a newly created document in Mongoose?

I have a problem when using .create() to create new document, I need to $lookup aggregate 1 field in the document. The problem I am facing is .aggregate is not working on the returned document after created and I get an error if I try to do that: .aggregate is not a function. How can I do correctly aggregate a new created document ?
My code:
let newsDoc = await newsModel.create({...});
newsDoc = await newsDoc.aggregate([...]); // not working
res.status(200).send(newsDoc);
.aggregate() method is a function inside the mongoose model instance, which is essentially a wrapper around the official MongoDB driver.
.create() returns the object that it has been created and not the model instance.
See this:
// Here newsDoc is the news document inserted and NOT the news Model
let newsDoc = await newsModel.create({...});
// Hence this
// newsDoc = await newsDoc.aggregate([...]);
// Causes error
// For you to aggregate, use the model reference:
// This would work
newsDoc = await newsModel.aggregate([...]); // not working
You can pass the _id that is returned from the create() method inside the aggregate() to filter out the object that is running currently in the context.
Hope it helps.

how can i populate objectId inside another objectId in mongoose

I have an chatList and inside it there is messageId as an object id refer to Message Model , and that message model has messages array and inside it senderId as an objectId refer to User Model , in the User Controller i like to populate chatList.messageId.messages.senderId and i try but not working.
thats code i want it to work
.populate('chatList.messageId.messages.senderId')
You Can Follow this code
> ref_id means model reference id
.populate("ref_id")

How to add custom ObjectId to parse-server object?

I have a problem when adding data to my User class by Mongoose that can't update it later by parse API.
I've found that's because of mongoose use ObjectId for _id field and parse just work with plain string as ObjectId.
The question is how can I set my custom unique plain string as ObjectId in Parse Server object creation?
You can do it like this:
const cryptoUtils = require('parse-server/lib/cryptoUtils');
const id = cryptoUtils.newObjectId();
Source: https://github.com/parse-community/parse-server/blob/master/src/cryptoUtils.js

Erase created mongoose model or dynamically change its properties

In mongoose, I need to find a maximal property value of given model. I have tried dynamically created the mongoose model in a function as
var DynamicSchema=new Schema({id:Number},{collection:collection});
var DynamicModel=mongoose.model('DynamicModel',DynamicSchema);
but after second run of the function I get an error 'OverwriteModelError: Cannot overwrite DynamicModel model once compiled.'
So I tried to create a model in intitialization without setting the collection as
var DynamicSchema = new Schema({id:Number});
var DynamicModel=mongoose.model('DynamicModel',DynamicSchema);
and in a function, tried to set the property collection by
var DynamicModel=mongoose.model('DynamicModel');
DynamicModel.set({collection : collection});
But it does not work. Is there any solution for this situation?

Mongoose: what's up with "_doc"?

It seems Mongoose is doing something really funky internally.
var Foo = new mongoose.model('Foo', new mongoose.Schema({a: String, b: Number}));
var foo = new Foo({a: 'test'; b: 42});
var obj = {c: 1};
foo.goo = obj; // simple object assignment. obj should be
// passed by reference to foo.goo. recall goo
// is not defined in the Foo model schema
console.log(foo.goo === obj); // comparison directly after the assignment
// => false, doesn't behave like normal JS object
Essentially, any time you try to deal with properties of a Mongoose model that aren't
a) defined in the model's schema or
b) defined as the same type (array, obj, ..) ... the model doesn't even behave like a normal Javascript object.
Switching line 4 to foo._doc.goo = obj makes the console output true.
edit: trying to reproduce weirdness
example 1:
// Customer has a property 'name', but no property 'text'
// I do this because I need to transform my data slightly before sending it
// to client.
models.Customer.find({}, function(err, data) {
for (var i=0, len=data.length; i<len; ++i) {
data[i] = data[i]._doc; // if I don't do this, returned data
// has no 'text' property
data[i].text = data[i].name;
}
res.json({success: err, response:data});
});
_doc exist on the mongoose object.
Because mongooseModel.findOne returns the model itself, the model has structure (protected fields).
When you try to print the object with console.log it gives you only the data from the database, because console.log will print the object public fields.
If you try something like JSON.stringify then you get to see inside the mongoose model object. (_doc, state ...)
In the case where you want to add more fields in the object and it's not working
const car = model.findOne({_id:'1'})
car.someNewProp = true // this will not work
If later you set the property to the object car and you didn't specify in the Model Schema before then Mongoose model is validating if this field exists and if it's the valid type.
If the validation fails then the property will not be set.
Update
Maybe I misunderstood your original question, but now it looks like the nature of your question changed, so the below information isn't relevant, but I'm leaving it. :)
I tested your code and it works fine for me. Mongoose doesn't execute any special code when you set properties that aren't part of the schema (or a few other special properties). JavaScript currently doesn't support calling code for properties that don't yet exist (so Mongoose can't get in the way of the set of the goo property for example).
So, when you set the property:
foo.goo = { c: 1 };
Mongoose isn't involved. If your console.log was something other than the code you displayed, I could see that it might report incorrectly.
Additionally, when you send the results back as JSON, JSON.stringify is being called, which calls toString on your Mongoose Model. When that happens, Mongoose only uses the properties defined on the schema. So, no additional properties are being sent back by default. You've changed the nature of the data array though to directly point at the Mongoose data, so it avoids that problem.
Details about normal behavior
When you set the property goo using Mongoose, quite a few things happen. Mongoose creates property getters/setters via the Object.defineProperty (some docs). So, when you set the goo property, which you've defined as a [String], a few things happen:
Mongoose code is called prior to the value being set onto the object instance (unlike a simple JavaScript object)
Mongoose creates an array (optionally) to store the data (a MongooseArray) which will contain the array data. In the example you provided, since you didn't pass an array, it will be created.
Mongoose will attempt to cast your data to the right type
It will call toString on the data passed as part of the cast.
So, the results are that the document now contains an array with a toString version of the object you passed.
If you checked the contents of the goo property, you'd see that it's now an array with a single element, which is a string that contains [object Object]. If you'd picked a more basic type or matched the destination property storage type, you would see that a basic equality check would have worked.
you can use toJSON() instead of _doc
Try using lean
By default, Mongoose queries return an instance of the Mongoose Document class. Documents are much heavier than vanilla JavaScript objects, because they have a lot of internal state for change tracking. Enabling the lean option tells Mongoose to skip instantiating a full Mongoose document and just give you the POJO.
https://mongoosejs.com/docs/tutorials/lean.html
Had same problem. Instead of updating my model.
const car = model.findOne({_id:'1'})
let temp = JSON.stringify(car);
let objCar = JSON.parse(temp);
objCar.color = 'Red'; //now add any property you want
this solves my problem
I was stuck on this today... Drove me nuts. Not sure if the below is a good solution (and OP has mentioned it too), but this is how I overcame this issue.
My car object:
cars = [{"make" : "Toyota"}, {"make" : "Kia"}];
Action:
console.log("1. Cars before the color: " + car);
cars.forEach(function(car){
car.colour = "Black"; //color is NOT defined in the model.
});
console.log("2. Cars after the color: " + car);
Problematic console output:
1. Cars before the color: [{"make" : "Toyota"}, {"make" : "Kia"}];
2. Cars after the color: [{"make" : "Toyota"}, {"make" : "Kia"}]; //No change! No new colour properties :(
If you try to pass in this property that was undefined in the model, via doc (e.g. car._doc.color = "black"), it will work (this colour property will be assigned to each car), but you can't seem to access it via EJS (frontend) for some reason.
Solution:
(Again, not sure if this is the best way... but it worked for me): Add in this new property (colour) in the car model.
var carSchema = mongoose.Schema({
make: String,
color: String //New property.
})
With the model redefined, everything worked as normal / expected (no _doc 'hacks' needed etc.) and I lived another day; hope it helps someone else.
There is some weirdness with Mongoose models and you have to check that Mongoose doesn't already have a model created in it's models array.
Here is my solution:
import mongoose from 'mongoose';
createModel = (modelName="foo", schemaDef, schemaOptions = {})=> {
const { Schema } = mongoose;
const schema = Schema(schemaDef, schemaOptions);
const Model = mongoose.models[modelName] || mongoose.model(modelName, schema);
return Model;
}
I use my own mongoose model class and base class for my models. I made this and it should work for you.
For those using spread(...) and/ can't see a solution, here's an example of #entesar's answer
Instead of spread or ._doc in:
import User from "./models/user";
...
async function createUser(req, res) {
const user = await User.create(req.body);
res.status(201).json({
message: "user created",
data: {
...user // OR user._doc,
token: "xxxxxxxx",
},
});
}
...
Use this
import User from "./models/user";
...
async function createUser(req, res) {
const user = await User.create(req.body);
res.status(201).json({
message: "user created",
data: {
...user.toJSON(),
token: "xxxxxxxx",
},
});
}
...
Ps: took me a while to understand the answer.
You should add .lean() on the find to have it skip all the Model "magic".

Resources