Search code examples
mysqlnode.jsexpresssequelize.jssequelize-cli

SequelizeEagerLoadingError: (parent) is not associated to (child)!


I am building an application using sequelize. I currently have 3 tables; a User, a Tour, and a Location. The Location has a n:1 relationship with the Tour. The Tour has a n:1 relationship with the user.

Without the User association, the other two tables work fine. Once I add in the user association (and I have tried to do so through a migration AND by dropping and then recreating my entire database), I get a SequelizeEagerLoadingError: Location is not associated with Tour!

Here are my models:

module.exports = function(sequelize, DataTypes) {
  var Location = sequelize.define("Location", {
    title: {
      type: DataTypes.STRING,
      allowNull: false
    },
    description: {
      type: DataTypes.TEXT,
      allowNull: false,
      validate: {
        len: [500]
      }
    },
    address: {
      type: DataTypes.TEXT,
      allowNull: false
    }
  });

  Location.associate = function(models) {
    Location.belongsTo(models.Tour, {
      onDelete: "cascade"
    });
  };

  return Location;
};

module.exports = function(sequelize, DataTypes) {
  var Tour = sequelize.define("Tour", {
    title: {
      type: DataTypes.STRING,
      allowNull: false
        },
    description: {
      type: DataTypes.TEXT,
      allowNull: false,
      validate: {
        len: [1, 1000]
      }
    },
    neighborhood: {
      type: DataTypes.STRING,
      allowNull: false
    },
    URL: {
      type: DataTypes.TEXT,
      allowNull: false,
      validate: {
        len: [1, 1000]
      }
    },
    numberOfStops: DataTypes.INTEGER,
    duration: {
      type: DataTypes.INTEGER,
      allowNull: false
    },
    tags: DataTypes.STRING
  });

    Tour.associate = function(models) {
    Tour.hasMany(models.Location);
  };

  Tour.associate = function(models) {
    Tour.belongsTo(models.User);
  };

  return Tour;
};
 

var bcrypt = require("bcrypt-nodejs");
module.exports = function(sequelize, DataTypes) {
  var User = sequelize.define("User", {
    name: {
      type: DataTypes.STRING,
      allowNull: false
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true,
      validate: {
        isEmail: true
      }
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    }
  });
  User.prototype.validPassword = function(password) {
    return bcrypt.compareSync(password, this.password);
  };

  User.hook("beforeCreate", function(user) {
    user.password = bcrypt.hashSync(
      user.password,
      bcrypt.genSaltSync(10),
      null
    );
  });

  User.associate = function(models) {
    User.hasMany(models.Tour);
  };

  return User;
};

And here is the include statement where it is failing, and where we establish the link with the tourId to the location:

app.get("/tour/:id", function(req, res) {
    db.Tour.findOne({
      where: { id: req.params.id },
      include: [db.Location]
    }).then(function(tour) {
      res.render("tour", {
        tour: tour
      });
    });
  });
  
  
  
  
   

var API = {
  saveTour: function(tour) {
    return $.ajax({
      headers: {
        "Content-Type": "application/json"
      },
      type: "POST",
      url: "api/tours",
      data: JSON.stringify(tour)
    });
  },
  saveLocations: function(locations) {
    return $.ajax({
      headers: {
        "Content-Type": "application/json"
      },
      type: "POST",
      url: "api/locations",
      data: JSON.stringify(locations)
    });
  },
  getUserId: function() {
    return $.ajax({
      type: "GET",
      url: "api/user_data"
    });
  }
};

var tour = {
    Users: thisUser.getUserId(),
    title: title,
    description: description,
    neighborhood: neighborhood,
    URL: URL,
    duration: duration,
    tags: tags
  };

  // console.log(tour);

  if (!errors.length) {
    // Post our tour to the Tours table, then reveal the form and set our local tour object.
    API.saveTour(tour).then(function(tour) {
      document.getElementById("submit-tour").remove();
      document.getElementById("tourstopssection").style.display = "block";
      thisTour.setId(tour.id);
    });
  }
}

