Bookshelf.js get pivot table attributes - mysql

I am trying to get attributes from my m-n relational mySql table using Bookshelf.js.
I have a table users: id, name
and tournaments: id, place
and a pivot table: user_id, tournament_id, teamname
Those are my models:
var User = Bookshelf.Model.extend({
tableName: 'users',
tournaments: function () {
return this.belongsToMany(Tournament);
}
});
var Users = Bookshelf.Collection.extend({
model: User
});
var Tournament = Bookshelf.Model.extend({
tableName: 'tournaments',
users: function () {
return this.belongsToMany(User);
}
});
var Tournaments = Bookshelf.Collection.extend({
model: Tournament
});
var Tournaments_Users = Bookshelf.Model.extend({
tableName: 'tournaments_users'
});
Now when I do
Tournaments.forge().fetch({withRelated: ['users']})
.then(function (collection) {
res.send(collection.toJSON());
})
I get
{
"id": 1,
"place": "Berlin",
"users": [
{
"id": 1,
"name": "Jim",
},
{
"id": 2,
"name": "Tom",
}, ...
}
What I want:
{
"id": 1,
"place": "Berlin",
"users": [
{
"id": 1,
"name": "Jim",
"teamname" : "Team A"
},
{
"id": 2,
"name": "Tom",
"teamname" : "Team B"
}, ...
}
Anyone knows how to do this using Bookshelf?

You may use .withPivot() method.
Use it like this :
users: function () {
return this.belongsToMany(User).withPivot(['teamname']);
}
In your return, you will get a field named _pivot_teamname. Just rename them to get it good.
Documentation : http://bookshelfjs.org/#Collection-instance-withPivot
As long as I know, there is no way to fetch the pivot fields with a custom name.

Related

getting the values from 3 tables in sequelize using nested includes and sequelize.col as single object

I am new to nodejs as well as sequelize and any kind of ORMs
I wish to get all the values from 3 tables linked together through belongsTo associations
3 tables :
item - [id, itemName, itemCategoryID]
itemCategory - [id, itemCategoryName]
itemRequirement - [id, itemID, quantity, requirementDate, requirementStatusID]
requirementStatus - [id, requirementStatusName]
this is my get api req for getting the item requirements
router.get("/", async (req, res) => {
const itemRequirements = await itemRequirement
.findAll({
include: [
{
model: item,
include: [
{
model: itemCategory,
attributes: [],
},
],
attributes: [
//gets error in this line
[Sequelize.col("itemCategory.itemCategoryName"),"itemCategoryName",],
//alternatively this line works fine
['itemCategoryID']
],
},
{ model: requirementStatus, attributes: [] },
],
attributes: [
"id",
"quantity",
"requiredBy",
[Sequelize.col("item.itemName"), "itemName"],
[
Sequelize.col("requirementStatus.requirementStatusName"),
"requirementStatusName",
],
],
})
.then((itemRequirements) => {
console.log(itemRequirements);
res.json(itemRequirements);
});
});
I get error when trying to do a sequelize.col but I am able to get the ID alone if I don't use the sequelize.col in the above code at the mentioned line
code: 'ER_BAD_FIELD_ERROR',
errno: 1054,
sqlState: '42S22',
sqlMessage: "Unknown column 'item.itemCategory.itemCategoryName' in 'field list'",
currently i am getting this if i directly get the id
[
{
"id": 1,
"quantity": 10,
"requiredBy": "2022-02-28T18:30:00.000Z",
"itemName": "vanilla essence",
"requirementStatusName": "pending",
"item": {
"itemCategoryID": 1
}
}
]
i wish to get this
[
{
"id": 1,
"quantity": 10,
"requiredBy": "2022-02-28T18:30:00.000Z",
"itemName": "vanilla essence",
"requirementStatusName": "pending",
"itemCategoryName":"someCategoryName"
}
]
You should use DB column name in Sequelize.col instead of its field counterpart in a model:
// let's say the model field is itemCategoryName and the column name in a table is item_category_name
Sequelize.col("itemCategory.item_category_name")
To query more than 2 tables using joins in sequelize we will have to use reference the table and column name correctly.
Instead of adding [Sequelize.col("itemCategory.itemCategoryName"),"itemCategoryName",] as an attribute to the referencing table and to get the response as a single json object without nesting we need to add this [Sequelize.col("item.itemCategory.itemCategoryName"),"itemCategoryName"] as the attribute to the table from which you are querying now
below is the edited code which returns json as expected
router.get("/", async (req, res) => {
const itemRequirements = await itemRequirement
.findAll({
include: [
{
model: item,
include: [
{model:itemCategory,attributes:[]},
{model:quantityType,attributes:[]}
],
attributes:[]
},
{ model: requirementStatus, attributes: [] },
],
attributes: [
"id",
"quantity",
"requiredBy",
[Sequelize.col("item.itemName"), "itemName"],
[
Sequelize.col("requirementStatus.requirementStatusName"),
"requirementStatusName",
],
//actual way of referencing the different tables to get an object without
//nesting
[Sequelize.col("item.itemCategory.itemCategoryName"),"itemCategoryName"],
[Sequelize.col("item.quantityType.quantityName"),"quantityTypeName"]
],
})
.then((itemRequirements) => {
console.log(JSON.stringify(itemRequirements,null,2));
res.json(itemRequirements);
});
});
module.exports = router;
output
[
{
"id": 4,
"quantity": 10,
"requiredBy": "2022-02-03T00:00:00.000Z",
"itemName": "choco",
"requirementStatusName": "pending",
"itemCategoryName": "Essence",
"quantityTypeName": "ml"
}
]

sequelize count associated table rows

Using sequelize and mySQL, I have two tables: User and Post.
Relation between two tables is M : N
db.User.belongsToMany(db.Post, { through: "Likes", as: "Liked" });
db.Post.belongsToMany(db.User, { through: "Likes", as: "Likers" });
What I want is getting post with whole likers id and count of whole likers.
I know how to get whole likers like this.
const post = await Post.findOne({
where: { id: postId },
attributes: ["id", "title", "imageUrl"],
include: [{
model: User,
as: "Likers",
attributes: ["id"],
through: { attributes: [] },
}]
})
// result
{
"id": 36,
"title": "test",
"imageUrl": "하늘이_1644886996449.jpg",
"Likers": [
{
"id": 13
},
{
"id": 16
}
]
}
And, I also know how to get count of whole likers.
const post = await Post.findOne({
where: { id: postId },
attributes: ["id", "title", "imageUrl"],
include: [{
model: User,
as: "Likers",
attributes: [[sequelize.fn("COUNT", "id"), "likersCount"]],
}]
})
// result
{
"id": 36,
"title": "test",
"imageUrl": "하늘이_1644886996449.jpg",
"Likers": [
{
"likersCount": 2
}
]
}
But, I don't know how to get both of them at once.
Check the result when I use both of them.
{
model: User,
as: "Likers",
attributes: ["id", [sequelize.fn("COUNT", "id"), "likersCount"]],
through: { attributes: [] },
}
// result
"Likers": [
{
"id": 13,
"likersCount": 2
}
]
It only shows 1 liker(id: 13)
It must show another liker(id: 16).
What is the problem?
It shows only one because COUNT is an aggregating function and it groups records to count them. So the only way to get both - use a subquery to count records in a junction table while getting records on the other end of M:N relationship.
const post = await Post.findOne({
where: { id: postId },
attributes: ["id", "title", "imageUrl",
// you probably need to correct the table and fields names
[Sequelize.literal('(SELECT COUNT(*) FROM Likes where Likes.postId=Post.id)'), 'LikeCount']],
include: [{
model: User,
as: "Likers",
attributes: ["id"],
through: { attributes: [] },
}]
})

Passing associated table attributes as main table attributes in sequelize

I have a query which is similar to the following.
const TODAY = new Date().setHours(0, 0, 0, 0);
const studentAttendances = await STUDENT_ATTENDANCES.findAll({
where: {
punch_in: { [Op.gt]: TODAY },
},
attributes: ['id', 'student_id', 'arrived_time'],
include: [
{
model: STUDENTS,
attributes: ['name'],
},
],
raw: true,
nest: true,
});
The current output given is an array of objects which look like the following.
{
"id": 1041,
"student_id": 16,
"arrived_time": "2019-05-29T08:29:41.000Z",
"student": {
"name": "Tom"
}
},
Instead of having a nested object as above how do i make the student name itself be an attribute of the main object ? Example as follows.
{
"id": 1041,
"student_id": 16,
"arrived_time": "2019-05-29T08:29:41.000Z",
"student": "Tom"
},
I hope to do this through sequelize without using any JS loops
Something like this should work, assuming your singular model name is "Student":
const studentAttendances = await STUDENT_ATTENDANCES.findAll({
where: {
punch_in: { [Op.gt]: TODAY },
},
attributes: [
[sequelize.col('Student.name'), 'studentName'], // will give you name as 'studentName'
'id', 'student_id', 'arrived_time'
],
include: [
{
model: STUDENTS,
attributes: [], // empty this out
},
]
});
I think you can handle it with pure javascript :
studentAttendances = studentAttendances.get({plain: true})
for(student in studentAttendances){
studentAttendances[student].student = studentAttendances[student].student.name
}

Sequelize orm using Association methods

Sequelize orm using Association methods
I'm working on a API using Node and Sequelize ORM. The database being used is MYSQL. I've used belongsTo() in below code.
Each user have favorite games. Get the favorite games for each user using user_Id.
I got a sequelize result and but excepting another.
this.userFavoriteGameModel = require("../entity/favorite_game")(this.database, this.Sequelize);
this.gameModel = require("../entity/game")(this.database, this.Sequelize);
//Fecth user favorite game list
favoriteGameList(name, cb) {
let self = this;
var user_Id = 5;
self.userFavoriteGameModel.belongsTo(self.gameModel, {
as: "game_detail",
foreignKey: 'game_Id'
});
self.userFavoriteGameModel.findAll({
attributes: [
'game_Id'
],
where: {
user_id: user_Id
},
include: [{
model: self.gameModel,
as: 'game_detail',
attributes: ["game_Title", "game_Subtitle", "icon", "game_Rating"]
}],
group: ['game_Id']
}).then(function (result) {
cb(result);
let obj1 = result;
let obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2));
}, function (err) {
console.log('An error occurred while creating the table:', err);
cb(err);
});
}
}
[
{
"dataObject": [
{
"game_Id": 57,
"game_detail": {
"game_Title": "battlefield1",
"game_Subtitle": "Limited edition",
"icon": "image.png",
"game_Rating": 2
}
},
{
"game_Id": 58,
"game_detail": {
"game_Title": "battlefield2",
"game_Subtitle": "Limited edition",
"icon": "image.png",
"game_Rating": 1
}
}
]
}
]
Excepting Result :
[
{
"dataObject": [
{
"game_Id": 57,
"game_Title": "battlefield1",
"game_Subtitle": "Limited edition",
"icon": "image.png",
"game_Rating": 2
},
{
"game_Id": 58,
"game_Title": "battlefield2",
"game_Subtitle": "Limited edition",
"icon": "image.png",
"game_Rating": 1
}
]
}
]
sequelize orm make nested json as result of hasMany belongsTo or other relations if you dont want it as final result you could get the sequelize result then make it manually like what you want follow this:
var finalResult={}
for(var key in object){
if(typeof object['key'] == 'object'){
var parent=object['key']
for(var childKey in parent){
finalresult['childKey']=parent['childKey']
}
}else{
finalresult['key']=object['key']
}
}

