mongoose custom schema types - node.js

I read from mongoose documentation that it is possible to create custom schema types to be added to the ones already existing.
As suggested I tried to look into the mongoose-long example: https://github.com/aheckmann/mongoose-long
I need this in an application I am developing in which I have a profile that is a mongoose model. The profile has several fields such as name, surname and so on that are MultiValueField. A MultiValueField is on object with the following structure:
{
current : <string>
providers : <Array>
values : {
provider1 : value1,
provider2 : value2
}
}
The structure above has a list of allowed providers (ordered by priorities) from which the field's value can be retrieved. The current property keeps track of which provider is currently picked to use as value of the field. Finally the object values contains the values for each provider.
I have defined an object in node.js, whose constructor takes the structure above as argument and provides a lot of useful methods to set a new providers, adding values and so on.
Each field of my profile should be initialized with a different list of providers, but I haven't found a way yet to do so.
If I define MultiValueField as an Embedded Schema, I can do so only having each field as Array. Also I cannot initialize every field at the moment a profile is created with the list of providers.
I think the best solution is to define a SchemaType MultiValueFieldType, that has a cast function that returns a MultiValueField object. However, how can I define such a custom schema type? And how can I use custom options when defining it in the schema of the profile?
I already posted a question on mongoose Google group to ask how to create Custom Schema Types: https://groups.google.com/forum/?fromgroups#!topic/mongoose-orm/fCpxtKSXFKc

I was able to create custom schema types only as arrays too. You will be able to initialize the model, but this aproach has drawbacks:
You will have arrays even if you don't need them
If you use ParameterSchema on other model, you will need to replicate it
Custom Schema Type Code:
// Location has inputs and outputs, which are Parameters
// file at /models/Location.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ParameterSchema = new Schema({
name: String,
type: String,
value: String,
});
var LocationSchema = new Schema({
inputs: [ParameterSchema],
outputs: [ParameterSchema],
});
module.exports = mongoose.model('Location', LocationSchema);
To initialize the model:
// file at /routes/locations.js
var Location = require('../models/Location');
var location = { ... } // javascript object usual initialization
location = new Location(location);
location.save();
When this code runs, it will save the initialized location with its parameters.
I didn't understand all topics of your question(s), but I hope that this answer helps you.

Related

Using mongoose, how to add object propety to an object stored in mongodb that is not included in the schema?

