Can't render my object from db - "object not iterable" - javascript

I'm trying to show "events" created by an "artist" on the artist's page but am running into error 'artist.events is not iterable'. Below is my 'artist' model (models-> artists.js):
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const passportlocalMongoose = require('passport-local-mongoose');
const artistSchema = new Schema({
email: {
type: String,
required: true,
unique: true
},
location: {
type: String,
required: [true, 'Hometown (so you can be paired with local venues)']
},
genre: {
type: String
},
joined_date: {
type: Date,
default: Date.now
},
about: String,
size: Number,
});
artistSchema.plugin(passportlocalMongoose);
module.exports = mongoose.model('Artist', artistSchema);
Next is my event model (which associates artists with event through the 'artist' array.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const eventSchema = new Schema({
event_name: String,
location: String,
description: String,
image: String,
artist: {
type: Schema.Types.ObjectId,
ref: 'Artist'
},
});
module.exports = mongoose.model('Event', eventSchema);
Here is where I'm running into my actual problem - whey I try to list events under artists I hit the error. Below is the artist show page (views-> artists-> show.ejs)
<% layout('layouts/boilerplate') %>
<div class="row">
<div class="col-12">
<div class="card mb-3">
<img src="<%= artist.image %>" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title"><%= artist.username %></h5>
<p class="card-text">Genre: <%= artist.genre %></p>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item">Location: <%= artist.location %></li>
<li class="list-group-item">Number of people in group: <%= artist.size %></li>
<li class="list-group-item">About: <%= artist.about %></li>
</ul>
<% if (currentUser && artist.equals(currentUser._id)) {%>
<div class="card-body">
<a class="card-link btn btn-primary" href="/artists/<%=artist.id%>/edit">Edit</a>
<form class="d-inline" action="/artists/<%=artist.id%>?_method=DELETE" method="POST">
<button class="btn btn-primary">Delete</button>
<% } %>
</form>
<% for(let events of artist.events) { %>
<div class="class mb-3">
<p>Event name: <%= event.event_name %></p>
</div>
<% } %>
</div>
</div>
<div class="card-footer text-muted">
Back to
All Artists
</div>
</div>
</div>
Full error:
TypeError: /Users/chaseschlachter/mtapp/views/artists/show.ejs:22
20| <% } %>
21| </form>
>> 22| <% for(let events of artist.events) { %>
23| <div class="class mb-3">
24| <p>Event name: <%= event.event_name %></p>
25| </div>
artist.events is not iterable
Adding my artists routes for context (routes-> artists.js):
const express = require('express');
const router = express.Router();
const passport = require('passport');
const Artist = require('../models/artist');
const catchAsync = require('../utils/catchAsync');
const ExpressError = require('../utils/ExpressError');
/* lists artists from database */
router.get('/', async (req, res) => {
const artists = await Artist.find({});
res.render('artists/index', { artists })
});
router.get('/new', (req, res) => {
res.render('artists/new');
});
/* shows specific artists that exist in database */
router.get('/:id', catchAsync(async(req, res,) => {
const artist = await Artist.findById(req.params.id);
if (!artist) {
req.flash('error', 'Cannot find that Artist');
return res.redirect('/artists');
}
res.render('artists/show', { artist });
}));
/* artist edits form*/
router.get('/:id/edit', catchAsync(async (req, res) => {
const artist = await Artist.findById(req.params.id);
if (!artist) {
req.flash('error', 'Cannot find that Artist');
return res.redirect('/artists');
}
res.render('artists/edit', { artist });
}))
router.put('/:id', catchAsync(async (req, res) => {
const { id } = req.params;
const artist = await Artist.findByIdAndUpdate(id, { ...req.body.artist });
res.redirect(`/artists/${artist._id}`);
}))
What am I doing wrong?

Looking at your artistSchema, you haven't defined an events field so there isn't anything to iterate over. That field is simply not there inside the artist object, it is undefined.
Your artistSchema creates a collection of artist objects. These objects have only the fields you've supplied in your schema definition. Whereas you have another collection of event objects which are completely separate from artists defined by the eventSchema.
Since you would like to associate artists with events you have several options to do this:
Maintain a list of events as a array inside the artist schema (potentially of ObjectIds which reference the event objects)
Have events retain a reference to the artist (as you currently do) and then query over events using the artist's _id.
Do not store events as a separate collection and instead embed the event objects as an array inside the artist schema.
Each strategy has it's own pros and cons (read more about that here: https://docs.mongodb.com/manual/applications/data-models-relationships/)
I would think your best bet would be to go with option 2 as with option 1 you would need to make sure to have any new events or deletions of events reflected back in the artist's model as well.
In fact, since you're using mongoose you can implement what you want using virtuals (https://mongoosejs.com/docs/populate.html#populate-virtuals) as follows:
artistSchema.virtual('events', {
ref: 'Event', // The model to use
localField: '_id', // Find events where `localField`
foreignField: 'artist', // is equal to `foreignField`
justOne: false // we can have more than 1 event per artist
});
Now we only need to populate this events array, many ways to do this, one way would be:
artist.events // == undefined
artist.populate('events').execPopulate();
artist.events // == [event1, event2, etc...]
If you add this virtual to your artist object and you populate your artist object prior to your piece of code, it should execute as expected. As I mentioned though, this is just one way to achieve this.

Related

I have this code made with nodejs and i try to show in frontend the model of the car

I have a problem with this code.I try to show the car model in frontend with EJS but doesn't work.I make a router to get the model car from a collection and show it, but when i try to open that page, the model don't show up. I don't know what is wrong and what I need to do to work.
This is the code
The EJS part
<div class="row bg-text justify-content-center">
<% if(model !='' ){ %> <% model.forEach(function(model,index) { %>)
<div class="col-auto img-fluid">
<h1 style="color: rgb(196, 196, 196)" class="text-center">
<%= model.modelMasina.model %>
</h1>
<img class="dim-model" src="<%- model.poza %>" alt="<%- model.modelMasina.model %>" />
</div>
<% }) %> <% } %>
</div>
</div>
And here is the JS part
const router = express.Router();
const MasinaCategorie = require("../Models/categorie-modele");
const ModeleMasina = require("../Models/modele-masina");
router.get("/model/:nume", async (req, res) => {
try {
const nume = req.params.nume;
const model = await MasinaCategorie.find({modelMasina:{categorie:nume}});
res.render("modele", {
title: "Modele Masina",
model:model
});
} catch (error) {
res.status(500).send({
message: error.message || "Eroare la afisarea paginii principale",
});
}
});
module.exports = router;
And here is the Schema for MongoDB, i try to nest the model in category. I try in diffrent way to show but didn't work.
var mongoose = require('mongoose');
const modeleMasina= require("../Models/modele-masina");
var Schema = mongoose.Schema;
var categoriemasina = new Schema({
masina:{
type: String,
required: true,
},
poza:{
type: String,
},
locatie:{
type: String,
},
modelMasina:[{
categorie:{
type: String,
required: true,
},
model:{
type: String,
required: true,
},
poza:{
type: String,
},
}]
});
categoriemasina.index({modelMasina:'text'});
var CategorieMasina = mongoose.model('CategorieMasina', categoriemasina);
module.exports = CategorieMasina;

