Typescript & MongoDB type erasing when adding methods to the schema - mongodb

I've been playing around with typescript and mongodb for the past few days and I wanted to add a custom method which I can execute on Document instances. Here is my setup:
import { Document, Schema, model, Model } from "mongoose";
import { AlbumSchema, AlbumDocument } from './album';
And here is my Document interface:
interface ArtistDocument extends Document {
name: string;
identifier: string;
albums: [AlbumDocument];
testFunction(): string
}
And my Schema:
const ArtistSchema = new Schema({
name: {type: String, required: true},
identifier: {type: String, required: true},
albums: {type: [AlbumSchema], required: true, default: []}
});
ArtistSchema.methods.testFunction = function(): string {
return "Hello World";
}
Note that I can just call testFunction(); on an instance of Artist just fine. So I know that methods are working.
Here is the issue though:
ArtistSchema.methods.testFunction = function(): string {
return "Albums:" + this.albums.length;
}
this.albums (which should be of type AlbumDocument[]) is somehow type any and therefore I can not use any array builtin functions nor can I filter and have AlbumDocument available to use its properties.
Is there anything that I am doing wrong? Is there a fix for it?

Try to instanciate albums like so :
albums:new Array<AlbumDocument>();

Related

Grails with MongoDB - how to define nested and reusable fields

I'm trying to rewrite my node.js + express backend in grails. I'm using a MongoDB database, where it is common to nest fields. In my current backend I have defined a schema (using mongoose), with a field such as:
normalized: {
full_name: {type: String, trim: true, index: true}
},
I have also defined a separate, reusable schema called "locationSchema" that I can use for many of my fields, like so:
// Definition of reusable "sub-schema"
const locationSchema = new mongoose.Schema({
country: {type: String, trim: true},
region: {type: String, trim: true},
city: {type: String, trim: true},
notes: String,
latitude: {type: Number},
longitude: {type: Number}
}, {_id: false});
Which is then used, for example, to define the birth field:
birth: {
date: {type: dateSchema, validate: dateValidator},
location: locationSchema,
notes: String
},
I did not manage to find much information on this, other than that it is possible to define nested fields as Maps, but how would I apply constraints to the sub-fields? And how would I use "sub-schemas" in Grails?
UPDATE WITH SOLUTION
I misunderstood the use of embedded. If you (like me) do not want to create tables/collections for embedded types, simply put the embedded class (like Location in my case) under src/main/... or define it in the same file as the domain class. This is stated in the documentation: http://docs.grails.org/3.1.1/ref/Domain%20Classes/embedded.html
My final, simplified solution is then:
grails-app/.../Person.groovy
class Person implements MongoEntity<Testy> {
ObjectId id
String s
Location location
static mapping = {
collection "person"
}
static embedded = ['location'] // key part that was missing
}
src/main/.../Location.groovy
package hegardt.backend.grails.model.person
import grails.validation.Validateable
class Location implements Validateable {
String country
String region
String city
String notes
Double latitude
Double longitude
static constraints = {
country nullable: true
region nullable: true
city nullable: true
notes nullable: true
latitude nullable: true
longitude nullable: true
}
}
Now I can send requests with JSON data, and the mapping and saving works correctly, as well as validation on my embedded types!
You should be using embedded object for your case.
class User {
Birth birth
static embedded = [ 'birth' ]
}
class Birth {
Date date
Location location
String notes
static constraints = {
date validator:dateValidator
}
}
Then if you do:
User u = new User()
u.birth = new Birth(...)
u.save()
the birth will be saved as a sub-document in User.
If you say:
new Birth(...).save()
then the GORM creates a new collection and fills the record in as usual.

Is possible to have multiple different types of refs for a single attribute in mongoose (mongodb)

I am using mongodb with mongoose. And i am wondering if it is possible to have multiple references for an object id attribute in a schema.
I have tried the code underneath and it did not work.
const Schema = new Schema({
refrens: {
type: ObjectId,
// this did not work
ref: [
"Post",
"Account"
],
required: true
}
});
I know it is possible to remove the ref attribute (field, key) and then all object ids are valid but i want certain object ids to be valid, the object ids of the Post and Account model.
const Schema = new Schema({
refrens: {
// This will allow all different types of object ids to be the value
type: ObjectId,
required: true
}
});
Look like refPath is what you need. You can do something like this:
const Schema = new Schema({
refrens: {
type: ObjectId,
refPath: 'onModel',
required: true
},
onModel: {
type: String,
required: true,
enum: ['Post', 'Account']
}
});