I want to be able to add a new object property to an object in a document stored in a mongodb database where the model schema does not include that property. I've seen all sorts of examples on google and none of them seem to work so I want to ask this question myself and get some direct answers.
For example take this scheme
const MapSchema = new mongoose.Schema({
name:{
type: String
}
})
const ListMap = mongoose.model("listmap", MapSchema);
Now I want to add a new property to it using its id
model.ListMap.findByIdAndUpdate(_id, {newprop:"whatever");
model.ListMap.findByIdAndUpdate(_id, {name:"test");
Using those two commands, I can add name:"test" to it but I can not add newprop:"whatever" to it. What I want to do is be able to add a new property without having to declare it in the schema first. I know this seems like it has been asked before but I've googled it all and I don't believe anyone has answered it. They either didn't understand the question or their code doesn't actually work.
Also bonus question, why does mongodb always add an s to collection names? like the above would show up in collection "listmaps", assuming I used .create() to add the first object.
For your first question, you can not add a property to your schema without declaring it first.
you can define a generic property like this:
const MapSchema = new mongoose.Schema({
property:{
title: String,
value: String
}
})
const ListMap = mongoose.model("listmap", MapSchema);
and for your second question you can refer to this answer:
Why does mongoose always add an s to the end of my collection name
-------SOLVED-------------
I am adding this here for others because it was extremely difficult for me to google this. This answer is from another stackexchange link
How to add a new key:value pair to existing sub object of a mongoDB document
based on this, this is how to do what I proposed earlier
const commentMapSchema = new mongoose.Schema({
List:{}
})
const CommentsMap = mongoose.model("commentmap", commentMapSchema);
const update = {$set: {["List."+"test2"] : "data2"}};
CommentsMap.findByIdAndUpdate(id, update);
First you need to declare the property in the schema. This property can hold more properties exactly like how it works with javascript objects. Then use the update method with mongoose with the query that combines the property name with a new property via dot notation. However when adding a property this way, the key must be a string. This works exactly like it does in javascript. You can just create a new name and it'll add to the object properties.
I hope this helps anyone else who landed here.

Dynamic Mongoose Field Type

I am developing an app where a user could store his model on a database using mongoDB and mongoose. Taken from mongoose tutorial the type of the field has to be defined. For example here we have to define that the name is a string.
const personSchema = new mongoose.Schema({
name: String
});
const Person = mongoose.model('Person', personSchema);
Is there any way to make it dynamic to user's input. I want to create a form where a user will enter a field name and select one of the field types that Mongoose offers [String,Number,Date etc], but I cannot figure any way to implement it. To be honest I don't know even if this is a good approach. An alternative would be to pass everything as a String and serialise the input in order to store it. I want to achieve something like that:
const {fieldName,fieldType} = userInput;
const customSchema = new mongoose.Schema({
fieldName: fieldType
});
const CustomModel = mongoose.model('CustomSchema', customSchema);
Is this possible or should I implement another approach? An alternative would be to pass everything as a String and serialise the input in order to store it.
Thank you in advance!
If I understand you correctly it should work like that:
User defines the model to store
Schema is created using the data provided by the user
User can pass the data to store using the previously created model which will validate the user's input later
In fact, I'm working on a project that has the same functionality. Here is how we did it.
A user sends the model and we store it as a string since we need to have the ability to create the model once again.
When the user passes new data to store using the created model we get the string from mongo and parse it to create the schema. This operation is relatively easy (but depends on what you want to achieve as it can get tricky if you want to have some advanced validation) as you have to just create an object with correct values from mongoose. Something like this for every field that the user has defined.
export const fieldConverter = ({name, type}) => {
switch (type) {
case 'String':
return { [name]: String };
case 'Number':
return { [name]: Number };
...
}
When you have your object ready then you can create a model out of it.
The line with accessing your model from mongoose.models is important as the mongoose will cache the model and throw an error if you try to create it once again.
const DatasetModel =
mongoose.models["your-model-name"] ??
mongoose.model("your-model-name", new mongoose.Schema(schema));
Now when you have the model the rest is just like with the normally created one.
This approach worked for us so I'm adding this as inspiration maybe it will help you. If you have any specific questions about the implementation feel free to ask I will be happy to help.
There is also a Mixed type in mongoose if you don't need the validation later. You can check it here: https://mongoosejs.com/docs/schematypes.html#mixed
You can use Schema.Types.Mixed, An "anything goes" SchemaType. Mongoose will not do any casting on mixed paths.
let customSchema = new Schema({custom: Schema.Types.Mixed})
Read more about it here
After some research I figure at that mongoose type can also be strings. For example
const personSchema = new mongoose.Schema({
name: "String"
});
const Person = mongoose.model('Person', personSchema);
Mongoose will handle it

Using Joi schema to get a json object with fields specified in the schema

I am not sure if this type of question has been asked before, but I was not able to find anything related to this. In my current project we use Joi schemas to perform the validations. I like the ability to define custom schemas and run validations on the incoming objects using that schema. I have a task where I need to filter out object properties. Something similar to _.pick but the properties are complex and deal with nested objets and arrays. We already have a joi schemas that we have designed to perform validations but I am thinking of using the same to get the specific properties of the object, like filtering object data using that schema. Something like this:
const Joi = require('joi');
const val = {
a: 'test-val1',
b: 'test-val2'
}
const schema = Joi.object({
a: Joi.string()
});
// now the below result have the object with both `a` and `b`
// properties but I want joi to strip the `b` property from the object
const result = schema.validate(value, { allowUnknown: true });
Joi's documentation doesn't mention anything like this. I have come across this(ajv) library which does do what I want but I wanted to know for sure if this can not be achieved using Joi. Thanks in advance.
Joi offers stripUnkown property that can be used to get only the fields defined in the schema.

Unable to get the value of particular key in mongoose if that key is not present in Schema

UserEventsInfo = new mongoose.Schema({
name: String,
username: String,
event_movie:[String],
event_tour:[String],
event_restaurant:[String],
event_lifetimeevents:[String]
},{strict : false});
I am able to insert new key-value pair other than defined in the schema
but when I try to read the value of that key. I can't. I am using the following code.
UserEventsDetails.find({username:username},function(err,docs){
if(!docs.length)
{
res.send('datanotavailable');
}
else{
res.send(docs[0][eventname]);
}
});
Here eventname is a variable.
When I add that key in the schema it returns the value i.e. work's fine.
Otherwise it is not returning any value.
Looks like there was an issue submitted like this to mongoose. Here is there response:
The benefit we see in a schemaless database is the ability for our data model to evolve as fast as our features require it, without a linear impact on performance and slower deployment cycles with needless migrations.
If you don't want your data to be normalized and validated prior to saving, then you don't need a tool like Mongoose, you can use the driver directly.
After a little digging there is a way to do this, but you will need to have a field with type Schema.Types.Mixed. So it would look like this:
var schema = new Schema({
mixed: Schema.Types.Mixed,
});
var Thing = mongoose.model('Thing', schema);
var m = new Thing;
m.mixed = { any: { thing: 'i want' } };
m.save(callback);
To do a find on a mixed this SO question answers that.
****EDIT
forgot to link the documentation of mixed types

Underscore.js _.extend function does not copy mongoose model properties when they are not defined in the schema?

Why can I not use underscore(_) extend to update mongoose models where the properties are not defined in the schema definition. Is there a way to get around this?
Node model:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var NodeSchema = new Schema({
label: {type : String, default : 'none'}
}, { strict: false })
mongoose.model('Node', NodeSchema)
Node Controller:
var node = new Node();
node = _.extend(node, {"EXTENDNOTinSchema":"TRUE"});
console.log("extend: " + node);
node.set("SETNOTinSchema","TRUE");
console.log("set: " + node);
Console Output:
extend: { __v: 0,
_id: 50bb05656880a68976000001,
label: 'none' }
set: { __v: 0,
_id: 50bb05656880a68976000001,
label: 'none'
SETNOTinSchema: TRUE}
This is happening because if something is not in the schema then Mongoose cannot use 'defineProperty', and this treats the assignment like any other.
So first off, just to be clear.
node = _.extend(node, {"EXTENDNOTinSchema":"TRUE"});
is identical to this:
node['EXTENDNOTinSchema'] = 'TRUE';
Which is entirely different from this, in the general case.
node.set("SETNOTinSchema","TRUE");
The trick is that Mongoose is smart, and using the defineProperty function I mentioned above, it can bind a function to get called for things like this:
node['INSCHEMA'] = 'something';
But for things that are not in the schema, it cannot do this, so the assignment works like a normal assignment.
The part that tripping you up I think is that console.log is doing some hidden magic. If you look at the docs, console.log will call the inspect method of an object that is passed to it. In the case of Mongoose, it's models do not store attributes directly on the model object, they are stored on an internal property. When you assign to a property being watched with defineProperty or call set, it stores the value on the internal object. When you log the model, inspect prints out the internal model contents, making it seem like the model values are stored right on the object.
So when you do
console.log(node);
what you are really seeing is
console.log(node.somehiddenproperty);
So the answer to your question is really, if you have a bunch of values that are not in the schema, you cannot use _.extend. Instead, just use set because it takes an object anyway.
node.set({"EXTENDNOTinSchema":"TRUE"});

Resources