Sequelize self reference can´t make a join - node.js

I'm trying to make a many to many table with matches of different teams and it works, but when I tried to make a join I got this error:
Unhandled rejection Error: team is not associated to match!
Here is my code:
var Sequelize = require('sequelize');
var sequelize = new Sequelize('cricket', 'root', '');
var Team = sequelize.define('team', {
name: Sequelize.STRING,
});
var Match = sequelize.define('match', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
scoreHome: Sequelize.INTEGER,
scoreAway: Sequelize.INTEGER,
});
Team.belongsToMany(Team, {as: 'Home', through: Match, foreignKey: 'homeTeamId'});
Team.belongsToMany(Team, {as: 'Away', through: Match, foreignKey: 'awayTeamId'});
sequelize.sync().then(function () {
Match.findAll({
include: [ Team ]
}).then(function (matches) {
console.log(matches)
});
});

You also need to provide the reverse relationship for Match.
In your current setup Team is associated with Team, not with Match. Match model is your through model.
For example valid association for Match would be :
var HomeTeam = Match.belongsTo( Team, { as: 'HomeTeam', foreignKey: 'homeTeamId' });
var AwayTeam = Match.belongsTo( Team, { as: 'AwayTeam', foreignKey: 'awayTeamId' })
sequelize.sync().then(function () {
Match.findAll({
include: [ HomeTeam, AwayTeam ]
}).then(function (matches) {
console.log(matches)
});
});
// Makes the following query
// SELECT `match`.`id`, `match`.`scoreHome`, `match`.`scoreAway`, `match`.`createdAt`, `match`.`updatedAt`, `match`.`homeTeamId`, `match`.`awayTeamId`, `HomeTeam`.`id`
// AS `HomeTeam.id`, `HomeTeam`.`name` AS `HomeTeam.name`, `HomeTeam`.`createdAt`
// AS `HomeTeam.createdAt`, `HomeTeam`.`updatedAt` AS `HomeTeam.updatedAt`, `AwayTeam`.`id` AS `AwayTeam.id`, `AwayTeam`.`name` AS `AwayTeam.name`, `AwayTeam`.`createdAt` AS `AwayTeam.createdAt`, `AwayTeam`.`updatedAt` AS `AwayTeam.updatedAt` FROM `matches` AS `match` LEFT OUTER JOIN `teams` AS `HomeTeam` ON `match`.`homeTeamId` = `HomeTeam`.`id` LEFT OUTER JOIN `teams` AS `AwayTeam` ON `match`.`awayTeamId` = `AwayTeam`.`id`

Related

Node.JS TypeError: result.setPet is not a function

