I have these collections (books,book_genres,genres,books)
my book Schema is like that
var bookModel = function () {
var bookSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
author_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Author",
},
title: String,
description: String,
cover: String,
likes: Number,
});
return mongoose.model("Book", bookSchema);
};
module.exports = new bookModel();
I'm using dust templating and I'm rendering that collection data on my layout like that
{#books}
<div class="large-4 small-12 columns book">
<div class="row img-row">
<img src="{.cover}" alt="">
</div>
<div class="row">
<h3 class="small-12">{.title}</h3>
<h4 class="small-5 columns ">{.author_id}</h4>
<h4 class="small-7 columns ">{.name}</h4>
</div>
<div class="row p-row">
<p class="small-12">{.description}</p>
</div>
</div>
{/books}
my author Schema is like that
var authorModel = function () {
var authorSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: String,
});
return mongoose.model("Author", authorSchema);
};
I want to be able to reach to author name so I can render it on my layout through the author_id that I'm getting from the book Schema (for sure the id in the authors collection is the same as the author_id in the books collection)
I tried to search for some solutions but no one was using dust templating so I wasn't able to figure that out
You can use populate to resolve the Author reference:
bookModel.find({}).populate('author_id').exec();
You should then be able to access the referenced user fields with:
<h4 class="small-7 columns ">{.author_id.name}</h4>
In your case, you should change your code to:
module.exports = function (router) {
router.get('/', function (req, res) {
Book.find({})
.populate('author_id')
.exec(function (err, books) {
if (err) {
console.log(err);
}
var model = { books: books };
res.render('index', model);
});
});
};
Related
I have just started NodeJS, so I'm on a beginner level. I am trying to build a shopping list app with MongoDB, in which user logs in, creates a list and then adds items to that list. I can register & log in and create a list, but when I try to add items to it, that's when I run into this:
"TypeError: Cannot read property 'products' of undefined".
Code for it is:
const user = req.user;
const shoppinglist_id = req.body.shoppinglist_id;
const name = req.body.name;
let new_product = product_model({
title: req.body.title,
imagePath: req.body.imagePath,
quantity: req.body.quantity
});
new_product.save().then(() => {
shoppinglist_id.products.push(new_product);
console.log('product saved');
shoppinglist_id.save().then(() => {
return res.redirect('/');
});
}); ```
User model:
const user_schema = new Schema({
name: {
type: String,
required: true
},
shoppinglists: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'shoppinglist',
req: true
}]
});
Shoppinglist model:
const shopping_list_schema = new Schema({
name: {
type: String,
required: true
},
products: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'product',
req: true
}]
});
Product model:
var schema = new Schema({
imagePath: {type: String, required: true},
title: {type: String, required: true},
quantity: {type: Number, required: true}
});
Any idea what I'm doing wrong? I know the problem is with this line:
"shoppinglist_id.products.push(new_product);", I have tried everything on it, "user.shoppinglists.products" etc. Nothing works. Any help?
EDIT:
I'll also post as a reference my add shoppinglist, which is working fine.
const user = req.user;
let new_shoppinglist = shopping_list_model({
name: req.body.name,
products: req.body.products
});
new_shoppinglist.save().then(() => {
console.log('shoppinglist saved');
user.shoppinglists.push(new_shoppinglist);
user.save().then(() => {
return res.redirect('/');
});
});
}```
EDIT #2:
I am adding how I am getting to shoppinglist-view:
´´´
const get_shoppinglist = (req, res, next) => {
const shoppinglist_id = req.params.id;
shopping_list_model.findOne({
_id: shoppinglist_id
}).then((shoppinglist) => {
shoppinglist.populate('products')
.execPopulate()
.then((shoppinglist) => {
let data = {
shoppinglist: shoppinglist
};
console.log(data);
let html = shoppinglist_views.shoppinglist_view(data);
res.send(html);
});
});
};´´´
And shoppinglist-view:
´´´
const shoppinglist_view = ((data) => {
let html = `
<html>
<body>
<h1></h1>
<h2>${data.shoppinglist.name}</h2>
<h4>Go back</h4>
<br>
`
data.shoppinglist.products.forEach((product) => {
html += products
html += `
<div>
<p>
<h2>Name of the shopping list: ${shoppinglists.shoppinglist.name}</h2>
<h3> Name: ${product.title}<br></h3>
<img src="${product.imagePath}" width="50px" height="50px" />
quantity: ${product.quantity} </p>
</div>
</body>
</html>
`;
});
html += `
<form action="/add-product" method="POST">
<p>Add products</p><br>
Title?<br>
<input type="text" name="title"><br>
Image-link<br>
<input type="img" name="imagePath"><br>
Quantity?<br>
<input type="number" name="quantity"><br><br>
<button type="submit">Add to list</button>
</form>
</html>
</body>`;
return html;
´´´
This is because shoppinglist_id is undefined. At the moment, you derive the value like this:
const shoppinglist_id = req.body.shoppinglist_id;
In other words, req.body has no property called shoppinglist_id. If you print the value of req.body, you will most likely see that it is a string. If you are attempting to send/receive JSON data (which I'm guessing you are) you must first parse the request body:
const data = JSON.parse(req.body);
const shoppinglist_id = data.shoppinglist_id;
Or even better:
const data = JSON.parse(req.body);
const { shoppinglist_id } = data;
Please note that you should always parse untrusted JSON inside a try ... catch block, but that's another lesson for another day.
I am attempting to post comments to a blog post and have these comments as an array for each post.
I have managed to get the comments to post, but I cannot call the information from the post. The comment model has text and author in it but the comment only takes the author part of the model. When the comments show up on the post, they only show the name of the user that has written the comment. I have checked the database and comment.text isn't getting passed through at all.
Comments Create Route
router.post("/", ensureAuthenticated, (req, res) => {
Blog.findById(req.params.id, (err, blog) => {
if(err){
console.log(err);
res.redirect("/blog");
} else {
Comment.create(req.body.comment, (err, comment) => {
if(err){
req.flash("error", "Something went wrong");
console.log(err);
} else {
//add username and id to comment
comment.author.id = req.user._id;
comment.author.name = req.user.name;
comment.author.email = req.user.email;
comment.save();
//save comment
blog.comments.push(comment);
blog.save();
req.flash("success", "Successfully added comment");
res.redirect("/blog/" + blog._id);
}
});
}
});
});
Comment Schema
const mongoose = require("mongoose");
var commentSchema = new mongoose.Schema({
text: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
name: String,
email: String
}
});
module.exports = mongoose.model("Comment", commentSchema);
Part of the Show Page Which Shows The Comment
<div class="card-body">
<% blog.comments.forEach(function(comment){ %>
<div class="row">
<div class="col-md-12">
<strong><%= comment.author.name %></strong>
<span class="float-right">10 days ago</span>
<p class="card-text">
<%= comment.text %>
</p>
<% if(user && comment.author.id.equals(user._id)) { %>
<a class="btn btn-warning btn-sm d-inline-block" href="/blog/<%= blog._id %>/comments/<%= comment._id%>/edit">Edit</a>
<form class="d-inline-block" action="/blog/<%= blog._id %>/comments/<%= comment._id%>?_method=DELETE" method="POST">
<button class="btn btn-danger btn-sm">Delete</button>
</form>
<% } %>
<hr>
</div>
</div>
<% }) %>
</div>
I am expecting the show page to show the text of each comment.
I was missing bodyParser. In case anyone else has this problem, https://stackoverflow.com/a/55793723/8145657 fixed it for me.
i have this problem sorting my object of data in my index of my blog app.
I have a blog app based on Express using ejs and mongoDB using mongoose.
What i want is sorting the results so the newest post starts at the top. At this moment it will show the first post at the top.
app.js / mongoose schema
blogSchema = new mongoose.Schema({
title: String,
image: String,
body: String,
created: {type: Date, default: Date.now}
});
var Blog = mongoose.model("Blog", blogSchema);
app.js / Index route
app.get("/blogs", (req, res)=>{
Blog.find({}, (err, blogs)=>{
if(err){
console.log("Error!");
console.log(err);
} else {
res.render("index", {blogs: blogs});
}
});
});
index.ejs foreach
<% blogs.forEach(function(blog){ %>
<img alt="img" src="<%= blog.image %>">
<%= blog.title %>
<span><%= blog.created.toDateString() %></span>
<p><%- blog.body.substring(0, 200) %>...</p>
Read More
<% }) %>
Does anyone have a clue how i can do this?
You can use the sort() method of Mongoose:
Blog.find((err, blogs) => {
if (err) {
console.log(err);
} else {
res.render("index", { blogs: blogs });
}
}).sort({ created: 'desc' });
When I add a comment to any blog post it doesn't work. This is a normal Node.js MVC controller which is linked to route:
commentBlog(req, res) {
const comment = {
comment: req.body.comment,
author: req.params.id
}
Comment.create(comment, (error, comment) => {
if (error) {
console.log(error);
} else {
Blog.findById(req.params.id, (error, blog) => {
blog.comments.push(comment);
console.log(comment);
blog.save((error, savedBlog) => {
if (error) {
console.log(error);
} else{
res.redirect('/blogs/' + blog._id);
}
})
})
}
})
};
( this is the model )
--------------------
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
var commentSchema = new Schema({
author: { type: Schema.Types.ObjectId, ref: 'user' },
comment: {type: String},
created: {type: Date, default: Date.now},
blog: { type: Schema.Types.ObjectId, ref: 'blog' }
});
var Comment = mongoose.model('comment', commentSchema);
module.exports = Comment;
this is the ejs file
---------------------
<body>
<% if(blog) { %>
<form action="/blogs/<%= blog._id %>/comment" method="POST">
<textarea name="comment[text]"
rows="10" cols="50">Write something here
</textarea>
<input type="submit" value="Post comment">
</form>
<% } %>
**i don't know why it doesn't display when i add like to any post it just
don't work**
Well, you were not displaying the blog content in your ejs file. Only rendering the form. You should render the blog fields for them to display. Below is an example to show the blog title( provided there is a title field in the blog object).
<body>
<% if(blog) { %>
<h2><%= blog.title %> </h2>
<form action="/blogs/<%= blog._id %>/comment" method="POST">
<textarea name="comment[text]"
rows="10" cols="50">Write something here
</textarea>
<input type="submit" value="Post comment">
</form>
<% } %>
You can then display the other fields in your blog object using the example above.
I am trying to loop my database collections based on a parameter request for one of my schema properties within a view. I have a tags property within my schema that is saved as an array. What I want to be able to do is click on one of the values saved to that array, which will render a page with collections that contain the selected tags value. I have been able to create the route, which will direct me to the individual tag that was clicked, but I receive a tags: [xxx, xxx, xxx] has no method .forEach.that occurs when I call my loop within my view. Why would this be and how should I solve this?
Error message:
TypeError: /Users/user/Desktop/Projects/node/blog/views/pages/tag.ejs:15
13| <div class="col-md-12">
14| <h1><%= blogpost.tags %></h1>
>> 15| <% blogpost.forEach(function(blogpost) { %>
16| <%= blogpost.title %>
17| <% }); %>
18| </div>
Object { _id: 54c7bd20c58f389232000001,
category: 'Analytics/SEO/SEM',
content: '',
tagline: 'yep',
author: 'Author',
blogUrl: 'roger',
featureImage: '/images/event-placeholder.png',
title: 'Roger',
__v: 0,
date: Tue Jan 27 2015 11:29:48 GMT-0500 (EST),
tags: [ 'wolf', ' cow', ' monkey' ] } has no method 'forEach'
Here is my model:
var mongoose = require('mongoose');
var mongoosePaginate = require('mongoose-paginate');
var Schema = mongoose.Schema;
var BlogPostSchema = new Schema({
title: { type: String, unique: true },
featureImage: String,
blogUrl: String,
author: String,
tagline: String,
category: String,
content: String,
tags: { type: Array, lowercase: true },
date: { type: Date, default: Date.now() }
});
BlogPostSchema.post('init', function (post) {
var date = new Date(post.date || Date.now() );
post.dateString = date.getMonth() + 1 + '/' + date.getDate() + '/' + date.getFullYear();
});
BlogPostSchema.plugin( mongoosePaginate );
var Blogpost = mongoose.model("Blogpost", BlogPostSchema);
module.exports = mongoose.model('Blogpost', BlogPostSchema);
Here is how I'm calling my individually selected tag within my route (Does my function use the correct parameter call method?):
router.route('/admin/posts/create')
// START POST method
.post(function(req, res) {
console.log("New instance");
console.log(req.body.tags);
var blogpost = new Blogpost(); // create a new instance of a Blogpost model
blogpost.title = req.body.title; // set the blog title
blogpost.featureImage = req.body.featureImage; // set the blog image
blogpost.blogUrl = blogpost.title.toLowerCase().replace(/\s+/g,"-");
blogpost.author = req.body.author; // set the author name
blogpost.tagline = req.body.tagline; // set the tagline
blogpost.content = req.body.content; // set the blog content
blogpost.category = req.body.category; // set the category
blogpost.tags = req.body.tags.trim().split(","); // set the tags
//Save Blog Post
blogpost.save(function(err) {
if (err)
res.send(err);
res.redirect(303, '/'); //NEEDS TO BE CHANGED
});
}) // END POST method
.get(isLoggedIn, function(req, res, blogpost) {
res.render('pages/blogpost-create', {
blogpost : blogpost
});
});
function getTagCriteria(params){
return {
tags: params.blogpost_tags
};
}
router.route('/tag/:blogpost_tags')
.get(function (req, res) {
var tagCriteria = getTagCriteria(req.params);
Blogpost.findOne(tagCriteria, function (err, blogpost) {
if (err)
res.sent(err);
res.render('pages/tag', {
blogpost : blogpost
})
})
});
pages/tag view file:
<div class="container">
<div class="col-md-12">
<h1><%= blogpost.tags %></h1>
<% blogpost.forEach(function(blogpost) { %>
<%= blogpost.title %>
<% }); %>
</div>
</div>
Change your mongo query to:
Blogpost.find(tagCriteria, function (err, blogpost) {
if (err)
res.sent(err);
res.render('pages/tag', {
blogpost : blogpost
})
})
NOTE: blogpost will now be an array [] instead of an object {}
Then in your jade, do the following
<% blogpost.forEach(function(post) { %>
<%= post.title %>
<% }); %>