Mongoose Schema planning: using ObjectID reference or using array of type: [otherSchema]?

I'm currently planning out the database structure for an app I'm building, and something in this linked answer raised some questions for me.
In the structure that Shivam proposed, he sometimes references another collection directly, in other words within one Schema he defines the type of a field to be an array of another schema type. Example:
import { Schema } from "mongoose";
import { QuestionSchema } from "./question-schema";
const mongoose = require('mongoose');
export const QuestionSetSchema: Schema = new Schema({
questionSet: {
type: [QuestionSchema],
validate: {
validator: function(value: any) {
return value.length === 12;
},
message: 'Question set must be 12.'
}
},
}, {
timestamps: true
});
In other cases, he only uses an ObjectID reference to another schema/collection:
export const CandidateSchema: Schema = new Schema({
name: String,
email: String, // you can store other candidate related information here.
totalAttempt: {
type: Number,
default: 0,
validate: {
validator: function(value: number) {
return value === 3;
},
message: 'You have already done three attempts.'
}
},
candidateQuestionAnswers: {
type: [Schema.Types.ObjectId],
ref: 'CandidateQuesAnswer'
}
}, {
timestamps: true
});
What are the use cases for each of the above? Does the "type:[otherSchema]" method actually embed instances of that collection or does it only provide their properties to the Schema they are called from?

define authorized values for a Schema's field

Is it possible in Mongoose, to define authorized values for a specific field ?
For example my field "style", would only be allowed to be set to "jazz", "blues" or "rock"
var artistSchema = new mongoose.Schema({
style: {
type: String
// only allow values "jazz", "blues", "rock"
}
});
First of all you are talking about mongoose schema types (String).
You have to know that it is possible to add some options when defining a schema type (options).
According to what you want to do, the option enum seems to be a good choice.
Ps: enum is a built-in validator, what is a validator?
Example from the documentation
var states = 'opening open closing closed'.split(' ')
var s = new Schema({ state: { type: String, enum: states }})
Your case
const CONSTANT_GOOD_VALUES = ['jazz', 'blues', 'rock'];
var artistSchema = new mongoose.Schema({
style: {
type: String
enum: CONSTANT_GOOD_VALUES,
},
});
Try this :-
var artistSchema = new mongoose.Schema({
status: {
type: String,
enum : ['jaz,'blues','rock']
},
});

mongoose dynamically attaching schema to validate against on the fly

I'd like to dynamically attach a schema to a particular field, based on some business logic in schema.pre('save',function(..){...}). How to do this, if at all possible?
Some (simplified) schemas and background:
var fact= new Schema({
name: { type: String, required: true, index: { unique: false }}
,value: {type: {}, required: true}
,moreinfodesc: { type: String, required: false}
,createddate : { type: Date, required: true, default: Date.now, select: false } }
}, { collection: 'Fact' } );
var factSchema= new Schema({
name: { type: String, required: true, index: { unique: true }}
, valueType: { type: {}, required: true}
,isMulti: {type: Boolean, required: true }
//ACL-stuff
,directChangeRoles: {type: [String]} //i.e: [super, admin,owner]
,suggestChangeRoles: {type: [String]} //ie: [editor]
,enum: {type: [{}]}
,mixins: {type: [String]}
}, { collection: 'FactSchema' });
This is a simplified structure to allow "facts' of a particular 'entity' to be edited.
e.g: entityA.facts=[fact]
As can be seen from the schema's fact.value can have any type as far as mongoose is concerned. I however, want to constrain it at runtime to the schema as defined in
FactSchema.valueType (a String containing "Boolean", "String" or something more complex as "[Tag]") . This might all seem cumbersome, but this is the way i'd like to go for several reasons.
So let's say that for a particular fact with fact.name=tags I want to assign fact.value the type [Tag] at runtime. For this I would have setup a Tag-schema with validation like usual and have fact.value validate against it.
I was thinking of somehow "attaching" the [Tag]-schema to fact.value in fact.pre('save',function(..){.. //validation here }) and hope validation would magically happen as if fact.valuewas assigned the type [Tag] at design-time instead of run-time.
Finally the question: I've got no idea if it's possible to do that 'attach' and if so, how?
Thanks.
"attaching" at run time isn't possible but you can add a custom validator to your path and base its logic on current document state:
https://gist.github.com/2789681
Try using mongoose discriminators
And if needed, you can alter the validation at runtime with:
YourModelName.schema.path('your_field_name').required(false);