Loading more content from api in Node.js

So I just started learning Node.js and I'm trying to figure out how to load more content on the button click. I think I'm either misunderstanding something or I'm confusing myself.
The code might be a bit of a mess at the moment as I've been trying a bunch of different things.
So far I have a homepage route:
//Home
app.get('/', (req, res)=> {
res.render('homepage');
});
On the homepage, I have a search bar that calls the /search route with get:
<form action="/search" method="get" id="form">
<input class="search" name="searchQuery"/>
<button type="submit">Submit</button>
</form>
Then the search route which makes a call to the API, parses the XML, renders the search page, and sends some data:
//Search
app.get('/search', async (req, res) => {
g_searchQuery = req.query.searchQuery;
const fetch_response = async (url, query) => {
try {
const response = await fetch(url);
const xml = await response.text();
xml2js.parseString(xml, (error, result) => {
var paginations = result.GoodreadsResponse.search[0];
totalResults = parseInt(paginations['total-results']);
resultsEnd = parseInt(paginations['results-end']);
g_resultsEnd = resultsEnd;
var test = result.GoodreadsResponse.search[0].results[0].work.map( w => {
let bestBook = w.best_book[0];
return {
title: bestBook.title[0],
author: bestBook.author[0].name,
image_url: bestBook.image_url[0],
small_image: bestBook.small_image_url[0],
original_publication_year: w.original_publication_year[0]._,
}
});
res.render('search', {data : {
goodReadsResponse : test,
searchQuery : query,
totalResults : totalResults,
resultsEnd : g_resultsEnd,
currentPage : g_currentPage
}});
res.end();
});
}
};
fetch_response(goodreadsapi + '?key=' + goodreadskey + '&q=' + g_searchQuery + '&page=' + g_currentPage, g_searchQuery);
});
The search page renders the data and has a load more button which with Fetch api makes a call to /search POST:
<body>
<h1>You searched for: <%= data.searchQuery %></h1>
<% if(data.goodReadsResponse) { %>
<ul class="data-container">
<%= data.totalResults %>
<%= data.currentPage %>
<% data.goodReadsResponse.forEach(function(book) { %>
<li>
<span><%= book.title %></span>
<span><%= book.author %></span>
<span><img src="<%= book.image_url %>"/></span>
<span><img src="<%= book.small_image %>"/></span>
<span><%= book.original_publication_year %></span>
</li>
<% }); %>
</ul>
<% if(data.resultsEnd <= data.totalResults) { %>
<div class="load-more__container">
<button class="load-more__button" type="button">Load More</button>
</div>
<% } %>
<% } %>
</body>
<script type="text/javascript">
window.addEventListener('load', function() {
const loadMore = document.querySelector('.load-more__button');
if(loadMore) {
loadMore.addEventListener('click', (event) => {
event.preventDefault('Load More');
fetch('/search', {
method : 'POST'
})
});
}
});
</script>
Then this is the part where I get stuck and confused. I have a app.post /search route for the load more button but I'm not sure whats the best way to pass data to that route/if I even need to.
My thinking is that I need to pass most of the data I passed through the render in /search app.get route to the app.post /search route add 1 to the current page, then append the data that I get from the API to the previous data then render the page with that updated data. But I'm slightly stuck.
Here is my repo for this, it's not 1:1 since I tried to clean some things up for the question.
Any help would be greatly appreciated,
Thank you.
let bestBook = w.best_book[0];
In the above line, Considering w.best_book is a list of books, u could just loop it and save all the books in a seperate variable, and render it when Load More button is pressed.

