Sequelize: can attribute have value from relation attribute data? - node.js

i don't know if this is possible, there are 2 models which are associated through relation, and models are defined this way:
sequelize.define('account_has_plan', {
account_id: {
type: DataTypes.INTEGER(10),
allowNull: false,
primaryKey: true,
references: {
model: 'account',
key: 'id'
}
},
plan_id: {
type: DataTypes.INTEGER(10),
allowNull: false,
primaryKey: true,
references: {
model: 'plan',
key: 'id'
}
},
type_id: {
type: DataTypes.INTEGER(10),
allowNull: false,
primaryKey: true,
references: {
model: 'type',
key: 'id'
}
},
create_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: 'CURRENT_TIMESTAMP'
},
update_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: 'CURRENT_TIMESTAMP'
}
}, {
tableName: 'account_has_plan',
underscored: true,
freezeTableName: true,
timestamps: false
});
sequelize.define('type', {
id: {
type: DataTypes.INTEGER(10),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
element: {
type: DataTypes.ENUM('Meal','Workout','Status'),
allowNull: false
}
}, {
tableName: 'type',
freezeTableName: true,
timestamps: false
});
the code on which calls the models and execute the query to DB is this:
var model = $.loadModel("account_has_plan");
var type = $.loadModel("type");
model.belongsTo(type);
var query = {
where: {
account_id: $.params.accountId
},
limit: $.query.limit || 12,
offset: $.query.offset || 0,
attributes:["account_id"],
include: [{
model: type
}]
};
model.findAll(query)
.then(function(data){
$.data = data;
$.json();
})
.catch(function(err){
console.log(err);
errorHandler.throwStatus(500, $);
});
this way the data from the server responds like this:
{
"account_id": 1,
"type": {
"id": 2,
"name": "Arms",
"element": "Workout"
}
}
there is actually no problem with this data, but i am forced to follow a pattern from docs i was provided with, which the difference there is that type is actually a string value rather than object value as presented in the example, so in the end the data structure i try to get has to be like this:
{
"account_id": 1,
"type": "Arms"
}
i have actually how idea on how to achieve this, i know i have little practice with sequelize, maybe this has to be defined through a model method or using through in the reference (which i have tried but returns error)
but the point is.. i need to know if it can be possible before i have to report a change on the REST documentation which is big

First of all, since what you'll be getting through a sequelize query are instances, you need to convert it to "plain" object. You could do that by using a module called sequelize-values.
Then you should be able to manually map the value to the one you're looking for.
model.findAll(query)
.then(function(data){
return data.map(function(d){
var rawValue = d.getValues();
rawValue.type = rawValue.type.name;
return rawValue;
});
})
.then(function(data){
$.data = data;
$.json();
})
.catch(function(err){
console.log(err);
errorHandler.throwStatus(500, $);
});

Related

NodeJS - Sequelize how to declare association for eager loading only once to be used for queries later

The problem:
Whenever I fetch a user, I always have to declare/include the association on the query to get its role:
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: { email },
include: [ // EVERY QUERY, I HAVE TO INCLUDE THIS
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
}
]
});
Now there are instance where I forget to include this association so I get a undefined role.
My question is, is there a way where I only set this association once so that I don't have to include this later on my queries?
This the model for my AccessUser table
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
email: {
type: DataTypes.STRING(255),
allowNull: false
},
firstname: {
type: DataTypes.STRING(255),
allowNull: false
},
lastname: {
type: DataTypes.STRING(255),
allowNull: false
},
password: {
type: DataTypes.STRING(255),
allowNull: false
},
disable: {
type: DataTypes.TINYINT,
allowNull: false,
defaultValue: 0
},
role_id: {
type: DataTypes.SMALLINT,
allowNull: false,
defaultValue: 0
},
created_modified: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
}, {
tableName: 'access_user',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "user_id" },
]
},
]
});
AccessUserRoleLup table
const AccessUserRoleLup = <AccessUserRoleLupStatic>sequelize.define<AccessUserRoleLupInstance>(
'AccessUserRoleLup',
{
role_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
role_name: {
type: DataTypes.STRING(50),
allowNull: false
},
role_code: {
type: DataTypes.CHAR(50),
allowNull: false,
defaultValue: ""
}
}, {
tableName: 'access_user_role_lup',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "role_id" },
]
},
]
});
Association:
db.models.AccessUser.hasOne(db.models.AccessUserRoleLup, {
foreignKey: 'role_id',
as: 'role'
});
Use defaultScope for AccessUser. defaultScope is defined in a model definition and it is always applied (unless you removed inline).
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
...
}, {
tableName: 'access_user',
timestamps: false,
defaultScope: { // Add this
include: [{
model: AccessUserRoleLup,
as: 'role'
}]
},
...
});
With this model definition, all queries will include AccessUserRoleLup.
If you would like to remove for a certain query, use .unscoped().
// These will automatically add eager loading for role
DB.PORTALDB.models.AccessUser.findAll()
DB.PORTALDB.models.AccessUser.findOne()
// These won't fetch role
DB.PORTALDB.models.AccessUser.unscoped().findAll()
DB.PORTALDB.models.AccessUser.unscoped().findOne()
More detail about scope: https://sequelize.org/master/manual/scopes.html
My initial workaround was to create a utility function for querying the user like so:
export const getAccessUser = (where: WhereOptions, include?: IncludeOptions) => {
return new Promise(async (resolve, reject) => {
try {
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: where,
include: [
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
},
...[include]
]
});
resolve(user);
} catch (err) {
reject(err);
}
});
}
I wonder if my question above can be done in much simpler way.