So, I'm new to Node.JS, and this project is a simple petshop, with pets table, services offered, and appointments (when a certain service is applied to a certain pet in a certain date in the future).
Appointments have a petId and serviceId, each appointment must have only one of those, but pets and services can be referenced in multiple appointments.
This project was originally made with pure MySql, with separate repositories to handle queries, later I changed it to use sequelize ORM.
I've made the basics crud operations using the rest architecture, everything ok with the services and pets methods (post, get, patch, delete), but the problem began when trying to make post method with appointments, since appointments have serviceId and petId.
index.js
const customExpress = require('./config/customExpress');
const connection = require('./infrastructure/database/connection');
connection.sync()
.then(() => {
console.log('Successfully connected to database')
const app = customExpress()
app.listen(3000, () => console.log('Server running at port 3000'))
})
.catch(erro => console.log('Something went wrong while trying to synchronize to database'))
connection.js
here is where the associations between tables and models are made, I think the error might be here, but can't figure out where
const Sequelize = require('sequelize')
const config = require('config')
const { applyExtraSetup } = require('../../models/extraSetup')
const sequelize = new Sequelize(
config.get('mysql.database'),
config.get('mysql.user'),
config.get('mysql.password'),
{
host:config.get('mysql.host'),
dialect:config.get('mysql.dialect')
}
)
const modelDefiners = [
require('../../models/appointments'),
require('../../models/pets'),
require('../../models/services'),
]
// it gets the models definitions and pass the above instance of sequelize to them
for (const modelDefiner of modelDefiners){
modelDefiner(sequelize)
}
// and then make their relations with extra setup
applyExtraSetup(sequelize)
.then(module.exports = sequelize)
the extraSetup.js, makes the relations
// it exports the things the sequelize instance must do
async function applyExtraSetup(sequelize){
const { appointments, services, pets } = sequelize.models
await appointments.belongsTo(services)
await appointments.belongsTo(pets)
await services.hasMany(appointments)
await pets.hasMany(appointments)
}
module.exports = { applyExtraSetup }
and this is appointment.js, a controller that handles the /appointments route and call the sequelize methods
app.post('/appointment', async (req, res) => {
const data = req.body
const pet = await Pets.findAll({ where : { name : req.body.petId }})
const service = await Service.findAll({ where : { name : req.body.serviceId }})
const appointment = Appointment.create(data)
.then(result => {
result.setPet(pet)
res.end()
})
reading sequelize documentation, I saw that with this type of association, you get magic methods to handle data from the related tables, which you call from an instance of a model (setPet, getPet, and so on).
Can anyone help me?
edit, models definitions (they're together since I'm refactoring the code, but originally each one was in a separate module):
exports.appointment = (sequelize) => {
const now = new Date()
const Appointment = sequelize.define('appointments', {
client: {
type: DataTypes.STRING,
allowNull: false
},
creationDate: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: now
},
date: {
type: DataTypes.DATE,
allowNull: false,
validate: {
isBefore: now
}
},
status: {
type: DataTypes.STRING,
allowNull: false
},
observations: {
type: DataTypes.STRING
}
})
return Appointment
}
exports.pets = (sequelize) => {
const Pets = sequelize.define('pets', {
name : {
type: DataTypes.STRING,
allowNull: false
},
image: {
type: DataTypes.STRING,
allowNull: false
},
})
return Pets
}
exports.service = (sequelize) => {
const Service = sequelize.define('services', {
name: {
type: DataTypes.STRING,
allowNull: false
},
price: {
type: DataTypes.FLOAT,
allowNull: false
}
})
return Service
}

NodeJS & Sequelize: showing all the conversation the current user has

i am currently trying to implement my backend from my all conversations screen. i am finding all the conversations and including all the messages associated with each conversation.
In my react native frontend, in my all conversations screen, i am showing all the conversations as a flatlist and showing the last message sent between the 2 users by picking it out from the list of messages.
Should i continue using this approach or will it be better in terms of performance to include a link to the last message in Conversation table and update it every time a new message is sent in the conversation? If so, how do i change my code to do so?
Conversation Model:
const Conversation = db.define("Conversation", {},
{
paranoid: true,
timestamps: true,
deletedAt: "deletedAt",
}
);
module.exports = Conversation;
User.hasMany(Conversation, {
foreignKey: 'user1'
});
User.hasMany(Conversation, {
foreignKey: 'user2'
});
Conversation.belongsTo(User, { as: 'Creator', foreignKey: 'user1', allowNull: false })
Conversation.belongsTo(User, { as: 'Recipient', foreignKey: 'user2', allowNull: false })
Message Model:
const Message = db.define("Message", {
senderId: {
type: Sequelize.STRING,
},
receiverId: {
type: Sequelize.STRING,
},
message: {
type: Sequelize.STRING,
},
conversationId:{
type: Sequelize.INTEGER,
}
});
module.exports = Message;
User.hasMany(Message, {
foreignKey: 'senderId',
});
User.hasMany(Message, {
foreignKey: 'receiverId',
});
Message.associate = (models) => {
Message.belongsTo(models.Conversations,{
foreignKey: "conversationId",
}
);
};
Conversation.hasMany(Message,{
foreignKey: "conversationId",
}
);
Message.belongsTo(User, {
as:"Sender",
foreignKey: "senderId",
allowNull: false
});
Message.belongsTo(User, {
as:"Receiver",
foreignKey: "receiverId",
allowNull: false
});
Get All Conversations Query:
router.get('/', auth, async (req, res) => {
const conversations = await Conversation.findAll({
order: [[Message,"createdAt", "DESC"]],
include: [
{
model: Message,
where: {
[Op.or]: [
{ senderId: req.user.id },
{ receiverId: req.user.id },
],
},
required: true, // RIGHT JOIN
}]
})
if (!conversations) return res.status(404).send();
res.status(200).send();
});
You can try to limit included messages in each conversation to the last message only
const conversations = await Conversation.findAll({
order: [[Message,"createdAt", "DESC"]],
include: [
{
model: Message,
order: [["createdAt", "DESC"]],
limit: 1,
where: {
[Op.or]: [
{ senderId: req.user.id },
{ receiverId: req.user.id },
],
},
required: true, // RIGHT JOIN
}]
})
If the above query doesn't work you can always try to store the last message id in a conversation but this can lead to inconsistencies if you store a message and forget to update a link to the last message in a conversation or you delete the last message and again forget to update the link in a conversation.
You can cache the conversations and messages into the local database
using sqlite of the mobile device.
To do so, you don't have to query whole conversation for every login.
Meta data table can be even helpful to relieve the server stress
I suggest you to use sqlite in RN and add meta table in remote database.
Pattern.
Load Conversations from remote database with the count of messages when logining in.
( If meta data table exists, this part will be faster and lighter )
When displaying the conversions, use the lastest data in local database.
Comparing count of messages, if count is different, you can assume that user in this device didn't check the lastest message. So Render badge component to notify user that he or she didn't read it yet.
When user enter the conversation, fetch messages and store it into local database of the device.
Socket io part
Brief concept is like this.
ref. https://socket.io/docs/v3/server-api/#socket-on-eventName-callback
Client
const [ conversations, setConversations ] = useState([])
const [ syncState, setSyncState ] = useState(false)
const [ socket, setSocket ] = useState(null)
useEffect(() =>{
...
// you might wanna set conversation from local database here
...
io.on('connection', soc => {
soc.join("your room")
...
socket.on('synced', data =>{
//mapNewDataToConversationState
...
setSynced(true)
})
setSocket(soc)
})
...
}, [ ... ])
useEffect(() =>{//when loaded and socked established
if(!socket) return
if(syncState) return
socket.emit('sync', { data : conversation })
}, [ socket ])
Server
//let say you already set app.io as socket io connection
//add a event listener for client sync
socket.on('sync', async conversation =>{
const responseData = {}
for(const c of conversation){
const lastestMsg = c.Message[c.Message.length - 1]
const messages = await Message.findAll({
where : {
...//Maybe greater than createdAt, any cursor attribute in here
}
})
responseData[c.id] = messages
}
socket.emit('synced', responseData )
})

How to return the result set of sequelize query in camel case fashion?

I have defined a table schema in the database in an underscored fashion but I want to return the result set API response in camel case fashion. I know I can process the underscored object returned by sequelize and convert it into camelcase fashion. Is there any functionality to return the response of a query in camelcase fashion in sequelize itself?
To archieve this you need to use field when defining your model.
module.exports = (sequelize, DataTypes) => {
const yourTable = sequelize.define('yourTable', { // table name use it for Sequelize
camelCase: { //camelCase name that you'll use with sequelize.
field: 'under_score', //underscore name on yor database.
type: DataTypes.STRING
},
keyId: { //need to the same with association
field: 'key_id',
type: DataTypes.INTEGER
},
}, {
tableName: 'your_table', // then name of the table on the db
underscored: true,
});
yourTable.associate = (models) => {
yourTable.belongsTo(models.otherTable, {
as: 'Something',
foreignKey: 'key_id', //put attention here and keyId above.
onDelete: 'cascade'
});
}
}

sequelize selecting columns that are not mentioned in the model

I have 2 tables that are in a 1 to many relationship (course has many subjects), and before doing a delete operation on subject I want to check if it is associated with any course, so here are my models:
Course:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Course = sequelize.define('Course', {
name: DataTypes.STRING,
desc: DataTypes.TEXT
}, {});
Course.associate = function(models) {
Course.belongsToMany(models.Subject, {
through: 'courseSubjects'
});
Course.hasMany(models.Batch)
};
return Course;
};
and subject:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Subject = sequelize.define('Subject', {
name: DataTypes.STRING,
desc: DataTypes.TEXT
}, {});
Subject.associate = function(models) {
Subject.belongsToMany(models.Module, {
through: 'subjectModules'
});
Subject.belongsToMany(models.Course, {
through: 'courseSubjects',
});
};
return Subject;
};
So this is the statement I am calling:
res.status(200).send(subject.getCourses())
and getting error:
Executing (default): SELECT Course.id, Course.name, Course.desc, Course.createdAt, Course.updatedAt, Course.courseId, courseSubjects.createdAt AS courseSubjects.createdAt, courseSubjects.updatedAt AS courseSubjects.updatedAt, courseSubjects.CourseId AS courseSubjects.CourseId, courseSubjects.SubjectId AS courseSubjects.SubjectId FROM Courses AS Course INNER JOIN courseSubjects AS courseSubjects ON Course.id = courseSubjects.CourseId AND courseSubjects.SubjectId = 34;
[0] Unhandled rejection SequelizeDatabaseError: Unknown column 'Course.courseId' in 'field list'
I do not understand why is it trying to select 'courseId'.. Please help me resolve this.
You need to add relation into course table as well, it will remove the 'courseId' column from select.