Timing functions correctly in Express, Node and Mongo

I'm working on an app were a parent user needs to create a student object that can be accessed by both the parent user and teacher users. Everything is working right except this one thing. When the parent user creates the student, it redirects to the user's dash where the newly created student should display. However, the student doesn't display until I log out and back in. I'm assuming that this is because my code doesn't run in the order I want it to, but I have no idea. I've been stuck here for a couple days. Help!
Here's the route handling for creating a student
EDIT:
I got it to work by refactoring things here almost entirely. I was creating a student document and saving it and then pushing that document into an array on the parent object. That was dumb for two reasons. One, it wasn't displaying properly in terms of timing. Two, if I ever wanted to update the student, I needed to update it in multiple instances. Here, I'm creating the document and adding a reference to an array on the parent object. Now when I edit/delete a student, I do it in one place, and the ObjectID is immediately accessible for the Student.findById() that runs in the "users/:id" route. Here's my updated code:
CREATE STUDENT
router.post("/users/:id/createStudent", middleware.isLoggedIn, function(req, res){
const firstName = req.body.firstName,
lastName = req.body.lastName,
age = req.body.age,
instrument = req.body.instrument,
parent = {
id: req.user._id,
username: req.user.username
},
newStudent = {
firstName: firstName,
lastName: lastName,
age: age,
instrument: instrument,
parent: parent
}
Student.create(newStudent, function(err){
if(err){
console.log(err);
req.flash("error", "error")
} else {
res.redirect("/users/:id")
}
})
});
And the Dashboard template:
<%- include ("../partials/header") %>
//
code for teachers exists here
//
<% } else if(user.isTeacher === false) {%>
<h1 class="ui huge header"><%= user.username%>'s Dashboard</h1>
<h2 class="ui big header">Here are your students:</h2>
<% if(students.length === 0) {%>
<p>Oops! Looks like you haven't registered a student. You can do that here. </p>
<% } else { %>
<div class="ui relaxed divided list">
<% students.forEach(function(student){ %>
<div class="item">
<div class="content">
<%= student.firstName %> <%= student.lastName %>
<ul>
<li>
<a class="ui secondary mini basic button"
id="assignments"
href="/student/<%= student._id%>">
<%= student.firstName %>'s Assignments
</a>
<form action="/student/<%= student._id %>?_method=DELETE" method="POST">
<button class="ui negative mini basic button" id="assignments">
Remove Student
</button>
</form>
</li>
</ul>
</div>
</div>
<% }) %>
<p>Add a new student.</p>
</div>
<% } %>
<%}%>
</div>
</div>
<%- include ("../partials/footer") %>
Here's the student model:
const mongoose = require("mongoose"),
Schema = mongoose.Schema;
const StudentSchema= new Schema({
firstName: String,
lastName: String,
age: String,
instrument: String,
assignments: [
{ id: {
type: mongoose.Schema.Types.ObjectId,
ref:"Assignment"
},
title: String
}
],
parent: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
teacher: [{
id: {
type: mongoose.Schema.Types.ObjectId,
ref:"User"
},
username: String
}]
});
const Student = mongoose.model("student", StudentSchema);
module.exports = Student;

