Sequelize Bad Field Error on Eager loading after changing association - javascript

// Include Sequelize module.
const Sequelize = require('sequelize')
// Import sequelize object,
// Database connection pool managed by Sequelize.
const sequelize = require('./database.js')
const User = require('./User.js');
const Product = require('./Product.js');
// Define method takes two arrguments
// 1st - name of table
// 2nd - columns inside the table
const FavoriteProduct = sequelize.define('favorite_product', {
favorite_id:{
// Sequelize module has INTEGER Data_Type.
type:Sequelize.INTEGER,
autoIncrement:true,
allowNull:false,
primaryKey:true
},
createdAt: Sequelize.DATE,
//no dates
}, {
updatedAt: false
});
User.hasMany(FavoriteProduct, { foreignKey: 'user_id'} );
Product.hasMany(FavoriteProduct, { foreignKey: 'product_id'} );
FavoriteProduct.belongsTo(Product, { foreignKey: 'product_id'});
FavoriteProduct.belongsTo(User, { foriegnKey: 'user_id'})
module.exports = FavoriteProduct
Previously I had this model as a BelongsToMany model, with both Product and User->belongToMany through FavoriteProduct. I changed it because the eager loading of FavoriteProduct was not working when trying to load from products. I now have a new issue, attempting to eager load the new table throws this error:
"code": "ER_BAD_FIELD_ERROR",
"errno": 1054,
"sqlState": "42S22",
"sqlMessage": "Unknown column 'favorite_product.userUserId' in 'field list'",
I don't know why this occurred, but it must be because I attempted to switch from a belongsToMany association without doing any migrations. There are no extra columns in the database or anything like that.
Does anyone know how to fix this field issue without deleting the table?
For reference, I am simply doing this:
await Product.findOne({
include: [{
model: FavoriteProduct,
required: false,
}]

You need to correct the association FavoriteProduct.belongsTo(User: you misspelt the option foreignKey by indicating foriegnKey.
FavoriteProduct.belongsTo(User, { foreignKey: 'user_id'})

Related

Mongoose: setting default populate options

I have a schema in mongoose that references another schema, eg:
OrderSchema = new Schema({
createdBy: {
ref: 'User',
type: ObjectId
}
})
In my user schema, I'm using a soft-delete plugin to keep references. For normal user find queries, the plugin adds a where {deleted: {$ne: true}} to the query using a pre find hook.
When I try to find all orders with createdBy populated, the deleted query is also applied so that any (soft) deleted users are not populated. I can bybass the soft delete query by supplying a "includeDeleted" parameter in population options, this works well for specific queries.
I would like to be able to specify this option in the schema definition so that im not relying on every query to include the options, eg:
// doesnt work, options are not supplied to populate query
OrderSchema = new Schema({
createdBy: {
ref: 'User',
type: ObjectId,
options: {
includeDeleted: true
}
}
})
Virtual populates does work this way:
// WORKS
OrderSchema.virtual('_createdBy', {
ref: 'User',
...,
options: {
includeDeleted: true
}
})
Maybe theres another options to supply default populate options in the schema definition? I havent been able to find anything in the documentation.
Another solution would be to manually lookup the population options in the soft-delete plugin, but that requires me to know if a query is a "population query" in the pre find hook.
https://mongoosejs.com/docs/schematypes.html,
populate: Object, sets default populate options
this may help .
i think you should use it this way
OrderSchema = new Schema({
createdBy: {
ref: 'User',
type: ObjectId,
populate: {
includeDeleted: true
}
}
})

Sequelize Populate Relation Query Either Table

I have the following two tables in Sequelize
const Tokens = sequelize.define("Tokens", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
active: {
type: DataTypes.BOOLEAN
}
});
and
const User = sequelize.define("Users", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
first_name: {
type: DataTypes.STRING
}
});
User.associate = models => {
models["Users"].hasMany(models["Tokens"], {foreignKey: 'userID', as: 'tokens_userid'});
};
I'm trying to run the following query in Sequelize.
const token = await db.Tokens.findOne({
where: {
id,
active: true
},
include: ["tokens_userid"]
});
But I'm getting the following error.
Error: Association with alias "tokens_userid" does not exists
My main goal is to get the user based on a Token ID. Now I would just move that association to the User table, but the problem with that later on I will want to get all the tokens for a given User ID. So I will run into this problem either way.
I tried adding the following line, but it was complaining about circular relations or something like that.
models["Tokens"].hasOne(models["User"], {foreignKey: 'userID', as: 'tokens_userid'});
How can I query either the Users or Tokens table and have it populate correctly with the relation?
I was able to solve this by adding the following line to my table.
models["Tokens"].belongsTo(models["User"], {foreignKey: 'userID', as: 'tokens_userid_from_token'});
Basically what I tried before but changed hasOne to belongsTo.
Hopefully this helps someone else.

How do I ORM additional columns on a join table in sequelize?

I'm using node v9.5, sequelize v4.33 (postgres dialect).
I have two first-class models: Driver (specific people) and Car (generic make+model combinations). Thus far, they've been connected by a many-to-many join table. Now I want to start tracking additional properties on that join table, but am having trouble declaring these relationships so they actually work.
const Driver = sqlz.define('Driver', {
id: { primaryKey: true, type: DataTypes.UUID },
name: DataTypes.string
})
const Car = sqlz.define('Car', {
id: { primaryKey: true, type: DataTypes.UUID },
make: DataTypes.string,
model: DataTypes.string
})
// old associations; worked great when requirements were simpler
Driver.belongsToMany(Car, {
through: 'DriverCar',
as: 'carList',
foreignKey: 'driverId'
})
Car.belongsToMany(Driver, {
through: 'DriverCar',
as: 'driverList',
foreignKey: 'carId'
})
Now I want to begin tracking more information about the relationship between a car and its driver, like the color of that specific car.
Step 1: I update the migration script, adding a new column to the join table like so:
queryInterface.createTable( 'DriverCar', {
driverId: {
type: sqlz.UUID,
allowNull: false,
primaryKey: true,
references: {
model: 'Driver',
key: 'id'
}
},
carId: {
type: sqlz.UUID,
allowNull: false,
primaryKey: true,
references: {
model: 'Car',
key: 'id'
}
},
createdAt: {
type: sqlz.DATE,
allowNull: false
},
updatedAt: {
type: sqlz.DATE,
allowNull: false
},
// new column for join table
color: {
type: Sequelize.STRING
}
})
Step 2: I define a new sqlz model for DriverCar:
const DriverCar = sqlz.define('DriverCar', {
color: DataTypes.string
})
(I assume I only need to define the interesting properties, and that driverId and carId will still be inferred from the associations that will be defined.)
Step 3: I need to update the associations that exist among Driver, Car, and DriverCar.
This is where I'm stuck. I have attempted updating the existing associations, like so:
Driver.belongsToMany(Car, {
through: DriverCar, // NOTE: no longer a string, but a reference to new DriverCar model
as: 'carList',
foreignKey: 'driverId'
})
Car.belongsToMany(Driver, {
through: DriverCar, // NOTE: no longer a string, but a reference to new DriverCar model
as: 'driverList',
foreignKey: 'carId'
})
This executes without error, but the new color property is not fetched from the join table when I try driver.getCarList(). (Sqlz is configured to log every SQL statement, and I have verified that no properties from the join table are being requested.)
So, instead, I tried spelling out this relationship more explicitly, by associating Driver to DriverCar, and then Car to DriverCar:
// Driver -> Car
Driver.hasMany(DriverCar, {
as: 'carList',
foreignKey: 'driverId'
})
// Car -> Driver
Car.hasMany(DriverCar, {
foreignKey: 'carId'
})
I also tell sqlz that DriverCar won't have a standard row id:
DriverCar.removeAttribute('id')
At this point, requesting a Driver's carList (driver.getCarList()) seems to work, because I can see join table props being fetched in SQL. But saving fails:
driverModel.setCarList([ carModel1 ])
UPDATE DriverCar
SET "driverId"='a-uuid',"updatedAt"='2018-02-23 22:01:02.126 +00:00'
WHERE "undefined" in (NULL)
The error:
SequelizeDatabaseError: column "undefined" does not exist
I assume this error is occurring because sqzl doesn't understand the proper way to identify rows in the join table, because I've failed to establish the necessary associations. And frankly, I'm not confident I've done this correctly; I'm new to ORMs, but I was expecting I'd need to specify 4 assocations:
Driver -> DriverCar
DriverCar -> Car
Car -> DriverCar
DriverCar -> Driver
To recap: I have 2 first-class entities, joined in a many-to-many relationship. I'm trying to add data to the relationship, have discovered that the ORM requires defining those associations differently, and am having trouble articulating the new associations.
A note about your aliases
Before going to the answer, I would like to point out that your choice of aliases (carList and driverList) could be better, because although the auto-generated sequelize methods .setCarList() and .setDriverList() do make sense, the methods .addCarList(), .addDriverList(), .removeCarList() and .removeDriverList() are nonsense, since they take only a single instance as a parameter, not a list.
For my answer, I won't use any aliases, and let Sequelize default to .setCars(), .setDrivers(), .addCar(), .removeCar(), etc, which make much more sense to me.
Example of working code
I've made a 100% self-contained code to test this. Just copy-paste it and run it (after running npm install sequelize sqlite3):
const Sequelize = require("sequelize");
const sequelize = new Sequelize({ dialect: 'sqlite', storage: 'db.sqlite' });
const Driver = sequelize.define("Driver", {
name: Sequelize.STRING
});
const Car = sequelize.define("Car", {
make: Sequelize.STRING,
model: Sequelize.STRING
});
const DriverCar = sequelize.define("DriverCar", {
color: Sequelize.STRING
});
Driver.belongsToMany(Car, { through: DriverCar, foreignKey: "driverId" });
Car.belongsToMany(Driver, { through: DriverCar, foreignKey: "carId" });
var car, driver;
sequelize.sync({ force: true })
.then(() => {
// Create a driver
return Driver.create({ name: "name test" });
})
.then(created => {
// Store the driver created above in the 'driver' variable
driver = created;
// Create a car
return Car.create({ make: "make test", model: "model test" });
})
.then(created => {
// Store the car created above in the 'car' variable
car = created;
// Now we want to define that car is related to driver.
// Option 1:
return car.addDriver(driver, { through: { color: "black" }});
// Option 2:
// return driver.setCars([car], { through: { color: "black" }});
// Option 3:
// return DriverCar.create({
// driverId: driver.id,
// carId: car.id,
// color: "black"
// });
})
.then(() => {
// Now we get the things back from the DB.
// This works:
return Driver.findAll({ include: [Car] });
// This also works:
// return car.getDrivers();
// This also works:
// return driver.getCars();
})
.then(result => {
// Log the query result in a readable way
console.log(JSON.stringify(result.map(x => x.toJSON()), null, 4));
});
The code above logs as expected (as I would expect, at least):
[
{
"id": 1,
"name": "name test",
"createdAt": "2018-03-11T03:04:28.657Z",
"updatedAt": "2018-03-11T03:04:28.657Z",
"Cars": [
{
"id": 1,
"make": "make test",
"model": "model test",
"createdAt": "2018-03-11T03:04:28.802Z",
"updatedAt": "2018-03-11T03:04:28.802Z",
"DriverCar": {
"color": "black",
"createdAt": "2018-03-11T03:04:28.961Z",
"updatedAt": "2018-03-11T03:04:28.961Z",
"driverId": 1,
"carId": 1
}
}
]
}
]
Note that there is no secret. Observe that the extra attribute that you're looking for, color, comes nested in the query result, not in the same nesting level of the Car or Driver. This is the correct behavior of Sequelize.
Make sure you can run this code and get the same result I do. My version of Node is different but I doubt that could be related to anything. Then, compare my code to your code and see if you can figure out what is causing you problems. If you need further help, feel free to ask in a comment :)
A note about many-to-many relationships with extra fields
Since I stumbled myself upon problems with this, and this is related to your situation, I thought I should add a section in my answer alerting you to the "trap" of setting up an overcomplicated many-to-many relationship (it's a lesson that I learned myself after struggling for a while).
Instead of repeating myself, I will just add a brief quote of what I said in Sequelize Issue 9158, and add links for further reading:
Junction tables, the tables that exist in relational databases to represent many-to-many relationships, initially have only two fields (the foreign keys of each table defining the many-to-many relationship). While it's true that it's possible to define extra fields/properties on that table, i.e. extra properties for the association itself (as you put in the issue title), care should be taken here: if it's getting overcomplicated, it's a sign that you should "promote" your junction table to a full-fledged entity.
Further reading:
My own self-answered question involving an overcomplicated setup of many-to-many relationships in sequelize: FindAll with includes involving a complicated many-to-(many-to-many) relationship (sequelizejs)
And its sibling question: Is it OK to have a many-to-many relationship where one of the tables involved is already a junction table?

How to get all data of table by use of sequelize

I have postgres table name tests which contain few records, now I want to fetch all record of this table but unable because its provides only id,createdAt and updatedAt.
So either I have to provide an object which contain column name that I don't want, I wish it should be dynamic so after this if I pass another table name it will provide data of that.
I tried this but it returns null array of object
Project.findAll(attributes: ['*']
}).then(function(project) {
console.log("select_data: " + JSON.stringify(project));
})
I'm not totally clear on what the question / problem is here, but it sounds like when you're defining your model, you might not have included all the column names of the table in your model definition. Here's what it should look like:
module.exports = function(sequelize, DataTypes) {
const test = sequelize.define('test', {
my_column: DataTypes.STRING,
my_other_column: DataTypes.STRING,
my_boolean_column: DataTypes.BOOLEAN,
}, {
timestamps: true,
underscored: true,
tableName: 'tests',
})
return test
}
Notice each column of the table is explicitly defined in the model.

