0

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
            <a href="/artists">All Artists</a>
          </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?

1 Answer 1

1

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:

  1. Maintain a list of events as a array inside the artist schema (potentially of ObjectIds which reference the event objects)

  2. Have events retain a reference to the artist (as you currently do) and then query over events using the artist's _id.

  3. 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.

Sign up to request clarification or add additional context in comments.

5 Comments

Hi @david Gorski - thank you for the comprehensive answer. I think I know how to execute the 'populate' component (thanks to your help), but qq on the virtual artist schema.. would artistSchema.virtual exist in a new model, or could I append to the bottom of my artist model as an array?
Thanks again for the help. Read the docs I appended virtual artist to the bottom of my artist model (as its own array) but am struggling to add the populate line to my artists routes (added above) without "TypeError: Cannot read property 'populate' of undefined". Where should this line go?
Accidentally marked this question answered but it's not answered.
Hi @econobro, to the first question I typically just add the virtual call after my schema declaration (like how you have your plugin call after the schema declaration). As to the second problem you're encountering: it looks like your artist document is undefined so it seems like the problem has to do with the code leading up to what you have posted. Make sure that you properly query and await for your desired artist before trying to populate events. Sorry, hard to tell what's wrong without the full picture.
No problem - thanks for the additional context @David Gorski!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.