// Function takes in the newly created tour object, grabs DOM values for each.
function addTourLocations(e) {
  e.preventDefault();
  // Grab and process all of our tour stops.
  var locationElements = document.getElementsByClassName("tourstop");
  var areStopErrors = false;
  var locations = [];

  // Loop over every location element on the DOM.
  for (var j = 0; j < locationElements.length; j++) {
    var children = locationElements[j].children;

    // Initialize this location with the tour id; we'll pass in data...
    var thisLocation = {
      TourId: thisTour.getId()
    };

    // ... by looping over the DOM children and grabbing their form values.
    for (var k = 0; k < children.length; k++) {
      if (
        children[k].classList.value.includes("stoptitle") &&
        children[k].value
      ) {
        var stopTitle = children[k].value;
        thisLocation.title = stopTitle;
      }

      if (
        children[k].classList.value.includes("stopaddress") &&
        children[k].value
      ) {
        var stopAddress = children[k].value;
        thisLocation.address = stopAddress;
      }

      if (
        children[k].classList.value.includes("stopdescription") &&
        children[k].value
      ) {
        var stopDescription = children[k].value;
        thisLocation.description = stopDescription;
      }
    }

    // Push this location into our locations array.
    locations.push(thisLocation);

Finally, this is how the app/db are synced:

require("dotenv").config();
var express = require("express");
var session = require("express-session");
var exphbs = require("express-handlebars");
var helpers = require("./lib/helpers");

var db = require("./models");
var passport = require("./config/passport");

var app = express();
var PORT = process.env.PORT || 3000;

// Middleware
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static("public"));

var hbs = exphbs.create({
  defaultLayout: "main",
  helpers: helpers // Require our custom Handlebars helpers.
});

//Sessions are used to keep track of our user's login status
app.use(
  session({ secret: "keyboard cat", resave: true, saveUninitialized: true })
);
app.use(passport.initialize());
app.use(passport.session());
app.use(function(req, res, next) {
  res.locals.user = req.user; // Set a local variable for our user.
  next();
});

// Handlebars
app.engine("handlebars", hbs.engine);
app.set("view engine", "handlebars");

// Routes
require("./routes/apiRoutes")(app);
require("./routes/htmlRoutes")(app);

var syncOptions = { force: false };

// If running a test, set syncOptions.force to true
// clearing the `testdb`
if (process.env.NODE_ENV === "test") {
  syncOptions.force = true;
}

// Starting the server, syncing our models ------------------------------------/
db.sequelize.sync(syncOptions).then(function() {
  app.listen(PORT, function() {
    console.log(
      "==> 🌎  Listening on port %s. Visit http://localhost:%s/ in your browser.",
      PORT,
      PORT
    );
  });
});

module.exports = app;

I've been googling for four days....help!


Solution

  • Try adding this to your associations, also why are you defining twice the association function on Tour?

    module.exports = function(sequelize, DataTypes) {
      var Location = sequelize.define("Location", {
        //
      });
    
      Location.associate = function(models) {
        Location.belongsTo(models.Tour, { as:'Tour', foreignKey:'tourId', onDelete: "cascade"});
      };
    
      return Location;
    };
    
    module.exports = function(sequelize, DataTypes) {
      var Tour = sequelize.define("Tour", {
        //
      });
    
      Tour.associate = function(models) {
        Tour.hasMany(models.Location, { as: 'Locations', foreignKey: 'tourId'});
        Tour.belongsTo(models.User, { as: 'User', foreignKey: 'userId' });
      };
    
    
      return Tour;
    };
    
    module.exports = function(sequelize, DataTypes) {
      var User = sequelize.define("User", {
        //
      });
    
      User.associate = function(models) {
        User.hasMany(models.Tour, {as: 'Tours', foreignKey: 'userId'});
      };
    
      return User;
    };
    

    And add the same on the query.

    db.Tour.findOne({
      where: { id: req.params.id },
      include: [{ 
        model: db.Location,
        as: 'Locations'
      }]
    }).then(function(tour) {
      res.render("tour", {
        tour: tour
      });
    });