How to create foreign keys with sequelizejs in a feathersjs project

I am using featherjs v2.0.3 with sequelize v3.29.0 and I just created three models, the third having a relationship to the other two.
I used the feathers-cli to generate services for each and then edit the model file of each.
So far, so good, the tables are created (using PostgreSQL), indexes are created, feathersjs takes care of the CRUD nicely, but not foreign keys yet.
So, when I try to tell feathersjs the relationship between the models, I get in trouble.
When I add role_permission.belongsTo(permissions) to the role_permission model, I get this error:
ReferenceError: permissions is not defined
As I've seen in the Sequelize documentation, the models are defined in the same "document", hence I suspect the problem is somewhere there, but I don't understand what need to be done.
Finally, here's the relevant parts of the model definitions of permission and role_permission:
// permission-model.js - A sequelize model
module.exports = function(sequelize) {
const permission = sequelize.define('permissions', {
permission_id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV1,
primaryKey: true,
allowNull: false
}...
}, ...);
permission.sync();
return permission;
};
// role_permission-model.js - A sequelize model
module.exports = function(sequelize) {
const role_permission = sequelize.define('role_permissions', {
permission_id: {
type: Sequelize.UUID,
allowNull: false
}...
}, ...);
role_permission.belongsTo(permissions) //<-- undefined?
role_permission.sync();
return role_permission;
};
Do you have any pointers to help me solve this?
Thanks!
You need to import the permissions model. But it may or may not be defined yet.
Here's a method I discovered from #mrpatiwi on github to ensure every model is loaded before the associations are set up.
First, when you need to define a relationship add a classMethod called associate that accepts all the models and sets up the relationships.
module.exports = function(sequelize) {
const role_permission = sequelize.define('role_permisson', {
...
}, {
classMethods: {
associate(models) {
role_permission.belongsTo(models.permission);
},
},
});
// Don't add role_premission.sync() here
return role_permission;
};
Then, in src/services/index.js at the end of the module.exports function, add:
// Setup relationships
const models = sequelize.models;
Object.keys(models)
.map(name => models[name])
.filter(model => model.associate)
.forEach(model => model.associate(models));
sequalize.sync();
I had the same issue and resolved it simply by changing this:
role_permission.belongsTo(permissions)
to this:
role_permission.belongsTo(models.permissions)
(I used the Feathers CLI to generate the models today, 9 Aug 2018)

Categories