I have a simple schema as
const xpReward = new mongoose.Schema({
...,
receivedFor: {
type: Object,
required: true
}
});
Received for is an object which can have 2 keys and the values can be something like { "articleId": 5} or { "testId": 7}
I want to expand this receivedFor object with an required field, but keeping the possibility to add articleId or testId.
I know I can let it as in example above, because is an object and can have any form, but I want to specify the type there, to can be known in further usage.
I'm thinking at something like:
const xpReward = new mongoose.Schema({
...,
receivedFor: {
learningPath: {
type: String,
required: true
},
articleId | testId: {
type: Number,
required: true
}
}
});
I don't want to use another nested object as
receivedFor: {
learningPath: {
type: String,
required: true
},
// in this example, the old receivedFor will become this valueFor
// this will be just another nested level, one more parent for it
valueFor: {
type: Object,
required: true
}
}
Can be this done somehow better? thx
Would likely need some hooks to go along with this, but this is the basic idea.
const xpReward = new mongoose.Schema({
...,
receivedFor: {
learningPath: {
type: String,
required: true
},
foreignId: {
type: Number,
required: true
},
foreignCollection: { // maybe even make it an enum - articleId | testId
type: String,
required: true
}
}
});
Related
I am using mongoose in node.js. I have the following Schema.
const CustomerSchema = new mongoose.Schema({
...
email: {
type: String,
required: true,
lowercase: true,
trim: true
},
addresses: [
{
addressType: {
type: String,
enum: [ 'personal', 'shipping', 'billing' ]
},
street: {
type: String,
required: true,
trim: true
},
streetNumber: {
type: String,
trim: true
},
floor: {
type: String,
trim: true
},
apartament: {
type: String,
trim: true
},
cp: {
type: String,
required: true,
trim: true
},
district: {
type: String,
trim: true
},
city: {
type: mongoose.Schema.ObjectId,
ref: 'City',
required: true
}
}
]
});
I want to use "virtuals" to "add" a new field in every object in the array addresses?
How I can do this using virtuals? Is it possible?
I can achieve the same result using, but I would like to use virtuals.
const customerDB = await Customer.findById(idCustomer).lean()
customerDB.addresses = customerDB.addresses.map((address) => ({
...address,
addressDesc: mapTypeAddressDescription(address.addressType)
}));
Many Thanks!
As the name suggests virtuals are not added to the MongoDB documents. They are used for computed properties on documents.
Suppose you have a User model. Every user has an email, but you also want the email's domain. For example, the domain portion of 'test#gmail.com' is 'gmail.com'.
Below is one way to implement the domain property using a virtual. You define virtuals on a schema using the Schema#virtual() function.
const userSchema = mongoose.Schema({
email: String
});
// Create a virtual property `domain` that's computed from `email`.
userSchema.virtual('domain').get(function() {
return this.email.slice(this.email.indexOf('#') + 1);
});
const User = mongoose.model('User', userSchema);
let doc = await User.create({ email: 'test#gmail.com' });
// `domain` is now a property on User documents.
doc.domain; // 'gmail.com'
You should check the documentation for more details.
You can do something like this:
CustomerSchema.path('addresses').schema.virtual('fullAddr').get(function() {
return 'foo'
})
Also, it is useful to check this answer on stackoverflow if the above one doesn't work.
I have a Schema that has a property with the type of array of strings that are predefined.
This is what I've tried to do:
interests: {
type: [String],
enum: ['football', 'basketball', 'read'],
required: true
}
The thing is that when I'm trying to enter a wrong value that isn't defined on the enum, to the array it wouldn't validate it with the enum list.
for example, this would pass which it shouldn't:
{ "interests": ["football", "asdf"] }
because "asdf" isn't predefined in the enum list it shouldn't pass the validation but unfortunately, it passes the validation and saves it.
I've tried to check this thing with a string type of values instead of an array of strings and it works.
for example:
interests: {
type: String,
enum: ['football', 'basketball', 'read'],
required: true
}
for example, this is failing as expected:
{ "interest": "asdf" }
In conclusion, I need a schema's property with a type of array of strings that would check it's elements based on predefined values
Is the most effective way to achieve this goal is by using the validate method or there is a better way?
Quoting from here:
const SubStrSz = new mongoose.Schema({ value: { type: String, enum: ['qwerty', 'asdf'] } });
const MySchema = new mongoose.Schema({ array: [SubStrSz] });
Using that technique you will able to validate values inside of your array.
You can try a custom validation?Like this
const userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /\d{3}-\d{3}-\d{4}/.test(v);
},
message: props => `${props.value} is not a valid phone number!`
},
required: [true, 'User phone number required']
}
});
this is the docs:
https://mongoosejs.com/docs/validation.html
Here distributers will be array of distributerObj, similarly you can define object of any type.
const distributerObj = new Schema({
"dis_id": {
"type": "String"
},
"status": {
"type": "String"
}
});
const productSchema = new Schema({
"distributers": {
"type": [distributerObj]
}
});
use ref to make a relation to the defined enum schema
const actionsEnums = new Mongoose.Schema({value: { type: String, enum:["account-deletion","account-update"]}});
const Privilege = new Mongoose.Schema(
{
userLevel: { type: String, required: true },
actions: [{type: String, refs: actionsEnums}],
})
I have the following Mongoose schema definition in my project:
export const ProductSchema: SchemaDefinition = {
type: { type: String, enum: constants.productTypes, required: true },
name: { type: String, required: false },
espData: {
type: { type: String, required: true },
text: { type: String, required: true }
},
error: {
type: {
message: { type: String, required: true },
statusCode: { type: Number, default: null }
},
required: false
}
// Some more definitions...
};
What's important from here is that I have collection of products where each product has its own type (which is a required string property that can have values defined in constants.productTypes), a non-required name field and so on. Also, there's espData field that has its own mandatory type property which is completely different from the top level type. And, there's error property that does not exist at all times, but when it does, it must have message property and optional statusCode property.
I now have to modify this schema so that espData becomes optional field since I may now have products that don't have this property. How do I do that? I tried couple of things, but none of them worked:
First, I modified espData so that it looks the same as error:
espData: {
type: {
type: { type: String, required: true },
text: { type: String, required: true }
},
required: false
},
But, this is not working, most probably because there's so many nested type properties. Funny thing is that it perfectly works for the error property which has the same structure as espData, but without nested type property. The code I used is
const model = new this.factories.product.model();
model.type = 'hi-iq';
// model.espData purposely left undefined
await model.save();
The error I'm getting is Product validation failed: espData.type.text: Path 'espData.type.text' is required., espData.type.type: Path 'espData.type.type' is required. This indicates that model created from schema is created as espData.type.type which is not what I wanted (I wanted espData.type).
Second, I have tried the same from above, just instead of required field, I wrote: default: null which gave me an error TypeError: Invalid value for schema path 'espData.default', got value "null".
So, how do I define espData as an optional field, which must have type and text properties when it exists?
Is this what you want. Create a new Document Schema with all the validations and nest it in another Schema with required: false (its default to false anyway)
const EspDataSchema = new Schema({
type: { type: String, required: true },
text: { type: String, required: true },
},
{
_id: false,
}
);
example
export const ProductSchema = new Schema({
...
espData: {
type: EspDataSchema,
required: false
},
...
})
I am new to Mongoose and would like to know if it is possible to add validators on the fly on some parameters depending on queries. I have for example a schema like below:
var user = new Schema({
name: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
city: { type: String },
country: { type: String }
});
For a simple registration i force users giving the name, the email and the password. The Schema on top is OK. Now later I would like to force users giving the city and the country. Is it possible for example to update a user's document with the parameters city and country on required? I am avoiding to duplicate user schema like below:
var userUpdate = new Schema({
name: { type: String },
email: { type: String },
password: { type: String },
city: { type: String, required: true },
country: { type: String, required: true }
});
What you would need to do in this case is have one Schema and make your required a function which allows null and String:
var user = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
city: {
type: String,
required: function() {
return typeof this.city === 'undefined' || (this.city != null && typeof this.city != 'string')
}
}
});
You can extract this and make it an outside function which then you can use for county etc.
What this does is it makes the field required but also you can set null to it. In this way you can have it null in the beginning and then set it later on.
Here is the doc on required.
As far as I know, no, it is not possible.
Mongoose schema are set on collection, not on document.
you could have 2 mongoose model pointing to the same collection with different Schema, but it would effectively require to have duplicated Schema.
personnally, in your situation, I would create a single home-made schema like data structure and a function who, when feeded with the data structure, create the two version of the Schema.
by example :
const schemaStruct = {
base : {
name: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
city: { type: String },
country: { type: String }
}
addRequired : ["city", "country"]
}
function SchemaCreator(schemaStruct) {
const user = new Schema(schemaStruct.base)
const schemaCopy = Object.assign({}, schemaStruct.base)
schemaStruct.addRequired.forEach(key => {
schemaCopy[key].required = true;
})
const updateUser = new Schema(schemaCopy);
return [user, updateUser];
}
I have an object:
{ SKU: 'TR1234',
Description: 'Item 1',
UoM: 'each',
client_id: '531382e3005fe0c926bd3957',
Meta: { Test: 'test1', Image: 'http://www.aol.com' } }
I'm trying to save it given my schema:
var ItemSchema = new Schema({
sku: {
type: String,
trim: true,
},
description: {
type: String,
trim: true,
},
company_id: {
type: Schema.ObjectId,
ref: 'Client',
},
createdOn: {
type: Date,
default: Date.now
},
updatedOn: {
type: Date,
default: Date.now
}
}, {versionKey: false});
But it doesn't save and I assume it's because of the capitalized key names. However, those are dynamically generated from a CSV which is parsed with https://github.com/Keyang/node-csvtojson
Ideas?
You can also just use a setter in your mongoose schema, like that:
function toLower (v) {
return v.toLowerCase();
}
var UserSchema = new Schema({
email: { type: String, set: toLower }
});
Just apply it to your fields.
There is also one more approach, just:
email : { type: String, lowercase: true }
Update for keys:
If you would like to change keys, you should the approach likes 'ecdeveloper' mentioned below. My answer was for values, so it makes sense to give this reputation to 'ecdeveloper'. Sorry for confusing.
Here is one more approach without creating a new object:
Object.prototype.keysToUpper = function () {
var k;
for (k in this) {
if (this.hasOwnProperty(k))
this[k.toLowerCase()] = this[k];
delete this[k];
}
return this;
};
What about calling toLowerCase() on each key from your object, and build a new object with lower case keys?
// Assumy your object name is obj
var newObj = {};
Object.keys(obj).forEach(function(key) {
newObj[key.toLowerCase()] = obj[key];
});
// Here you can save your newObj