Trying to search entity with association Many to Many Sequelize

PROBLEM RESUME:
I'm having trouble when I try to do a findOne or findAll.
At the findOne or findAll answer I catch all the informations from the user but in the answer there aren't any data of "t_roles" associated to this user.
But the stranger issue is that if I use raw: true inside the findOne for example, the informations of roles are shown.
I Have two models
User:
const dbUser = {
a_id: {
primaryKey: true,
autoIncrement: true,
type: Sequelize.BIGINT,
},
a_date_created: {
type: Sequelize.DATE,
allowNull: false,
},
a_first_name: {
type: Sequelize.STRING,
allowNull: false,
},
a_last_name: {
type: Sequelize.STRING,
allowNull: false,
},
a_email: {
type: Sequelize.TEXT,
unique: true,
allowNull: false,
},
a_password: {
type: Sequelize.STRING,
allowNull: false,
},
a_birthday: {
type: Sequelize.DATE,
allowNull: false,
},
a_is_active: {
type: Sequelize.BOOLEAN,
allowNull: false,
},
};
User.init(dbUser, {
sequelize: db,
modelName: 't_user',
timestamps: false,
tableName: 't_users',
});
User.associate = (models) => {
console.log('ASSOCIADO')
User.belongsToMany(models.Role, {
through: { model: UserRole, unique: false },
as: 'roles',
foreignKey: 'a_user',
otherKey: 'a_role',
});
};
and Role:
const dbRole = {
a_id: {
primaryKey: true,
autoIncrement: true,
type: Sequelize.BIGINT,
},
a_role: {
type: Sequelize.STRING,
allowNull: false,
},
};
Role.init(dbRole, {
sequelize: db,
modelName: 't_role',
timestamps: false,
tableName: 't_roles',
});
Role.associate = (models) => {
Role.belongsToMany(models.User, {
through: { model: UserRole, unique: false },
as: 'UserOfRoles',
foreignKey: 'a_role',
otherKey: 'a_user',
});
};
As you can see I'm associating them using another model, UserRole:
const dbUserRole = {
a_id: {
primaryKey: true,
autoIncrement: true,
type: Sequelize.BIGINT,
},
a_role: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: false,
references: {
model: Role,
key: 'a_id',
},
onDelete: 'CASCADE',
},
a_user: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: User,
key: 'a_id',
},
onDelete: 'CASCADE',
},
};
UserRole.init(dbUserRole, {
sequelize: db,
modelName: 't_user_role',
timestamps: false,
tableName: 't_user_role',
});
UserRole.associate = (models) => {
UserRole.belongsTo(models.User, { targetKey: 'a_id', foreignKey: 'a_user' });
UserRole.belongsTo(models.Role, { targetKey: 'a_id', foreignKey: 'a_role' });
};
To create a user with a role (admin) I do like the code below:
onst createAdmin = async (body) => {
try {
const userResult = await createUser(body);
if (userResult.error) {
return {
ok: false,
error: userResult.error,
};
}
const isAdmin = await UserRole.create({
a_role: 1,
a_user: userResult.a_user_id,
});
return {
ok: true,
};
} catch (error) {
return {
ok: false,
error,
};
}
Seems to be working fine, because the user are being created, and the association using the "t_user_role" too, because the data is also being created at the table.
As I sad at the problem resume, my trouble is when I'm trying to do a findOne or findAll.
For example, when I try the code below, I catch all the informations from the user but in the answer there aren't any data of "t_roles" associated to this user.
const { body } = req;
try {
const result = await User.findOne({
where: {
a_id: 1,
},
include: [
{
association: 'roles',
attributes: ['a_role'],
through: {
attributes: [],
},
},
],
});
console.log('====================================');
console.log(JSON.stringify(result, null, 2));
console.log('====================================');
} catch (error) {
console.error(error);
}
If I use raw: true inside the findOne for example, the informations of roles are shown, so I presume that the association is correct.
I really appreciate any help to find what I'm missing here.
Thanks
Well, after days working on and trying different ways to solve this problem, a friend of mine just helped me starting again the entire project, following the documentation of Sequelize and the exact structure we did before, but a little bit more simple, and surprisingly ... worked. So I suppose that was something with migrations ore models or whatever, but we can't really say.

sequelize self association error Aliased associations must have unique aliases

I'm using sequelize 4.32 and I was trying to write a self-association in one of the tables, I'm not sure if there is something else that I need to do to solve this relation, my goal is to get all the records in my table and include all the records associated with each one
this is the error that I'm getting in the console:
You have used the alias adjucent_stands in two separate associations. Aliased associations must have unique aliases
below you'll find my model:
module.exports = (sequelize, DataTypes) => {
const stands = sequelize.define('stands', {
id: {
type: DataTypes.INTEGER,
unique: true,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
name: {
type: DataTypes.STRING,
field: 'name',
unique: {
args: true,
msg: 'Name already exists ',
},
},
location: {
type: DataTypes.JSON,
field: 'location',
},
remote: {
type: DataTypes.BOOLEAN,
field: 'remote',
defaultValue: false,
},
created_at: {
type: DataTypes.DATE(3),
defaultValue: sequelize.literal('CURRENT_TIMESTAMP(3)'),
},
updated_at: {
type: DataTypes.DATE(3),
defaultValue: sequelize.literal('CURRENT_TIMESTAMP(3)'),
},
}, { freezeTableName: true, timestamps: true, underscored: true });
stands.associate = (models) => {
stands.hasMany(stands, { as: 'adjucent_stands' });
};
return stands;
};

Node js and Sequelize insert to database

In my project I am using MySQL database and Sequelize Js,
I have two models created:
Post code model:
module.exports = function(sequelize, Sequelize) {
var Post_code = sequelize.define('post_code', {
id: {
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER(11)
},
code: {
type: Sequelize.STRING(16),
allowNull: false,
unique: true
},
city: {
type: Sequelize.STRING(45),
allowNull: false
}
},{
freezeTableName: true,
underscored: true
});
Post_code.associate = function(models) {
models.post_code.hasMany(models.building);
};
return Post_code;
}
Building model:
module.exports = function(sequelize, Sequelize) {
var Building = sequelize.define('building', {
id: {
autoIncrement: true,
primaryKey: true,
allowNull: false,
type: Sequelize.INTEGER(11)
},
name: {
type: Sequelize.STRING(100),
allowNull: false
},
number: {
type: Sequelize.INTEGER(11),
allowNull: false
},
address: {
type: Sequelize.STRING(255),
allowNull: false
},
latitude: {
type: Sequelize.DECIMAL(9,6),
allowNull: false
},
longitude: {
type: Sequelize.DECIMAL(9,6),
allowNull: false
}
},{
freezeTableName: true,
underscored: true
});
Building.associate = function (models) {
models.building.belongsTo(models.post_code, {
foreignKey: {
allowNull: false
}
});
models.building.hasMany(models.flat);
};
return Building;
};
They are in relation one to many, it follows that Post code has many Buildings:
I want to add new building to database, when POST request is send to this route:
"/post-codes/:post_code_id/buildings"
I have access to post_code_id but I don't know how to correctly associate post_code model with building.
I was trying to do something like this:
models.post_code.findById(req.params.post_code_id)
.then(function(postCode){
postCode.setBuilding(
models.building.create({
}).then(function(building){});
);
});
But without result. I would be grateful if someone could explain how to make inserts correctly.
I haven't heard about the include option on create, but you can do something like this:
db.post_code.find({
where: {
id: req.params.post_code_id
}
}).then(post_code => {
if (!post_code) {
return res.status(404).send({
message: 'No post_code with that identifier has been found'
});
} else {
//create here your building
}
}).catch(err => {
return res.jsonp(err)
});
I don't know if this is the best way to do it, but it verify first if the post_code exists.

Sequelize many to many relationship

I need some help to get my head around the sequelize many to many relationship.
I have to models, clients and services (many to many). Now I would like to load all services offered by one client (via client id). How does the condition with include have to look like to achieve this?
Service:
var Service = sequelize.define('service', {
title: {
type: DataTypes.STRING(200),
allowNull: false
}
},{
paranoid: true,
underscored: true,
classMethods: {
associate:function(models){
Service.belongsToMany(models.client, { through: 'client_services' });
Service.hasMany(models.rule, { foreignKey: 'service_id' })
}
}
});
Client:
var Client = sequelize.define('client', {
title: {
type: DataTypes.STRING(200),
allowNull: false
},
company: {
type: DataTypes.STRING(200),
allowNull: false
},
vendor: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
consumer: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
},
address_id: {
type: DataTypes.INTEGER,
allowNull: true
}
},{
paranoid: true,
underscored: true,
classMethods: {
associate:function(models){
Client.hasMany(models.service, { through: 'client_services'});
}
}
});
In my Controller (doesn't work err: [Error: client (client) is not associated to service!]):
var condition = {
where: { 'client.id': req.user.client_id },
include: [{ model: Client, as: 'client' }]
}
Service.findAll(condition)
.then(responseWithResult(res))
Ok the where clause needs to be in the include with the model:
var condition = {
include: [{ model: Client, where: { 'id': req.user.client_id } }]
}
Service.findAll(condition)
.then(responseWithResult(res))

Resources