Mongoose - how to use model inheritance in a sub document collection - mongodb

I have this model setup. I want a parent record with an array of sub docs.
The sub docs have a schema, and use inheritance.
//
// Children
function abstractSchema() {
Schema.apply(this, arguments);
this.add({
name: String
});
};
util.inherits(abstractSchema, Schema);
var mySchema = new abstractSchema();
//
// Inherited Types
var textPropertySchema = new abstractSchema({
length: Number
});
var numberPropertySchema = new abstractSchema({
dp: Number
});
//
// Parent Model
var myModelSchema = mongoose.Schema({
name: String,
properties : [mySchema]
});
When i save each an instance of numberPropertySchema or textPropertySchema, the _t (type is written) and is able to deserialise properly.
When however added as a sub doc array, they're all persisted with the base object properties only.
Is there any way round this? any extensions that could be used?
Thanks
sam

Related

How can I transform an object schema's property schemas to derive a new object schema?

Is there a way to introspect and then transform a Joi schema?
I see there is any.describe() which "returns an object that represents the internal configuration of the schema" but then I have to map that internal configuration back to schema builder calls and re-create the schema myself.
e.g. given
const PersonSchema = Joi.object().keys({
firstName: Joi.string().required(),
lastName: Joi.string().required(),
age: Joi.number().required(),
});
I want to define a generic function to take any object schema and make its properties support arrays of the original type equivalent to the following:
const PersonFilterSchema = Joi.object().keys({
firstName: Joi.array().items(Joi.string().required()).optional(),
lastName: Joi.array().items(Joi.string().required()).optional(),
age: Joi.array().items(Joi.number().required()).optional(),
});
i.e. I want to be able to write something like this:
function createFilterSchema(schema) {
return Joi.object().keys(
Object.fromEntries(
schema
.getEntries()
.map(([key, value]) => [key, Joi.array().items(value).optional()])
)
);
}
and then elsewhere use it with whatever raw schemas I have:
const PersonFilterSchema = createFilterSchema(PersonSchema);

Feather.js + Sequelize + postgres 11 : How to patch a jsonb column?

