Sequelize doesn't use field alias from model - sql

I have model called proxyPool with next fields:
poolId: {
type: DataTypes.INTEGER,
references: {
model: 'pool',
key: 'id',
},
field: 'pool_id',
},
proxyId: {
type: DataTypes.INTEGER,
references: {
model: 'proxy',
key: 'id',
},
field: 'proxy_id',
},
It's a N:M table for two tables which have next associations:
proxy.associate = (models) => {
proxy.belongsToMany(models.pool, {
through: models.proxyPool,
foreignKey: 'proxy_id',
});
};
and
pool.associate = (models) => {
pool.belongsToMany(models.proxy, {
through: models.proxyPool,
foreignKey: 'pool_id',
});
};
When I call proxyPool.findOrCreate({where: {proxyId, poolId}}) it says that column proxyPool.proxyId does not exist, but in raw SQL I see:
SELECT "id", "pool_id" AS "poolId", "createdAt", "updatedAt", "pool_id", "proxy_id"
FROM "portnoi"."proxy_pool" AS "proxyPool"
WHERE "proxyPool"."proxyId" = '3' AND "proxyPool"."pool_id" = '1' LIMIT 1;
Why does it use alias for poolId = pool_id but not use alias described in model for proxyId = proxy_id?

Have you tried to make a query like that?
proxyPool.findOrCreate({
where: {
proxyId: {
[Op.eq]: proxyId,
},
...
}
})

Related

Sequelize define association on Array

I have two models in Sequelize as below:
export const User = db.define('user', {
id: {
type: Sequelize.UUID,
primaryKey: true,
},
});
export const Class = db.define('class', {
id: {
type: Sequelize.UUID,
primaryKey: true,
},
students: Sequelize.ARRAY({
type: Sequelize.UUID,
references: { model: 'users', key: 'id' },
})
});
How can I define an association between my Class model and the user model?
I have tried the below but it gives me an error.
Class.hasMany(User, { foreignKey: 'students' });
User.belongsTo(Class);
DatabaseError [SequelizeDatabaseError]: column "class_id" does not exist
I think you are missing the syntax
students: Sequelize.ARRAY({
type: Sequelize.UUID,
references: { model: 'users', key: 'id' }, // this has no effect
})
Should be
students: {
type: DataTypes.ARRAY({ type: DataTypes.UUID }),
references: { model: 'users', key: 'id' }
}
This won't work either, because the data type of students (ARRAY) and id (UUID) of User does not match.
Also, with these associations, you are adding two columns on User referencing id of Class but you only need one
Class.hasMany(User, { foreignKey: 'students' }); //will add students attribute to User
User.belongsTo(Class); //will add classId attribute to User
if you want to name the foreign key column passe the same name to both associations, by default Sequelize will add classId, however if you configured underscored: true on the models it will be class_id
Here is a working solution
const User = sequelize.define('user', {
id: { type: DataTypes.UUID, primaryKey: true },
});
const Class = sequelize.define('class', {
id: { type: DataTypes.UUID, primaryKey: true },
students: DataTypes.ARRAY({ type: DataTypes.UUID }),
});
Class.hasMany(User, { foreignKey: 'class_id' });
User.belongsTo(Class, { foreignKey: 'class_id' });

How to use an alias inside an include where condition using Sequelize