Try to create HABTM connection Sequelize.js

I'm trying to create a HABTM relationship with Sequelize but I can't get it done.... I still receive an error message:
return (tableName1.toLowerCase() < tableName2.toLowerCase()) ? (tableName1
^
TypeError: Cannot call method 'toLowerCase' of undefined
I have a User model, a Book model and an UserBooks model. And ofcourse my database contains a "users" table, "user_books" table and "books" table.
UserBooks model:
module.exports = function(schema, DataTypes) {
var UserBooks = schema.define('UserBooks', {
}, {
tableName: 'user_books', // this will define the table's name
timestamps: false // this will deactivate the timestamp columns
});
UserBooks.sync();
return UserBooks;
};
User model:
module.exports = function(schema, DataTypes) {
var User = schema.define('User', {
keywords: DataTypes.STRING
}, {
tableName: 'users', // this will define the table's name
timestamps: false ,// this will deactivate the timestamp columns
syncOnAssociation:false
});
User.hasMany(Book, { foreignKey: 'user_id', through: UserBooks });
User.sync();
return User;
};
Book model:
module.exports = function(schema, DataTypes) {
var Book = schema.define('Book', {
keywords: DataTypes.STRING
}, {
tableName: 'books', // this will define the table's name
timestamps: false ,// this will deactivate the timestamp columns
syncOnAssociation:false
});
Book.hasMany(User, { foreignKey: 'book_id', through: UserBooks });
Book.sync();
return Book;
};
In your User model you are trying to create an association with a model that is not defined in that scope. In User.js, you only have access to User, not Book or UserBooks which are undefined. Thats whats causing your error.
You can either create associations in the place where you import all your models into your app, or in the models file by importing the models you want to associate with (bevare of circular imports). Your user model could be changed to:
module.exports = function(schema, DataTypes) {
var Book = schema.import(__dirname + '/book');
var UserBooks = schema.import(__dirname + '/userbooks');
var User = schema.define('User', {
keywords: DataTypes.STRING
}, {
tableName: 'users', // this will define the table's name
timestamps: false ,// this will deactivate the timestamp columns
syncOnAssociation:false
});
User.hasMany(Book, { foreignKey: 'user_id', through: UserBooks });
Book.hasMany(User, { foreignKey: 'book_id', through: UserBooks });
return User;
};
For another example of how to do it, see http://sequelizejs.com/articles/express#minimal-express-app
Also, I've removed the call to User.sync from your code. Sync is an async call, while import is sync. This means that your are defining your model, starting to sync it to the DB, and then returning it, before you know that it has finished syncing. This means you could potentially be trying to work create instances with it before the table has been created. Instead, you should use sequelize.sync to sync all your models at once, and attach a callback to wait for the sync to finish (see the link I posted for a code example)

Resources