I would like to update several row of my db with the same object.
let say I have a column customText type jsonb which contains an array of object
here my sequelize model :
customText: {
type: DataTypes.JSONB,
allowNull: true,
field: "custom_text"
}
Now from client I send an object:
const obj = {}
const data = {
textid: "d9fec1d4-0f7a-2c00-9d36-0c5055d64d04",
textLabel: null,
textValue: null
};
obj.customText = data
api.service("activity").patch(null, obj).catch(err => console.log(err));
Like the documentation from feathers.js said if I want to replace multiple record, I send an id equal to null.
So now here come the problem, if I do that my column customText will contain the new object only but I want an array of object, so I want to push the new data in the array. How can I patch the data?
My guess is to use a hook in feathers.js and a raw query with sequelize. But I'm not sure how to do that.
I'm not really sure of my answer but this hook work :
module.exports = function() {
return async context => {
debugger;
const sequelize = context.app.get("sequelizeClient");
const customText = JSON.stringify(context.data.customText[0]);
console.log(customField);
let query =
"UPDATE activity SET custom_text = custom_text || '" +
customText +
"' ::jsonb";
console.log(query);
await sequelize
.query(query)
.then(results => {
console.log(results);
context.results = results;
})
.catch(err => console.log(err));
return context;
I still have a problem because after this hook in feathers, the patch continue so it will update my db again.. so i put a disallow() hook.
Also, with this hook i lost the abilities to listening to event
Also i have a concern with the query, i'm not sure if it's better to use :jsonb_insert over ||

Adding a new property to mongo after 'save' in Keystone

I recently found out how to change the value of an existing property and saving it to the mongo database when using Keystone JS (How to alter a value before storing it to the database in Keystone JS).
Now I need to add a new property and save it to the database during the same pre('save') phase.
The aims is to say, if the result (existing property) of a game is 'Won', then add a new property 'won' which is a boolean (true). If it matters, the reason I want this is because in a handlebars template I want to say {{#if won}}class="success"{{/if}}
Game.schema.pre('save', function(next) {
if (this.isModified('result')) {
if (this.result === 'Won') {
this.won = true;
}
}
next()
});
But nothing happens. I read that you can't add properties unless they've been set in the schema. So I tried adding Game.schema.set('won', false); above that, but still nothing.
Is there a simple way to do this?
You could look at Mongoose virtuals which are properties that you can get and set but that do not get persisted to the database:
Game.schema.virtual('won').get(function() {
return this.result === 'Won'
})
http://mongoosejs.com/docs/guide.html#virtuals
If you just want to use it in your template then you could also set a specific property on locals in your view.
Perhaps something like this:
...
exports = module.exports = function(req, res) {
var view = new keystone.View(req, res)
var locals = res.locals
locals.games = []
view.on('init', function(next) {
var query = {} // Add query here
Game.model.find(query).exec(function(err, games) {
// Handle error
games.forEach(function(game) {
game.won = game.result === 'Won'
})
locals.games = games
})
})
}
...

new Model() that has fields set as select:false are not available

This is my Schema:
var userScheme = mongoose.Schema({
aField:String,
info: {
type:{
local: {
email:String
}
},
select:false
}
});
When I try to create a new user this works fine:
var newUser = new User()
newUser.aField="Something"
newUser.save()
But when I try to access the field that has select:false, I can't access the data. so this doesn't work:
var newUser = new User()
newUser.aField="something"
newUser.info.local.email="email#domain.com"
newUser.save()
The error I get is:
TypeError: Cannot read property 'local' of undefined
My guess is that the new Model is returned without the info field becuase it is set to select:false.
How can I make the new Model() return all the fields including those set to 'select:false'?
Thanks!
Turns out the select:false had nothing to do with it.
The culprit was the fact that info has no values by default and there for was not included in the model at all.
The solution was to create a new schema just for info, and to include it in the user schema like this:
var userScheme = mongoose.Schema({
aField:String,
info: {
type:infoSchema,
default:infoSchema, //Without this, the new document will still not include an 'info' document,
select:false
}
});
Hope this helps anyone. This was just 3 hours of my life.

Mongoose -- Force collection name

I am trying to use mongoose to create a database and a collection in it. My code is:
var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://localhost/testdb');
var Schema = mongoose.Schema;
var UserInfo = new Schema({
username : String,
password : String
});
mongoose.model('UserInfo', UserInfo);
var user = db.model('UserInfo');
var admin = new user();
admin.username = "sss";
admin.password = "ee";
admin.save();
When I run this code, mongoose created collection named UserInfo instead of userinfo.
How to force collection name in mongoose?
This should do it
var UserInfo = new Schema({
username : String,
password : String
}, { collection: 'userinfo' });
See this link from the Mongoose documentation for more information.
If you are using mongoose 2.0.0, pass the collectionName as the third argument
mongoose.model('UserInfo', UserInfo, 'UserInfo');
Mongoose will add 's' to collection name by default. If you want to avoid that, just pass as third argument the name of the collection:
var mongoose = require('mongoose');
var db = mongoose.connect('mongodb://localhost/testdb');
var Schema = mongoose.Schema;
var UserInfo = new Schema({
username: String,
password: String
});
mongoose.model('UserInfo', UserInfo, 'UserInfo')
tan = new user();
admin.username = 'sss';
admin.password = 'ee';
admin.save();
API structure of mongoose.model is this:
Mongoose#model(name, [schema], [collection], [skipInit])
What mongoose do is that, When no collection argument is passed, Mongoose produces a collection name by pluralizing the model name. If you don't like this behavior, either pass a collection name or set your schemas collection name option.
Example:
var schema = new Schema({ name: String }, { collection: 'actor' });
or
schema.set('collection', 'actor');
or
var collectionName = 'actor'
var M = mongoose.model('Actor', schema, collectionName);
You need to set the collection name in your schema.
new Schema({...},{collection: 'userInfo'});
Mongoose maintainer here. We recommend doing mongoose.model('UserInfo', UserInfo, 'UserInfo');, third arg to mongoose.model() is the collection name. Here's the relevant docs.
Passing a third argument on module.exports = mongoose.model('name', schema, 'collection') overrides the automatic collection name based on model name, which has already been answered.. but there are 2 other ways,
per mongoose.model doco link:
https://mongoosejs.com/docs/api.html#mongoose_Mongoose-model
there are 3 methods to manually enter a collection name:
var schema = new Schema({ name: String }, { collection: 'actor' });
// or
schema.set('collection', 'actor');
// or
var collectionName = 'actor'
var M = mongoose.model('Actor', schema, collectionName)
Answer:
mongoose.model('UserInfo', UserInfo, 'userinfo'); //3rd parameter 'userinfo': as collection name
Better explanation with syntax:
Mongoose.model(name, [schema], [collection], [skipInit])
Parameters Explanation:
1st parameter - name model name
2nd parameter [schema] schema name
3rd parameter [collection] collection name (optional, induced from model name)
4th parameter [skipInit] whether to skip initialization (defaults to false)
your model name : userInfo.js
in express route file or app.js
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/testdb');
then in your userInfo.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserInfo = new Schema({
username : String,
password : String
});
module.exports = mongoose.model('UserInfo', UserInfo);