Understanding upsert with $push - javascript

I have a region collection:
var RegionSchema = new Mongoose.Schema({
name: {type: String},
registrations: [{type: Mongoose.Schema.Types.ObjectId, ref: 'registrations'}],
...
});
A registration collection:
var RegistrationSchema = Mongoose.Schema({
firstName: {type: String},
...
});
In my controller I instantiate a registration then save it to my region with the upsert option set to true:
var registration = new Registration(req.body.registration);
...
Region
.update(
{ _id: user.region},
{ $push: {registrations: registration},
{ upsert: true }
)
.exec();
What I find is that an ObjectId("...") does, in fact, get pushed onto the registrations property of the region, e.g.:
{
name: "Northwest",
registrations: [ObjectId("57d038a1466345d52920b194")]
}
but there is no matching document with that _id in the registrations collection. So my question: what am I not understanding about the nature of the upsert flag; does it not suggest that calling save on the registration is unnecessary?

The upsert flag only applies to the collection you're operating on; in this case, the Region collection. So, when calling Region.update, it will create an object with _id of user.region if an object with that ID doesn't exist.
Mongo doesn't enforce strict ID references, so it will let you push an ID onto registrations so long as the ID is valid.
You'll need to save the Registration object first:
var registration = new Registration(req.body.registration);
registration.save(function() {
Region.update(
{ _id: user.region },
{ $push: { registrations: registration } },
{ upsert: true }
).exec();
});

Related

Cannot pull item from a document's property's array

I have 2 models: Commitments and Users. The Users model has a property named commitments whose value is an array of ObjectIds and reference is the Commitments collection:
const modelName = 'users';
const schema = new mongoose.Schema<UserProperties, UsersModel, UserVirtuals>({
(...)
commitments: {
type: [
{
type: mongoose.SchemaTypes.ObjectId,
ref: 'commitments',
},
],
required: true,
default: [],
},
}, {
collection: modelName,
}).loadClass(User);
The Commitments model has a property named owner whose value is an ObjectId that references a document from the Users model's collection:
const modelName = 'commitments';
const schema = new mongoose.Schema<CommitmentProperties, CommitmentsModel, CommitmentVirtuals>({
(...)
owner: {
type: mongoose.SchemaTypes.ObjectId,
required: true,
},
}, {
collection: modelName,
}).loadClass(Commitment);
I wanted to make it so whenever I deleted a document from the Commitments model's collection, it would also remove the ObjectId of that document from the Users model document of ObjectId owner's commitments property. So, in my Commitments model's schema:
schema.pre('deleteOne', async function() {
(<UsersModel>storage.models.get('users')).findOneAndUpdate({
_id: this.owner,
}, {
$pull: {
commitments: this._id,
},
}).exec().catch(reason => {
logger.log({
category: LoggingCategories.MIDDLEWARE,
type: LoggingType.ERROR,
message: reason,
extraInfo: 'COULD NOT REMOVE COMMITMENT FROM ARRAY OF USER COMMITMENTS',
});
});
});
However this is not working. I don't get any errors and I'm sure that the middleware function is getting executed because I tried logging something within it to test and it did log.

Cast to ObjectId failed for value error in Mongoose findOne

I've been struggling with a weird exception and still confused about it after an hour.
CastError: Cast to ObjectId failed for value "pedrammarandi#gmail.com"
at path "_id" for model "Account"
I'm trying to retrieve an Account via email address. Here is my query
export async function getPendingRecipients(user_id, email_address) {
const account = await Account
.find({email: email_address})
.exec();
return true;
}
This is my Schema object
const userGmailSchema = new Schema({
id: {
type: String,
unique: true
},
displayName: String,
image: Object,
accessToken: String,
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
refreshToken: {
type: String,
default: null
},
email: {
type: String,
unique: true
},
emails: [
{
type: Schema.Types.ObjectId,
ref: 'Emails'
}
]
});
I'm not sure, but I guess the problem is you wrote an id field.
In MongoDB, the "primary key" is _id field, which is an ObjectId object (actually it's a 12-byte-value), and in mongoose, id is a virtual getter of _id, easily said, id is an alias of _id.
(A little different is that, _id returns ObjectId, id returns String version of _id.)
By default, mongoose manage _id field automatically, so commonly we should not write anything about id in schema.
If your id is for something like primary key ID in SQL DB, just remove it from mongoose schema. If it's means something else in your app, try to add an option:
const userGmailSchema = new Schema({
// your schemas here
},
{
{ id: false } // disable the virtual getter
})
or rename it.
http://mongoosejs.com/docs/guide.html#id
Hope this helps.

Mongoose schema

I have these mongoose schemas:
var ItemSchema = new Schema({
"pieces": Number,
"item": { type: Schema.Types.ObjectId, ref: 'Items' }
});
var cartSchema= new Schema({
"items": [ItemSchema]
});
but when I want to push a new item in items, mongoose add an _id field(on the new item) but I don't understand why.
if you want to add item without _id field then you should add { _id: false } in ItemSchema.
var ItemSchema = new Schema({
"pieces": Number,
"item": { type: Schema.Types.ObjectId, ref: 'Items' }
}, { _id: false });
Mongoose assigns each of your schemas an _id field by default if one is not passed into the Schema constructor. The type assigned is an ObjectId to coincide with MongoDB's default behavior. If you don't want an _id added to your schema at all, you may disable it using this option.
You can only use this option on sub-documents. Mongoose can't save a document without knowing its id, so you will get an error if you try to save a document without an _id.
Link: http://mongoosejs.com/docs/guide.html#_id

Manage authentication of different types of users in express.js using passport.js

I am having problem managing the states of different types of users using Passport.js in Express.js 4.x.
I have 3 kinds user collections in my mongodb database
1. Member (has his own profile page)
2. Operator (has his own dashboard)
3. Admin (handles the backend)
I have created their separate Login/Registration systems. But only member seems to work, and the others don't. I have even written different sets of login/registration strategies for each user.
Like for the member passport.use('signup') and passport.use('login').
for operator passport.use('op-signup') and passport.use('op-login') and so on.
What I think is that I am not using the correct approach for handling users, means the collections don't need to be separated but role based in a single collection. Right ?
Here is the current mongoose models I have right now;
// Member Schema
var MemberSchema = new Schema({
username: String,
password: String,
name: { first: String, last: String },
locality: String,
// and other attributes
});
module.exports = mongoose.model('Member', MemberSchema);
// OperatorSchema
var OperatorSchema = new Schema({
username: String,
password: String,
name: { first: String, last: String },
officeAddress: String,
privatePhone: Number,
// and other attributes related to the operator
});
module.exports = mongoose.model('Operator', OperatorSchema);
Is the above approach correct or like this ?
var UserSchema = new Schema({
username: String,
password: String,
roles: {
member: { type: Schema.Types.ObjectId, ref: 'Member' },
operator: { type: Schema.Types.ObjectId, ref: 'Operator' },
admin: { type: Schema.Types.ObjectId, ref: 'Admin' }
}
});
module.exports = mongoose.model('User', UserSchema);
// and then plug the sub models to this parent one
// Member Schema
var MemberSchema = new Schema({
_user: { type: Schema.Types.ObjectId, ref: 'User' },
name: { first: String, last: String },
locality: String,
// and other attributes
});
module.exports = mongoose.model('Member', MemberSchema);
// OperatorSchema
var OperatorSchema = new Schema({
_user: { type: Schema.Types.ObjectId, ref: 'User' },
name: { first: String, last: String },
officeAddress: String,
privatePhone: Number,
// and other attributes related to the operator
});
module.exports = mongoose.model('Operator', OperatorSchema);
I am quite confused here and in a stuck situation, because when a user state is managed in session after login, the user object is exposed to the request object, and so it can only handle one type of user at a time, and may be member, operator and admin can't log in at the same time from the same browser.
So how do I manage all of these user as different instances in the browser ?
I am quite a newbie in Node.js and coming from a PHP background where managing user states was a breeze :)
What i would do is to add plugins, because you are duplicating username and password field, it is very redundant
models/plugins/member.js
module.exports = function(schema) {
schema.add({
// All the appropriate fields that your member schema need
role: String,
});
}
models/user.js
var member = require(./plugins/member);
var UserSchema = mongoose.Schema({
username: String,
password: String
});
UserSchema.plugins(member);
Later on when you want to check which user could access to which route, use middleware to check it
create this in your passport configuration
exports.requireRole = function(role) {
return function(req, res, next) {
if (req.user && req.user.role === role) next();
else
res.send(404);
}
}
In your route later
app.get('/profile', requireRole('member'), function(req, res) {
// do whatever you want to do
});
app.get('/dashbord', requireRole('operator'), function(req, res) {
// do whatever you want to do
});
There are a lot of ways to implement different access level to a user. This method is one of many.
The best solution would be to use schema inhertiance. That is why we use an ORM like mongoose.
var VehicleSchema = mongoose.Schema({
make : String,
}, { collection : 'vehicles', discriminatorKey : '_type' });
var CarSchema = VehicleSchema.extend({
year : Number
});
var BusSchema = VehicleSchema.extend({
route : Number
})
var Vehicle = mongoose.model('vehicle', VehicleSchema),
Car = mongoose.model('car', CarSchema),
Bus = mongoose.model('bus', BusSchema);
var accord = new Car({
make : 'Honda',
year : 1999
});
var muni = new Bus({
make : 'Neoplan',
route : 33
});
accord.save(function(err) {
muni.save(function(err) {
// vehicles are saved with the _type key set to 'car' and 'bus'
});
})
At this point in MongoDB you will have documents similar to this
{ "_type" : "car", "make" : "Honda", "year" : 1999, "_id" : ObjectId("5024460368368a3007000002"), "__v" : 0 }
{ "_type" : "bus", "make" : "Neoplan", "route" : 33, "_id" : ObjectId("5024460368368a3007000003"), "__v" : 0 }
Source
when querying
Vehicle.find({}, function(err, vehicles) {
console.log(vehicles[0]); // vehicles[0] instanceof Car === true
console.log(vehicles[1]); // vehicles[1] instanceof Bus === true
});
Checkout source / more examples by looking at briankircho little cheatsheet enter link description here

Mongoose/MongoDb ,how to validate an array of Ids against another model

I have 2 moongose Schema:
var Schema2 = new Schema({
creator : { type: String, ref: 'User'},
schema_name : [{ type: String}],
});
var Schema1 = new Schema({
creator : { type: String, ref: 'User'},
schema_ref : [{ type: String, ref: 'Schema2' }],
});
Would like to know which is the best practice when I create a new Schema1 check that every element of array schema_ref, have the same creator.
Because schema1 elements are added by client form and so i have to check that the schema_ref elements are owned by same User that send the form
You can try with either validator function, or with a simple 'save' middleware:
Schema1.pre('save', function(next) {
let owner;
for (let entry in this.schema_ref) {
if (!owner) {
owner = entry;
} else {
if (entry !== owner) {
return next(new Error("owner mismatch");
}
}
}
});
Also, your schema might not work as you expect it to, it looks like you actually need:
schema_ref: [{
type: {type: String},
ref: "User"
}]
Additionally, take a look at id-validator plugin, or some similar to that - it will, in addition to your validation, also check that all ref-type properties like this actually exist in the other (Users) collection.

Categories

Resources