A table contains 2 user id cols, userOneId and userTwoId. My user id could be in either col. I'd like to get all rows where my userId is in either col, and also include the associated object of the other user.
Here's the latest attempt. I query where my id is in userOneId or userTwoId, make an alias for the other user id, otherUserId, with a case/when. Then use alias otherUserId in an include, to return the other user associated object.
where: {
[Op.or]: [{
userOneId: userId,
}, {
userTwoId: userId,
}]
},
attributes: [
'id',
[sequelize.literal("CASE WHEN \"userOneId\" = " + userId + " THEN \"userTwoId\" ELSE \"userOneId\" END"), 'otherUserId']
],
include: {
where: {
id: '$otherUserId$'
},
model: sequelize.models.User,
as: 'otherUser',
attributes: [ 'id', 'name', ...
Model associations are:
MyModel.belongsTo(models.User, {
as: 'userOne',
foreignKey: 'userOneId'
})
MyModel.belongsTo(models.User, {
as: 'userTwo',
foreignKey: 'userTwoId'
})
User.hasMany(models.MyModel, {
as: 'userOne',
foreignKey: {
name: 'userOneId',
allowNull: false
}
})
User.hasMany(models.MyModel, {
as: 'userTwo',
foreignKey: {
name: 'userTwoId',
allowNull: false
}
})
However, it errors with
User is associated to MyModel multiple times. To identify the correct association, you must use the 'as' keyword to specify the alias of the association you want to include.
Any pointers on if or how to get this to work?

How to create element associated with an existing object

I created the following two models...
const Account = sequelize.define("account",
{
id_account: {
type: Sequelize.INTEGER.UNSIGNED,
primaryKey: true,
autoIncrement: true
},
name: {...},
surname: {...},
username: {...},
password: {...}
},
{
name: {
singular: "Account",
plural: "Accounts"
},
freezeTableName: true,
hooks: {
beforeSave: ((account, options) => {
return bcrypt.hash(account.password, 10)
.then(hash => {account.password = hash;})
.catch(err => {throw new Error();});
})
}
});
const Genre = sequelize.define("genre",
{
genre_name: {
type: Sequelize.STRING(40),
primaryKey: true
},
url_img: {
type: Sequelize.STRING(40),
allowNull: false
}
},
{
name: {
singular: "Genre",
plural: "Genres"
},
freezeTableName: true
});
...and the following associations
Account.Genres = Account.belongsToMany(Genre, {
through: "AccountGenre",
foreignKey: "ref_account"
});
Genre.Accounts = Genere.belongsToMany(Account, {
through: "AccountGenre",
foreignKey: "ref_genre"
});
I created the following genres: Rock, Metal, Pop, Hardcore.
Now i want to create an Account and associate it 3 genres.
The following code creates the Account but doesn't create the association with the existing genres in the AccountGenre table:
const genresArray = ["Rock", "Metal", "Pop"];
const account = {...} // I have an object with account properties
Account.create({
name: account.name,
surname: account.cognome,
username: account.nome_utente,
password: account.password,
genres: genresArray
}, {
include: [Genre]
});
What's wrong in this code?
The problem here is that you gave the reference to the
association the plural name “Genres”. You need the same in the include statement.
include: [Genres]

Filter by belongsToMany relation field

It is impossible to filter data using a linked table. There are two tables Instructor and Club. They related how belongsToMany. I need to get all Instructors which club_id = value.
Instructor model:
sequelize.define('Instructor', {
instance_id: DataTypes.INTEGER,
name: DataTypes.STRING(255)
}, {
tableName: 'instructors',
timestamps: false,
classMethods: {
associate: function (models) {
Instructor.belongsToMany(models.Club, {
through: 'InstructorClub'
});
}
}
});
Club model:
sequelize.define('Club', {
instance_id: DataTypes.INTEGER,
name: DataTypes.STRING
}, {
tableName: 'clubs',
timestamps: false,
classMethods: {
associate: function (models) {
Club.belongsToMany(models.Instructor, {
through: 'InstructorClub'
});
}
}
});
Related table:
sequelize.define('InstructorClub', {
InstructorId: {
type: DataTypes.INTEGER,
field: 'instructor_id'
},
ClubId: {
type: DataTypes.INTEGER,
field: 'club_id'
}
}, {
tableName: 'instructors_clubs'
timestamps: false
});
I am trying to get the data as follows::
models
.Instructor
.findAll({
include: [
{
model: models.Club,
as: 'Clubs',
through: {
attributes: []
}
}
],
# I need to filter by club.id
where: {
'Clubs.id': 10
}
})
Current query generated SQL:
SELECT `Instructor`.`id`,
`Instructor`.`instance_id`,
`Instructor`.`name`,
`Clubs`.`id` AS `Clubs.id`,
`Clubs`.`name` AS `Clubs.name`,
`Clubs.InstructorClub`.`club_id` AS `Clubs.InstructorClub.ClubId`,
`Clubs.InstructorClub`.`instructor_id` AS `Clubs.InstructorClub.InstructorId`
FROM `instructors` AS `Instructor`
LEFT OUTER JOIN (`instructors_clubs` AS `Clubs.InstructorClub` INNER JOIN `clubs` AS `Clubs` ON `Clubs`.`id` = `Clubs.InstructorClub`.`club_id`)
ON `Instructor`.`id` = `Clubs.InstructorClub`.`instructor_id`
WHERE `Instructor`.`Clubs.id` = 10;
Well, I need some kind of this:
SELECT `Instructor`.`id`,
`Instructor`.`instance_id`,
`Instructor`.`name`,
`Clubs`.`id` AS `Clubs.id`,
`Clubs`.`name` AS `Clubs.name`,
`Clubs.InstructorClub`.`club_id` AS `Clubs.InstructorClub.ClubId`,
`Clubs.InstructorClub`.`instructor_id` AS `Clubs.InstructorClub.InstructorId`
FROM `instructors` AS `Instructor`
LEFT OUTER JOIN (`instructors_clubs` AS `Clubs.InstructorClub` INNER JOIN `clubs` AS `Clubs` ON `Clubs`.`id` = `Clubs.InstructorClub`.`club_id`)
ON `Instructor`.`id` = `Clubs.InstructorClub`.`instructor_id`
# It should be like this:
WHERE `Clubs`.`id` = 10;
Move your 'where' up into the include (with model, as, and through).
include: [ {
model: models.Club,
as: 'Clubs',
through: { attributes: [] },
where: { 'Clubs.id': 10 }
} ]

Sails.js + Waterline: Many-to-Many through association

I'm new to Sails.js (v0.10.5) and Waterline ORM. I have 3 tables in database: users (id, name), roles(id, alias) and join table users_roles(user_id, role_id). It's important not to change table names and field names in database. I want Policy entity to be a join entity between User and Role. Here is some mapping code:
//User.js
module.exports = {
tableName: 'users',
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
id: {
type: 'integer',
required: true
},
name: {
type: 'string'
},
roles: {
collection: 'role',
via: 'users',
through: 'policy'
},
}
}
//Role.js
module.exports = {
tableName: "roles",
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
id: {
type: 'integer',
required: true
},
alias: {
type: 'string',
required: true
},
users: {
collection: 'user',
via: 'roles',
through: 'policy'
}
}
}
//Policy.js
module.exports = {
tableName: "users_roles",
tables: ['users', 'roles'],
junctionTable: true,
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
user: {
columnName: 'user',
type: 'integer',
foreignKey: true,
references: 'user',
on: 'id',
via: 'role',
groupBy: 'user'
},
roles: {
columnName: 'role',
type: 'integer',
foreignKey: true,
references: 'role',
on: 'id',
via: 'user',
groupBy: 'role'
}
}
}
But when I trying to access roles atribute in controller
User.findOne({id: 1}).populate('roles').exec(function(err, user) {
console.log(JSON.stringify(user.roles));
});
this returns
[]
And
User.findOne({id: 1}).populate('roles').exec(function(err, user) {
console.log(JSON.stringify(user));
});
returns
{"id":1,"name":"test", "roles":[]}
I checked twice that user, role and association between them exists in database. What is my mistake?
I have found way to solve this problem. It's not what I exactly want, but it works.
First: join entity:
//Policy.js
module.exports = {
tableName: "users_roles",
autoPK: false,
attributes: {
id: {
type: 'integer',
primaryKey: true,
autoIncrement: true,
},
user: {
columnName: 'user_id',
model: 'user'
},
role: {
columnName: 'role_id',
model: 'role'
}
},
//tricky method to get all users for specified role_id
//or to get all roles for specified user_id
get: function(id, modificator, cb) {
var fields = ['user', 'role'];
if (fields.indexOf(modificator) < 0) {
cb(new Error('No such modificator in Policy.get()'), null);
}
var inversedField = fields[(fields.indexOf(modificator) + 1) % 2];
var condition = {};
condition[inversedField] = id;
this.find(condition).populate(modificator).exec(function(err, policies) {
if (err) {
cb(err, null);
return;
}
var result = [];
policies.forEach(function(policy) {
result.push(policy[modificator]);
});
cb(null, result);
return;
});
}
}
As you see, I added ID field to this entity (and to db table users_roles too), so it's not the great solution.
//User.js
module.exports = {
tableName: 'users',
autoPK: false,
attributes: {
id: {
type: 'integer',
primaryKey: true,
autoIncrement: true,
unique: true,
},
name: {
type: 'string'
},
policies: {
collection: 'policy',
via: 'user'
}
}
}
And Role Entity:
//Role.js
module.exports = {
tableName: 'roles',
autoPK: false,
attributes: {
id: {
type: 'integer',
primaryKey: true,
autoIncrement: true,
},
alias: {
type: 'string',
required: true,
unique: true,
},
policies: {
collection: 'policy',
via: 'role'
}
}
}
That's how I get all roles for specified user_id:
...
id = req.session.me.id; //user_id here
Policy.get(id, 'role', function(err, roles) {
var isAdmin = false;
roles.forEach(function(role) {
isAdmin |= (role.id === 1);
});
if (isAdmin) {
next(null);
return;
} else {
return res.redirect('/login');
}
});
...
Maybe it'll be usefull for somebody =)