Error cannot read property of undefined Express app

I am currently learning to build a blog website using Node.js, Express and ejs
I got the following error when I try to render the "show" page
I found a similar problem here but there is a different usage of my code, I still couldn't get mine solved.
The similar problem
I did also had an same type error earlier says "cannot read property 'username' of undefined" , but after I restart the server it gives me a different error.
The error I got now:
TypeError: C:\Users\kevin\Desktop\MyBlog\views\blogs\show.ejs:22
20|
21| <div class="text-muted">
22| <%= blog.date.toLocaleDateString()%> By <%= blog.author.displayName %> **<------**
23| </div>
24| </div>
25|
Cannot read property 'toLocaleDateString' of undefined
Portion of router.js:
//show the specific blog
router.get("/home/:slug", async(req, res) => {
const blog = await Blog.find({ slug: req.params.slug });
console.log(blog);
if (blog == null) {
res.redirect("/home");
}
res.render("blogs/show", {blog: blog});
})
Portion of show.ejs
<div class="card-header">
<h1 class="mb-1">
<%= blog.title %>
<i class="fas fa-home fa-lg"></i>
<!-- Override DELETE method -->
<form action="/home/<%= blog.id %>?_method=DELETE" method="POST" class="d-inline">
<% if((currentUser) && (currentUser.username === blog.author.username)){ %>
<span class="far fa-edit fa-lg"></span>
<button type="submit" class="btn btn-light btn-sm float-right"><i class="fas fa-trash-alt fa-lg"></i></button>
<% } %>
</form>
</h1>
<div class="text-muted">
<%= blog.date.toLocaleDateString()%> By <%= blog.author.displayName %>
</div>
</div>
I checked that the blog object is saved into the database correctly:
{
"_id" : ObjectId("5f4722cc1a623a7710871d6d"),
"author" : {
"id" : ObjectId("5f47177faa8e6e3e4c80f3e9"),
"username" : "333#gmail.com",
"displayName" : "Jacky Chan"
},
"date" : ISODate("2020-08-27T03:04:44.646Z"),
"title" : "1321321321321",
"coverImg" : "",
"contents" : "<p>3213131321321321</p>",
"slug" : "1321321321321",
"sanitizedHtml" : "<p>3213131321321321</p>",
"__v" : 0
}
I used console.log in the ejs file and it seems that it did found the correct blog....
I am guessing that the blog object is referenced incorrectly, maybe?
Finally I don't know if it is the problem of async/await but I tried all I can which eventually lead me here...
Any help will be appreciated! Thanks in advance!
Update 1:
Thanks to #AdamExchange
I tried the answer from #AdamExchange that switched find() to findOne().
The previous error is gone but there is a new error that when I use my create new blog route, it is calling the show route and trying to find the blog.
New Error:
TypeError: C:\Users\kevin\Desktop\MyBlog\views\blogs\show.ejs:9
7| <div class="card-header">
8| <h1 class="mb-1">
9| <%= blog.title %>
10| <i class="fas fa-home fa-lg"></i>
11| <!-- Override DELETE method -->
12| <form action="/home/<%= blog.id %>?_method=DELETE"
method="POST" class="d-inline">
Cannot read property 'title' of null
My question is why would the show route get involved when I use the GET method on create routes? (I do have a button on the create blog page that uses the show route)
Here I will post my entire router hopefully that will help:
const express = require("express");
const router = express.Router({mergeParams:true});
const Blog = require("../models/blog");
const middleware = require("../middleware");
router.post("/home", (req, res, next) => {
req.blog = new Blog();
next()
}, saveBlogAndRedirect('new'))
router.get("/home/new", middleware.isLoggedIn, async(req, res) => {
let blog = new Blog();
res.render("blogs/new", { blog : blog });
})
//Get the blog with the correctid
router.get("/home/edit/:id", async(req, res) => {
const blog = await Blog.findById(req.params.id);
res.render("blogs/edit", { blog : blog });
})
router.put("/home/:id", async(req, res, next) => {
req.blog = await Blog.findById(req.params.id);
next()
}, saveBlogAndRedirect('edit'))
//show the specific blog
router.get("/home/:slug", async(req, res) => {
const blog = await Blog.findOne({ slug: req.params.slug });
if (blog == null) {
res.redirect("/home");
}
res.render("blogs/show", {blog: blog});
})
router.delete("/home/:id", async(req, res) => {
await Blog.findByIdAndDelete(req.params.id)
res.redirect("/home");
})
function saveBlogAndRedirect(path){
return async(req, res) => {
let blog = req.blog;
blog.title = req.body.title;
blog.coverImg = req.body.coverImg;
blog.contents = req.body.contents;
blog.author = {
id : req.user._id,
username: req.user.username,
displayName: req.user.displayName
}
try{
blog = await blog.save();
res.redirect(`/home/${blog.slug}`);
}
catch (e) {
console.log(e)
res.render(`blogs/${path}`, { blog : blog });
}
}
}
module.exports = router;
const blog = await Blog.find({ slug: req.params.slug });
Will return an array of the blogs with that slug. If you know there is only one, you can change it to .findOne. The array of blogs is getting passed to the ejs template as blog (noted by your screenshot
[
{
author: ...
}
]
The 'date' property of the array is meaningless, but if you make sure to only pass your ejs template one blog object, that should fix it
Updated your code for, it will also work if the number of document is more than one. Since you are using find() method it will return all the document with matching filter.
Router
//show the specific blog
router.get("/home/:slug", async(req, res) => {
const blog = await Blog.find({ slug: req.params.slug });
console.log(blog);
if(blog == null) {
res.redirect("/home");
}
res.render("blogs/show", {blog:blog});
});
ejs section
<% for(var i=0; i<blog.length; i++){ %>
<div class="card-header">
<h1 class="mb-1">
<%= blog[i].title %>
<i class="fas fa-home fa-lg"></i>
<!-- Override DELETE method -->
<form action="/home/<%= blog[i].id %>?_method=DELETE" method="POST" class="d-inline">
<% if((currentUser) && (currentUser.username === blog[i].author.username)){ %>
<span class="far fa-edit fa-lg"></span>
<button type="submit" class="btn btn-light btn-sm float-right"><i class="fas fa-trash-alt fa-lg"></i></button>
<% } %>
</form>
</h1>
<div class="text-muted">
<%= blog[i].date.toLocaleDateString()%> By <%= blog[i].author.displayName %>
</div>
</div>
<% } %>

How can I display my MongoDB attributes?

I hosted my Mongo database in mLab.com with several collections as shown in the picture below:
I can't seem to be able to access "requests" collection. Here is what I have done:
first, I connected to the database and created the function in the main process (main.js):
mongoose.connect('url', { useMongoClient: true });
ipcMain.on('load-requests', function(event) {
return Requests.find({}, { sort: {createdAt: -1}});
});
Inside another file called schema.js I have the following:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var hosSchema = new Schema({
hospital: String,
state: String,
reasons: String,
requestedDateTime: String,
requestNumber: String,
status: String,
});
module.exports = mongoose.model('Requests', hosSchema);
In the renderer process (homePage.html), I have the following:
<div id="page-inner">
<div class="row">
<div class="col-md-4 col-sm-4">
<div class="card teal">
<div class="card-content white-text">
<span class="card-title">state</span>
<p>reason</p>
</div>
<div class="card-action">
requestNumber
requestedDateTime
</div>
</div>
</div>
</div>
</div>
I want to access page-inner through its id and change the attributes to their related once in the database. For example the state should be changed with the attributes retrieved from the function in the main process (load-requests).
How can I display the attributes inside homePage.html?
In Schema.js:
var hosSchemaModel = mongoose.model('Requests', hosSchema);
module.exports = hosSchemaModel;
In main.js:
var hosSchemaModel = require("./Schema.js");
var getHosSchemas = function () {
hosSchemaModel.find({}, function (err, hosSchema) {
if (err) {
//...
} else {
//Do something with the hosSchema
}
});
}

Categories

Resources