I have been trying to follow the documentation to make a relationship between movies and categories (also among others that I will mention below, but starting with categories as an example).
Well, below I have the code of the parts of my code and the error response.
models/movie.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const {ObjectId} = mongoose.Schema;
const movieSchema = new Schema(
{
title: {
type: String,
required: true,
},
year: {
type: Number,
required: true,
},
duration: {
type: String,
required: true,
},
rating: {
type: String,
required: true,
},
score: {
type: String,
required: true,
},
category: {
type: ObjectId,
ref: "Category"
},
description: {
type: String,
required: true,
},
director: [{
type: ObjectId,
ref: "Director"
}],
actor: [{
type: ObjectId,
ref: "Actor"
}],
studio: {
type: ObjectId,
ref: "Studio"
},
poster: {
type: String,
required: true,
},
trailer: {
type: String,
required: true,
},
},
{
timestamps: true,
}
);
module.exports = mongoose.model("Movie", movieSchema);
models/category.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const {ObjectId} = Schema;
const categorySchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
movies: [
{
type: ObjectId,
ref: "Movie",
}
]
},
{
timestamps: true,
}
);
module.exports = mongoose.model("Category", categorySchema);
controllers/movie.js
const Movie = require('../models/movie');
const Category = require('../models/category');
const Actor = require('../models/actor');
const Director = require('../models/director');
const Studio = require('../models/studio');
const create = async (req, res) => {
const content = req.body;
const category = await Category.findById(content._id);
const actor = await Actor.findById(content._id);
const director = await Director.findById(content._id);
const studio = await Studio.findById(content._id);
const newMovie = new Movie({
...content,
category,
actor,
director,
studio
});
const savedMovie = await newMovie.save();
category.movies = [...category.movies, savedMovie._id];
await category.save();
actor.movies = [...actor.movies, savedMovie._id];
await actor.save();
director.movies = [...director.movies, savedMovie._id];
await director.save();
studio.movies = [...studio.movies, savedMovie._id];
await studio.save();
res.status(201).json({
message: 'Movie created successfully',
movie: savedMovie
});
};
Now my post request
POST http://localhost:3000/api/v1/movies HTTP/1.1
Content-Type: application/json
{
"title": "The Matrixxx",
"year": 1999,
"duration": "136 min",
"rating": "R",
"score": "8.7",
"category": "6265ba915a8064456ac6231b",
"description": "A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers.",
"director": ["626502e956cd00fe36692bf9"],
"actor": ["626501fc56cd00fe36692bf2"],
"studio": "626502ac56cd00fe36692bf7",
"poster": "https://images-na.ssl-images-amazon.com/images/M/MV5BNzQzOTk3OTAtNDQ0Zi00ZTVkLWI0MTEtMDllZjNkYzNjNTc4L2ltYWdlXkEyXkFqcGdeQXVyNjU0OTQ0OTY#._V1_SX300.jpg",
"trailer": "https://www.youtube.com/embed/m8e-FF8MsqU"
}
Response
TypeError: Cannot read property 'movies' of null
at create (C:\Users\default\Desktop\streaming-backend\src\controllers\movie.js:26:36)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
Thanks for the answers :3
I think you forgot to use the necessary search fields. In theory, this code should look like this.
const category = await Category.findById(content.category);
const actor = await Actor.findById(content.actor[0]);
const director = await Director.findById(content.director[0]);
const studio = await Studio.findById(content.studio);
Related
I'm doing a test on a project using Jest.
This is the code for testing:
describe("POST /", () => {
let token;
let title;
let genre;
let dailyRentalRate;
const exec = async () => {
genre = await new Genre({
name: "abcde",
}).save();
return await request(server)
.post(config.get("URI") + "/movies")
.set("x-auth-token", token)
.send({ title, genre, numberInStock: 1, dailyRentalRate });
};
beforeEach(() => {
token = new User().generateAuthToken();
title = "abcdefg";
dailyRentalRate = 4.2;
});
it("should save the movie if it is valid", async () => {
const res = await exec();
console.log(genre);
console.log(res.body.error.details);
// const movie = await Movie.find({ title });
// expect(res.status).toBe(StatusCodes.OK);
// expect(movie).not.toBeNull();
});
});
The models are following:
Genre:
const mongoose = require("mongoose");
const genreSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minLength: 5,
maxLenght: 50,
},
});
const Genre = mongoose.model("Genre", genreSchema);
module.exports = { Genre, genreSchema };
Movie:
const mongoose = require("mongoose");
const { genreSchema } = require("./genre");
const Movie = mongoose.model(
"Movie",
new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
minlength: 2,
maxlength: 255,
},
genre: {
type: genreSchema,
required: true,
},
numberInStock: {
type: Number,
min: 0,
max: 255,
required: true,
},
dailyRentalRate: {
type: Number,
},
})
);
module.exports = { Movie };
When I test this suite, I receive the following error:
Why am I receiving this error and how to solve it?
I am building ecommerce website using MERN stack. And I am getting error while testing using Postman.
backend/controllers/user.js
const User = require("../models/user");
const Order = require("../models/order");
exports.userPurchaseList = (req, res) => {
Order.find({ user: req.profile._id })
.populate("user", "_id name")
.exec((err, order) => {
if (err) {
return res.status(400).json({
error: "No Order in this account",
});
}
return res.json(order);
});
};
backend/models/Order.js
const mongoose = require("mongoose");
const { ObjectId } = mongoose.Schema;
const ProductCartSchema = new mongoose.Schema({
product: {
type: ObjectId,
ref: "Product",
},
name: String,
count: Number,
price: Number,
});
const ProductCart = mongoose.model("ProductCart", ProductCartSchema);
const OrderSchema = new mongoose.Schema(
{
products: [ProductCartSchema],
transaction_id: {},
amount: { type: Number },
address: String,
status: {
type: String,
default: "Recieved",
enum: ["Cancelled", "Delivered", "Shipped", "Processing", "Recieved"],
},
updated: Date,
user: {
type: ObjectId,
ref: "User",
},
},
{ timestamps: true }
);
const Order = mongoose.model("Order", OrderSchema);
module.exports = { Order, ProductCart };
backend/models/User.js
const mongoose = require("mongoose");
const crypto = require("crypto");
const uuidv1 = require("uuid/v1");
var userSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
maxlength: 32,
trim: true,
},
lastname: {
type: String,
maxlength: 32,
trim: true,
// required: false,
},
email: {
type: String,
required: true,
trim: true,
unique: true,
},
userinfo: {
type: String,
trim: true,
},
encry_password: {
type: String,
required: true,
},
salt: String,
role: {
type: Number,
default: 0,
},
purchases: {
type: Array,
default: [],
},
},
{ timestamps: true }
);
module.exports = mongoose.model("User", userSchema);
backend/routes/user.js
router.get(
"/orders/user/:userId",
isSignedIn,
isAuthenticated,
userPurchaseList
);
Error:-
TypeError: Order.find is not a function
at exports.userPurchaseList (C:\Users\Rahul\MernBootcamp\projbackend\controllers\user.js:47:9)
TESTING this route using POSTMAN.
You have exported an object so in your backend/controllers/user.js
you could import it like so from destructuring from the object then the rest of your code would be okay
const {Order} = require("../models/order");
or
accessing it using the dot notation
when calling the find Function
//importing it at the top
const Order = require("../models/order");
exports.userPurchaseList = (req, res) => {
Order.Order.find({ user: req.profile._id })
.populate("user", "_id name")
.exec((err, order) => {
if (err) {
return res.status(400).json({
error: "No Order in this account",
});
}
return res.json(order);
});
};
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!
This question already has answers here:
Can't get Mongoose virtuals to be part of the result object
(3 answers)
Closed 6 years ago.
This is my post model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema({
text: String,
image: String,
author: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
university: {
type: String,
required: true
},
uniOnly: {
type: Boolean,
required: true
},
createdAt: {
type: Number,
required: true
},
expiresAt: {
type: Number,
required: true
},
voteCount: {
type: Number,
required: true
},
comments: [{
type: Schema.Types.ObjectId,
ref: 'comment'
}],
commenters: [{
type: Schema.Types.ObjectId,
ref: 'user'
}],
categories: [{
type: String
}]
});
PostSchema.virtual('commentCount').get(function () {
return this.comments.length;
});
PostSchema.virtual('expired').get(function () {
const now = new Date().getTime();
return now <= this.expiresAt;
});
const Post = mongoose.model('post', PostSchema);
module.exports = Post;
As you can see, I'm attempting to create a couple of virtual types:
commentCount will give the length of the array of comments.
expired will either flag as true or false
Here is the controller:
create(req, res, next) {
const postProps = req.body;
const now = new Date().getTime();
const expiresAt = now + 43200000;
const { author, text, image, categories, university, uniOnly } = postProps;
Post.create({
author,
text,
image,
categories,
university,
uniOnly,
createdAt: now,
voteCount: 0,
expiresAt,
})
.then(post => res.send(post))
.catch(next);
},
Right now, using Postman I can see that the post itself is being created just fine. For some reason though, I'm not getting back anything for the virtual types, neither commentCount nor expired.
This is what I get back in response:
}
"__v": 0,
"author": "5896623ff821b14c4470cf97",
"text": "this is ANOTHER post",
"university": "University of Birmingham",
"uniOnly": false,
"createdAt": 1486306414679,
"voteCount": 0,
"expiresAt": 1486349614679,
"_id": "58973c6ef24ca4828c2adae1",
"categories": [
"music",
"dance"
],
"commenters": [],
"comments": []
}
Can you please tell me what I'm doing wrong here? I've followed along several along courses in which I've done similar before and I'm scouring through the course code. I can't work it out though.
Thank you
If you want to get virtuals serialized to json object, try doc.toObject({ virtuals: true }) as documented here http://mongoosejs.com/docs/api.html#document_Document-toObject.
I have a users model which includes a locationsSchema in it:
const locationSchema = require('./location.js');
const userSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true,
},
token: {
type: String,
require: true,
},
locations: [locationSchema],
passwordDigest: String,
}, {
timestamps: true,
});
My locations model is :
const mongoose = require('mongoose');
const locationSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
city: {
type: String,
},
region: {
type: String,
},
country: {
type: String,
},
coords: {
lat: {
type: Number,
require: true
},
long: {
type: Number,
require: true
},
},
visited: {
type: Boolean,
require: true,
default: false,
},
comment: {
type: String,
},
}, {
timestamps: true,
});
and finally the create action in my controller for locations is:
const controller = require('lib/wiring/controller');
const models = require('app/models');
const User = models.user;
const Location = models.location;
const create = (req, res, next) => {
User.findById(req.body.user.id).then (function(user){
let location = Object.assign(req.body.location);
user.locations.push(location);
return user.save();
})
.then(user => res.json({ user }))
.catch(err => next(err));
};
When I try to send a curl POST request to locations I get:
{"error":{"message":"this._schema.caster.cast is not a function","error":{}}}
console.logging user, user.locations, and locations just before the
user.locations.push(location);
line returns exactly what I'd expect. I'm fairly certain the error is stemming from the push function call. Any insight would be much appreciated.
your embedding location model
const locationSchema = require('./location.js');
so only you getting this error,
model can't be embedding schema only embedding
const locationSchema = require('./location.js'). schema;
so you try this