Join table on Sequelize CLI - mysql

I am creating an application, and I would like to have more information
about creating a join table.
I have a table media:
var Media = sequelize.define('Media', {
m_title: DataTypes.STRING(50),
m_duration: DataTypes.INTEGER,
m_year: DataTypes.DATEONLY,
m_country: DataTypes.STRING(50),
m_season: DataTypes.INTEGER,
m_order: DataTypes.INTEGER,
m_fk_id_type_media: DataTypes.INTEGER
},
And Celebrity:
var Celebrity = sequelize.define('Celebrity', {
c_lastname: DataTypes.STRING(50),
c_firstname: DataTypes.STRING(50),
c_birth_date: DataTypes.DATEONLY,
c_nationality: DataTypes.STRING(50)
},
Do I have to create the model myself (media_has_celebrities)? Because I tried to add
Media.associate = function (models) {
models.Media.belongsToMany(models.Celebrity, {through: 'MediaHasCelebrity'});
};
and
Celebrity.associate = function (models) {
models.Celebrity.belongsToMany(models.Media, {through: 'MediaHasCelebrity'});
};
but it does not create the model of join as indicated in the documentation?
Do you have to define foreign keys before?
The goal would be to do something like Media.addCelebrity ...
Sorry for my english !
Thank you

Related

Custom data type not getting assigned values for record creation

I wrote a custom data type by extending Abstract, like in the example listed for the following link: https://sequelize.org/v5/manual/data-types.html. Everything appears to work until I try to save foreign keys, in this case it continues to generate a new UUID instead of getting the foreign key passed in the object to the create method.
How should I accommodate foreign keys in this case? Which method call should I overload to handle a getter situation where I need to pass in values?
The code below outlines the extended class I am using
class BinToUUID extends ABSTRACT {
toString(options) {
return this.toSql(options);
}
toSql() {
return 'BINARY(16)';
}
validate(value) {
console.log(value);
}
_stringify(value, options) {
return `UUID_TO_BIN(${options.escape(value)})`;
}
_bindParam(value, options) {
return `UUID_TO_BIN('${options.escape(value)}')`;
}
parse(value) {
return uuidv1.parse(value);
}
_sanitize(value) {
if (value instanceof Buffer) {
const str = value.toString('hex');
return [
str.slice(0, 8),
str.slice(8, 12),
str.slice(12, 16),
str.slice(16, 20),
str.slice(20, 32),
].join('-');
} else {
const uuid = uuidv1();
return uuid;
}
return value;
}
}
Here is a sample of the model I am using it in:
sequelize.define("room", {
roomId: {
primaryKey: true,
allowNull: false,
type: Sequelize.BinToUUID,
defaultValue: Sequelize.BinToUUID,
validate: {
notNull: true
}
},
name: {
type: DataTypes.STRING(10)
},
userId: {
type: Sequelize.BinToUUID,
references: {
model: 'user',
key: 'userId',
}
},
groupId: {
type: Sequelize.BinToUUID,
references: {
model: 'group',
key: 'groupId',
}
},
createdAt: {
type: DataTypes.DATE
},
updatedAt: {
type: DataTypes.DATE
}
}, {
freezeTableName: true
});
Here are the associations for the tables:
db.Room.User = db.Room.hasOne(db.User, {
foreignKey: ‘roomId'
});
db.Room.Group = db.Room.hasOne(db.Group, {
foreignKey: ‘groupId'
});
This is the create method begin called to save the room:
Room.create(room, {
include: [{
association: Room.User
}, {
association: Room.Group
}]
});
I am just trying to save the foreign keys to this table. When I look into the database tables for User and Group, the values do not match the uuid primary key values. It seems like the BinToUUID datatype is overwriting the UUID that is getting passed to create method.
For one-to-one (primary/foreign key relationship), check out belongsTo / HasOne associations
belongsTo
https://sequelize.org/v5/class/lib/model.js~Model.html#static-method-belongsTo
which has the following example:
Profile.belongsTo(User) // This will add userId to the profile table
HasOne
https://sequelize.org/v5/class/lib/associations/has-one.js~HasOne.html:
This is almost the same as belongsTo with one exception - The foreign key will be defined on the target model.
See this tutorial for examples: https://sequelize.org/v4/manual/tutorial/associations.html
This (unofficial?) article shows how foreign key is being handled: https://medium.com/#edtimmer/sequelize-associations-basics-bde90c0deeaa

How do I join two tables using Sequelize?

I have 3 tables: Books Authors and AuthorBooks. The last one to solve many-to-many. So far i managed to get the tables to work and display as i expected to. Now i want to display a table that shows data from both tables: Books and Authors. BooksAuthors stores only the IDs of the other tables.
I want something like this to work with sequelize:
SELECT ab.idA, ab.idB, a.firstName, a.lastName, b.title, b.description
from authorbooks ab
left join authors a on ab.idA=a.idA
left join books b on ab.idB=b.idB;
This is how i tried to define the relationship. I think I'm missing something here.
db.models.Books = require("../models/books")(sequelize);
db.models.Authors = require("../models/authors")(sequelize);
db.models.AuthorBooks = require("../models/authorbooks")(sequelize);
db.models.Authors.belongsToMany(db.models.Books,{
through: db.models.AuthorBooks,
foreignKey:{ name: 'idA',
allowNull:true
}
})
db.models.Books.belongsToMany(db.models.Authors,{
through: db.models.AuthorBooks,
foreignKey:{ name: 'idB',
allowNull: true
}
});
module.exports = db;
This is how i defined the tables. BOOKS:
module.exports = (sequelize) => {
class Books extends Sequelize.Model {}
Books = sequelize.define('Books', {
idB:{
type: Sequelize.INTEGER,
allowNull: false,
primaryKey : true
},
title: {
type: Sequelize.STRING
},
description:{
type: Sequelize.STRING(6000)
},
});
return Books;
};
AUTHORS:
module.exports = (sequelize) => {
class Authors extends Sequelize.Model {}
Authors = sequelize.define('Authors', {
idA:{
type: Sequelize.INTEGER,
allowNull: false,
primaryKey : true
},
firstName: {
type: Sequelize.STRING
},
lastName:{
type: Sequelize.STRING
},
});
return Authors;
};
AUTHORBOOKS:
module.exports = (sequelize) => {
class AuthorBooks extends Sequelize.Model {}
AuthorBooks.init({
id: {
type: Sequelize.INTEGER,
primaryKey:true,
autoIncrement: true
}
},{sequelize});
return AuthorBooks;
}
I think you can use the "include" option to achieve this function.
For example, you can use the code like
Book.findAll({
include:[{model:Author}]
})
This option will add an "Author" field including the author information to the result.
What you need is a through options in the query, it combines all the authors linked to a particular book, empty attributes will avoid adding any attribute from mapping table which in your case is 'AuthorBooks'.
Book.findAll({
include:[{
model:Author,
through: {
attributes: []
}
}]
})

Populate data without association id in Sequelize

I have 4 tables, user, profile , speciality , user_specialities. I am using sequelize. My user model is-->
const User = sequelize.define('user', {
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
IsDeleted: {
type:Sequelize.BOOLEAN,
},
status: {
type:Sequelize.BOOLEAN,
},
});
My profiles model-->
const Profile= sequelize.define('profile', {
profile_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
first_name: {
type:Sequelize.STRING,
},
last_name: {
type:Sequelize.STRING,
},
},
{
indexes: [
{
unique: true,
fields: ['user_id']
}
]
});
Chef_Info.belongsTo(User, {foreignKey: 'user_id'});
Speciality table-->
const Profile= sequelize.define('speciality', {
speciality_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
title: {
type:Sequelize.STRING,
},
}
user_specialities hold the many to many relation between user and speciality. I have used belongsToMany association like this -
Speciality.belongsToMany(User, {through: 'user_speciality',foreignKey: 'speciality_id', otherKey: 'user_id'});
User.belongsToMany(Speciality, {through: 'user_speciality',foreignKey: 'user_id', otherKey: 'speciality_id'});
Now, I want to get data where I have user_id, first_name, last_name also the specialities. For example-->
first_name | last_name | specialities
Khabib | Nurmagamedov | Athlete, Wrestler
Tony | Ferguson | Athlete, Striker
I tried in MySql, where query should be-->
SELECT profiles.first_name,profiles.last_name,GROUP_CONCAT(specialities.title)
FROM (
users
LEFT JOIN profiles
ON users.user_id = profiles.user_id
LEFT JOIN user_specialities
ON users.user_id = user_specialities.user_id
LEFT JOIN specialities
ON chef_specialities.speciality_id = specialities.speciality_id
)
WHERE(
AND users.IsDeleted = false
And users.status = true
)
GROUP BY users.user_id;
But I am struggling to convert this to sequelize. I have come this far-->
UserSpecialities.findAll({
attributes:[sequelize.fn('GROUP_CONCAT', sequelize.col('title')), 'speciality_id'],
include: [
{
model: User,
attributes: [],
where:{
IsRemoved: false,
status: true,
},
},
{ model: Speciality,
attributes: [],
},
],
group:['user_id']
Its provides the specialities userwise-->
[
{
"speciality_id": "Athlete, Wrestler",
},
{
"speciality_id": "Athlete, Striker",
}
]
But I am failing to populate the profile data here.If I try to include profile, it shows no association error, or I try to add a hasManyin user model it throws error again. What should I do in this situation?
Thanks In Advance.
Because Profile is associated with Users but not UserSpecialties it would make more sense to accomplish this with two queries.
Grab your user specialties like you did above, then take those userId's and grab their associated profiles (should be something like User.getProfile depending on how your associations are set up).
Right now you have your UserSpecialties attributes set up to only return the specialty_id and title, if you add in userId to those attributes then you can use those userId's to query your profile table. A then promise works well for something like this so something like:
UserSpecialties.findAll({...}).then((userSpecialties) => {
User.getProfile({where: {userId: userSpecialties.userId}, attributes: {'first_name', 'last_name'})
});
Depending on what you are specifically trying to do with the information you will probably have to tweak it a bit, but the general idea of adding in userId as an attribute and then querying your User table for profiles using the userId from the UserSpecialties query result should work fine for the data you want.
Though looking at your models, it might make sense to just have profile and user be one table, where User has first_name, last_name, userId, and status. Since Profile doesn't have a lot of other stuff to it, and then if you need specific things for the userProfile you can use scopes on your user model.

Sails js sort by populated field

I need to sort data from a MySQL database on related table row.
Suppose we have two models:
ModelOne:
module.exports = {
tableName: 'modelOne',
attributes: {
// some atributes.........
modelTwo{
model: 'ModelTwo',
columnName: 'model_two_id'
}
}
}
ModelTwo:
module.exports = {
tableName: 'modelTwo',
attributes: {
// some atributes.........
model_two_id: {
type: 'integer',
autoIncrement: true,
primaryKey: true
},
name: 'string'
}
}
I would like to do something like:
ModelOne
.find(find_criteria)
.populateAll()
.paginate({page: page, limit: limit})
.sort('modelTwo.name')
.then(...)
Is there possibility to do this or I need to write an SQL query without using Waterline functions?
This is how I do it...
ModelOne
.find(find_criteria)
.populate('modelTwo', {sort: 'name ASC'})
.paginate({page: page, limit: limit})
.then(...)
As you can see, you can pass {sort} object to populate method.
No. Deep-population-sorting is on future list https://github.com/balderdashy/waterline/issues/266

Populate model of the model with sails js

I'm trying to populate model of the model with sails unfortunally it doesn't work.
I have 3 models
/**
Conversation.js
**/
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'conversation',
attributes: {
idConversation:{
columnName:'IDCONVERSATION',
primaryKey:true,
autoIncrement:true,
unique:true,
type:'integer',
index:true
},
dateStartConversation:{
columnName:'DATEDEBUT',
type:'date',
index:true
},
user1:{
columnName:'IDUSER1',
model:'user',
notNull:true
},
user2:{
columnName:'IDUSER2',
model:'user',
notNull:true
},
article:
{
model:'article',
columnName:'IDARTICLE',
notNull:true
}
}
};
/**
Article.js
**/
module.exports = {
autoPK: false,
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'article',
attributes: {
idArticle:{
type:'integer',
unique:true,
columnName:'IDARTICLE',
autoIncrement:true,
primaryKey:true
},
title:{
type:'string',
required:true,
columnName:'TITRE',
index:true,
notNull:true
},
utilisateur:{
model:'utilisateur',
columnName:'IDUTILISATEUR',
required:true,
notNull:true,
dominant:true
},
images:{
collection:'image',
via:'article'
},
conversation:{
collection:'conversation',
via:'article'
}
}
};
/**
Image.js
**/
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'image',
attributes: {
idImage:{
columnName:'IDIMAGE',
primaryKey:true,
autoIncrement:true,
unique:true,
type:'integer'
},
pathImage:{
columnName:'PATHIMAGE',
required:true,
type:'string',
notNull:true
},
article:{
model:'article',
columnName:'IDARTICLE',
notNull:true,
dominant:true
}
}
};
As you can see in my model, an conversation its between Two user, about one article, and those article cas have one or many Images.
So I want to get all conversations of one user and I able to populate with article but I'm not able to populate article with Image below how I proceed
Conversation.find().populate('article').populate('user1').populate('user2').where({
or : [
{ user1: iduser },
{ user2: iduser }
]})
.then(function( conversations) {
var i=0;
conversations.forEach(function(element,index){
i++;
console.log("article "+index+" "+JSON.stringify(element.article));
Article.findOne({
idArticle:element.article.idArticle
}).populate('images').then(function(newArticle){
//I try to set article with the newArticle but it don't work
element.article=newArticle;
})
if(i==conversations.length){
res.json({
hasConversation:true,
conversation:conversations
});
}
});
})
Because deep populate is not possible using sails, I try to use a loop to populate each article with associate Images and set it in conversation, But article is never set in conversation.
How can I fix it ?
Judging by the if(i==conversations.length) at the end, you seem to have an inkling that you need to write asynchronous code. But you're iterating i inside of the synchronous forEach loop, so your response is happening before any of the database queries even run. Move the i++ and the if inside of the callback for Article.findOne:
Conversation.find().populate('article').populate('user1').populate('user2').where({
or : [
{ user1: iduser },
{ user2: iduser }
]})
.then(function( conversations) {
var i=0;
conversations.forEach(function(element,index){
console.log("article "+index+" "+JSON.stringify(element.article));
Article.findOne({
idArticle:element.article.idArticle
}).populate('images').then(function(newArticle){
// Associate the article with the conversation,
// calling `toObject` on it first
element.article= newArticle.toObject();
// Done processing this conversation
i++;
// If we're done processing ALL of the conversations, send the response
if(i==conversations.length){
res.json({
hasConversation:true,
conversation:conversations
});
}
})
});
})
You'll also need to call toObject on the newArticle instance before assigning it to the conversation, because it contains getters and setters on the images property which behave unexpectedly when copied.
I'd also recommend refactoring this to use async.each, which will make it more readable.
Until this is resolved (https://github.com/balderdashy/sails-mongo/issues/108), you can use this function that I developed to solve this: https://gist.github.com/dinana/52453ecb00d469bb7f12