Sequelize findAll with association and foreign key

I'm using sequelize to model a mySql-database schema in my node-application. Let's say I have 3 table: Project, User and Role.
It's a "Many to Many" association between Project and User through "Project_User" where is defined the role of a user for a project.
Project Model :
var Project = sequelize.define('Project', {
name:{type: DataTypes.STRING, unique: true}
},
classMethods: {
associate: function(models) {
Project.belongsToMany(models.User, { through: 'Project_User', as: 'users'});
}
}
// Methods...
);
User Model :
var User= sequelize.define('User', {
name:{type: DataTypes.STRING, unique: true}
},
classMethods: {
associate: function(models) {
User.belongsToMany(models.Project, { through: 'Project_User', as: 'projects'});
}
}
// Methods...
);
And here is the association table Project_User Model :
var Project_User = sequelize.define('Project_User', {
role:
{
type: DataTypes.INTEGER,
allowNull: false,
references: 'Role',
referencesKey: 'id'
}
},{
classMethods: {
associate: function(models) {
Project_User.belongsTo(models.Role, {foreignKey: 'role'});
}
}
});
Now, I want to find all project, with their users and their role. So I've used findAll with the "include" parameters like below:
models.Projects.findAll({
include:[
{
model: models.User,
as:'users',
through: {attributes: ['role'], as: 'role'}
}]
}).then(function(result) {
// ...
});
This works great but I only have the roleId associated to the user. I wasn't be able to link this "roleId" with the role table to get the other attributes like role name...
Here is the JSON I've got :
[
{
"id": 1,
"name": "Project name",
"users": [
{
"id": 1,
"name": "User name",
"role": {
"role": 1
}
}
]
}
]
But I would like to have something like that :
[
{
"id": 1,
"name": "Project name",
"users": [
{
"id": 1,
"name": "User name",
"role": {
"id": 1,
"name": "Role name",
"description": "Some info...",
}
}
]
}
]
I've tried many things to realize this association, even successive includes but it was unsuccessful. What is needed in the findAll options to get this JSON result ?
Thanks
Assuming that your User model is linked to the Role model, something like this should work:
models.Projects.findAll({
include:[
{
model: models.User,
as:'users',
through: {attributes: []},
include: [models.Role]
}] }).then(function(result) {
// ... });