Create function to init model schema - javascript

Recently, at an interview, I was given a task that I could not solve. I would be very happy if someone helps to figure out how to solve it!
"Create a function to init model schema. Add method to define relation one-to-one, one-to-many. Model fields should have types. Raise an error when type is not corresponding."
const Card = Model({
number: Number,
pin: String
});
const Post = Model({
title: String,
description: String
});
const User = Model({
name: String,
age: Number,
posts: Model.hasMany(Post),
card: Model.hasOne(Card),
});
User.prototype.hasCard = function() {
return !!this.card;
};
const user = new User({
name: 'User',
age: 25,
posts: [{
title: 'Post1',
description: 'Post description'
}],
card: null
});
console.log(user.hasCard()); //false

Related

Default values not adding to mongoose Model

So this is an example of my schema I have for a user.
id: String,
email: String,
slug: {
type: Object,
phrase: {type: String, default: null},
},
When I want to define a new user and save that user, I would do the following;
const newUser = new User({
id: 123,
username: "CoolUser",
email: "BillGates#google.com"
});
newUser.save();
But this does not save the "slug" object, It was my understanding, that since I a default value for it, it would auto populate with that default value. What can I do to make it auto generate without having to define the whole schema again when saving a user?
You should add default for slug property, and not for his sub-property. Try changing your schema like this:
slug: {
type: {
phrase: { type: String },
},
default: {
phrase: null
},
},
try this:
const subschema = new Schema({
phrase: {type: String, default: null},
}, { _id: false });
and in your original schema:
id: String,
email: String,
slug: {
type: subschema,
default: () => ({})
},
this should do the trick.
const User = new Schema({
id: String,
about: {
bio: String,
location: String,
website: String,
discord: String,
twitter: String,
default: {
bio: "This is a default bio.",
location: "",
website: "",
discord: "",
twitter: "",
}
}
})
Ok here is the example, I removed some stuff from it just for ease, Basically as you can see I want bio to default to: This is a default bio.
But instead, I get the following error:
throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` +
^
TypeError: Invalid schema configuration: `This is a default bio.` is not a valid type at path `about.default.bio`. See bit ly / mongoose-schematypes for a list of valid schema types.

Mongoose pushing only ObjectID, not the whole document

I have a recipe blog site, where every single recipe have comments. Every comments saving into the comments collection, but when i push the comments to the right recipe, than only saving the ObjectID. Here is my code:
Recipe model
const { Double } = require('bson');
const mongoose = require('mongoose');
const recipeSchema = new mongoose.Schema({
name: {
type: String,
required: 'This field is required.'
},
description: {
type: String,
required: 'This field is required.'
},
quantity: {
type: Array,
required: 'This field is required.'
},
ingredients: {
type: Array,
required: 'This field is required.'
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
],
recipe_id: {
type: String,
}
});
recipeSchema.index({ name: 'text', description: 'text' });
const Recipe = module.exports = mongoose.model('Recipe', recipeSchema);
Comment Model
const commentSchema = new mongoose.Schema({
username: {
type: String,
required: 'This field is required.'
},
comment: {
type: String,
required: 'This field is required.'
},
recipe_id: {
type: String
}
});
Routes
router.get('/', recipeController.homepage);
router.get('/recipe/:id', recipeController.exploreRecipe );
router.get('/categories/:id', recipeController.exploreCategoriesById);
router.get('/categories', recipeController.exploreCategories);
router.get('/submit-recipe', recipeController.submitRecipe);
router.post('/submit-recipe', recipeController.submitRecipeOnPost);
router.post('/recipe/:id/comments', recipeController.CommentRecipeOnPost);
Controller
module.exports.CommentRecipeOnPost = async(req, res) => {
const comment = new Comment({
username: req.body.username,
comment: req.body.comment
});
comment.save((err, result) => {
if (err){
console.log(err)
}else {
Recipe.findById(req.params.id, (err, post) =>{
if(err){
console.log(err);
}else{
post.comments.push(result);
post.save();
console.log('====comments=====')
console.log(post.comments);
res.redirect('/');
}
})
}
})
}
I tried with populate and another methods, but no one worked, after a lots of hour programming i done with these, that i can save only the ObjectId-s.
This's because you define comments at recipe schema as an ObjectId and that's right, if you want to get full comment parameters you will populate the record
const recipe = await Recipe.findOne({ _id: "recordId"}).populate("posts")
this will return all comments details
{ name: "Fitness recipe", description: "Fitness recipe to loss 10kg in 10 days only", comments: [ {_id: "recordId", username: "Smith June", comment: "That's awesome"}]}
if you need to save the whole document as an array of objects, not just the id so you can make schema like that
const recipeSchema = new mongoose.Schema({
name: {
type: String,
required: 'This field is required.'
},
description: {
type: String,
required: 'This field is required.'
},
quantity: {
type: Array,
required: 'This field is required.'
},
ingredients: {
type: Array,
required: 'This field is required.'
},
comments: [
{ username: String, comment: String }
],
recipe_id: {
type: String,
}
})
and in this case, you won't have any relations, you can list recipe comments easily.

Mongoose: automatically embed field upon create?

Say I have one model, Book, and another model, Genre. When I create the book, I'd like to be able to pass a Genre ID and have the model automatically fetch and embed the document. For example:
const bookSchema = new Schema({
title: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
genre: {
type: ObjectId,
required: true,
}
});
const genreSchema = new Schema({
name: {
type: String,
required: true,
},
});
Then I'd like to be create a book as follows:
const Book = await Book.create({
title: 'Lord of the Rings',
author: 'J. R. R. Tolkien',
genre: '5d6ede6a0ba62570afcedd3a',
});
That would create a book and automatically embed the genre document from the given ID. Is there a way to do that from within the schema, or would I have to wrap it in additional logic?
You can use the pre-save mongoose middleware/hook to find the genre and set it as an embedded document. In mongoose pre-save hook, this will be the current document, you can read the value and set the value to this object before it is written to the database.
Note that, since this is a pre-save hook, it will be run only on Model.create() or document.save(). So it won't be run on Model.insertMany(). But it will be run when you update the document using document.save(). If you want to set the genre only on new documents, you will have to check for this.isNew property
const { Schema, Types } = mongoose
const genreSchema = new Schema({
name: {
type: String,
required: true,
},
});
const Genre = mongoose.model('Genre', genreSchema)
const bookSchema = new Schema({
title: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
genreId: {
type: Schema.Types.ObjectId,
required: true,
},
genre: {
type: genreSchema,
},
});
bookSchema.pre('save', async function() {
if (this.isNew) { // this.isNew will be true on only new documents
this.genre = await Genre.findById(this.genreId) // `this` is the new book document
}
})
const Book = mongoose.model('Book', bookSchema)
/* Test book creation */
const genre = await Genre.create({
name: 'Fantasy'
})
const book = await Book.create({
title: 'Lord of the Rings',
author: 'J. R. R. Tolkien',
genreId: genre._id,
});
console.log(book)
you can use mixed schema type and document middleware to solve your problem.see my sample code below:
const genreSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
});
const Genre = mongoose.model('Genre', genreSchema);
const bookSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
genre: {
type: Object,
required: true,
}
});
bookSchema.pre('save', async function () {
const genreID = mongoose.Types.ObjectId(this.genre);
this.genre = await Genre.findById(genreID);
});
const Book = mongoose.model('Book', bookSchema);
const newBook = new Book({ title: 'The book', author: 'xyz', genre: '5ef55c67be27fb2a08a1131c' });
newBook.save();
How do you know which genre ID to embed? Can you send this from your frontend?
If yes, then simply select the genre ID from you frontend and then pass it in your API's request body.
While in your backend:
router.route('/book')
.post((req, res) => {
Book.create({
title: req.body.title,
author: req.body.author,
genre: req.body.genre,
}, (err, product) => {
if (err) {
res.send(err);
} else {
res.json({success:true})
}
});
})
Do something like this to create a new book object in your Book collection.
If I understand your question correctly I think what you're looking for is populate. https://mongoosejs.com/docs/populate.html
It would change your schema to look like the following
const bookSchema = new Schema({
title: {
type: String,
required: true,
},
author: {
type: String,
required: true,
},
genre: {
type: Schema.Types.ObjectId,
ref: 'Genre',
required: true,
}
});
const genreSchema = new Schema({
name: {
type: String,
required: true,
},
});
When you get your book you can reference the genre by doing this
Book.find()
.populate('genre')
Hopefully, that answered your question!

Mongoose - save object with objectid

Here I have two schema:
var personSchema = Schema({
_id: Schema.Types.ObjectId,
name: String,
job: {
type: Schema.Types.ObjectId,
ref: 'Job',
}
});
var jobSchema = Schema({
_id: Schema.Types.ObjectId,
title: String,
});
var Job = mongoose.model('Job', jobSchema);
var Person = mongoose.model('Person', personSchema);
Suppose Job has some records:
[{
"_id" : ObjectId("5b46d41e04cfc922949dcfda"),
"Title": "Teacher"
}, ...]
When I have some person objects to insert:
[{
name: 'Peter',
job: 'Teacher'
}, ...]
Do I need to find the Job's _id and convert the job field to ObjectId type before each save? e.g.
Job.findOne({title: p.job}, (j) => {
Person.save({name: p.name, job: j._id}).exec(()=>{
// it's ok!
)}
})
Or I can use the middleware or populate function to make it easy? Thankyou!
While saving your person , you are needing a job for it.
So this is how you can proceed for the same:
Either create a new job / find an existing job.
Assign the found job's objects _id field to your new Person and save the same.
Eg.code
let person = new Person({
name : 'TszHin'
});
Job.findOne({ title : 'Abc'})
.then(function(job){
person.job = job._id;
person.save();
});

How to create field using variable name?

How can I create a field in mongoose using a variable name? I've seen some ways to do it using .update(), but I was wondering if there was a way to do it when creating a new document
I have my schema like:
var summariesSchema = mongoose.Schema({
type: String,
name: String,
date: String
})
and my object:
var date = '2015-02-01'
var obj = {
ios: 100,
android: 500
}
var doc = {}
doc[date] = obj
var mongoDoc = new Summaries({
name: 'John',
type: 'person',
date: date,
$doc: doc
})
mongoDoc.save(function(err){
if(!err) log('done')
else log(err.toString())
})
But it only saves the fields 'name', 'type' and 'date'.
Can anyone tell me if its possible to do something like that and if so, what am I missing?
Got it..
first part from #Alex is right, just change the schema strict property to false so I can add new fields to my mongo document. For the field name as a variable, I just first created my entire object and then create a new instance of the document:
var summariesSchema = mongoose.Schema({
type: String,
name: String,
date: String
}, { strict: false })
var date = '2015-02-01'
var obj = {
name: 'John',
type: 'person',
date: date
}
obj[date] = {
ios: 100,
android: 500
}
var mongoDoc = new Summaries(obj)
mongoDoc.save(function(err){
if(!err) log('Done.')
else log(err)
}
Success!
EDIT
Three years later ES6 syntax also allows us to do so:
var mongoDoc = await new Summaries({
name: 'John',
type: 'person',
date: date,
[date]: { ios: 100, android: 100 }
}).save();
Change your schema definition to
var summariesSchema = mongoose.Schema({
type: String,
name: String,
date: String
}, { strict: false })

Categories