How to add a field from associated model using Sequelize? - node.js

I'm making a web API using Node, Express, and Sequelize. I have models Users and Teams (shown below). Users has a teamId that references Teams.id, and there is an association between the two to reflect that.
User definition
const User = sequelize.define('User', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
displayName: {
type: DataTypes.STRING,
allowNull: false
},
teamId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'Team',
key: 'id'
}
}
}
Team definition
const Team = sequelize.define('Team', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}
}
Association
User.belongsTo(Team, { as: 'team', foreignKey: 'teamId' });
Query
Users.findAll({
include: [
{
model: Team,
as: 'team'
}
]
});
As expected, this returns a list of User objects, with the added "team" property the respective Team objects embedded like so:
[
{
"id": 1,
"displayName": "John Smith",
"teamId": 1,
"team": {
"id": 1,
"name": "My Awesome Team"
}
}
]
My goal is to return User objects, but instead of embedding the entire Team object under the team property, I'd like to add just the name of the team as the value of the property, like this:
[
{
"id": 1,
"displayName": "John Smith",
"teamId": 1,
"team": "My Awesome Team"
}
]
Is there a way to accomplish this with Sequelize?

Well, through typing up my question I thought of better ways to search for the answer, and I think I found one right off the bat...
I found this StackOverflow question where the asker was doing what I am trying to do.
The solution is to use attributes.include and Sequelize.col() to include the attribute I want, and in my include options, use attributes: [] to hide the Team objects.
Going off the examples I included in the original post, this is what works for me, giving me nearly the exact output I wanted:
Users.findAll({
attributes:{
include: [[Sequelize.col(team.name), 'teamName']]
}
include: [
{
model: Team,
as: 'team',
attributes: []
}
]
});
Output
[
{
"id": 1,
"displayName": "John Smith",
"teamId": 1,
"teamName": "My Awesome Team"
}
]
Since the association uses the alias "team", I had to name my property something different, and went with "teamName" which is perfectly fine and probably preferable since it is more descriptive.

Related

Nested associated data through Sequelize join tables

Using Sequelize, I'm trying to get an output like this:
[{
"Id": 1,
"Name": "Game 1",
"Teams": [{
"Id": 1,
"Name": "Team 1",
"Users": [{
"Id": 1,
"UserName": "User 1"
}]
}]
}, {
"Id": 2,
"Name": "Game 2",
"Teams": [{
"Id": 1,
"Name": "Team 1",
"Users": [{
"Id": 2,
"UserName": "User 2"
}]
}]
}]
Note that Team 1 has 2 different users, but that's only because they're set up that way per game... so a user isn't tied directly to a team, but rather through a team game constraint. Basically, my Game HasMany Teams, and my Game/Team HasMany Users... a many-to-many-to-many relationship. I was trying to follow this thread, but it seems like what they're doing there doesn't actually work, as I tried doing this:
// models/Game.js
module.exports = (sequelize, types) => {
const GameModel = sequelize.define('Game', {
Id: {
type: types.INTEGER,
primaryKey: true,
autoIncrement: true
},
Name: {
type: types.STRING,
allowNull: false
}
});
GameModel.associate = (models) => {
GameModel.belongsToMany(models.Team, {
as: 'Teams',
foreignKey: 'GameId',
through: models.GameTeam
});
};
return GameModel;
};
// models/Team.js
module.exports = (sequelize, types) => {
const TeamModel = sequelize.define('Team', {
Id: {
type: types.INTEGER,
primaryKey: true,
autoIncrement: true
},
Name: {
type: types.STRING,
allowNull: false
}
});
TeamModel.associate = (models) => {
TeamModel.belongsToMany(models.Game, {
as: 'Games',
foreignKey: 'TeamId',
through: models.GameTeam
});
};
return TeamModel;
};
// models/User.js
module.exports = (sequelize, types) => {
const UserModel = sequelize.define('User', {
Id: {
type: types.INTEGER,
primaryKey: true,
autoIncrement: true
},
UserName: {
type: types.STRING,
allowNull: false
}
});
return UserModel;
};
// models/GameTeam.js
module.exports = (sequelize, types) => {
const GameTeamModel = sequelize.define('GameTeam', {
Id: {
type: types.INTEGER,
primaryKey: true,
autoIncrement: true
}
});
GameTeamModel.associate = (models) => {
GameTeamModel.belongsToMany(models.User, {
as: 'Users',
through: 'GameTeamUser'
});
};
return GameTeamModel;
};
The above models create the tables just fine, with what appears to be the appropriate columns. I then do some inserts and try to use a findAll on the Game model like this:
GameModel.findAll({
include: [{
association: GameModel.associations.Teams,
include: [{
association: GameTeamModel.associations.Users,
through: {
attributes: []
}
}],
through: {
attributes: []
}
}]
});
The query starts to go wrong at the 2nd include with the association of the Users. Because I'm trying to nest the users inside of the teams, I figured the join would attempt to use the unique ID on the through table (GameTeams.Id), but instead, the query ends up using this:
LEFT OUTER JOIN `GameTeamUser` AS `Teams->Users->GameTeamUser` ON `Teams`.`Id` = `Teams->Users->GameTeamUser`.`GameTeamId`
I figured the ON would be GameTeams.Id = Teams->Users->GameTeamuser.GameTeamId, but I don't know why it's not, and how to adjust it... I've tried using a custom on in my include (per the docs), but it seems to be ignored completely. Anyone have any advice? Or possibly a better way of structuring this, so it works the way I want it to?
I think you are overcomplicating this thinking you have a many to many to many..and i can see that the fields for your model for GameTeam do not match up with the foreign keys you have declared in your other models...
What do your database tables look like?
Am i correct in saying, that a game has many teams, and a team has many users... however a user can only be on one team at a time, and a team is only in one game at a time? (i am assuming the game/team join and the team/user join are simply temporary records in the join tables disappearing after the game is over etc)

Sequelize how to make a join request?

I'm trying to make joined queries with Sequelize.
That's my db :
What I need is to select all of my relations and get this kind of result:
[
{
id: 1,
State: true,
FK_User: {
id: 2,
Name: "my name"
},
FK_Team: {
id: 3,
Name: "team name"
}
},
...
]
But today I've got this result:
[
{
id: 1,
State: true,
FK_User: 2,
FK_Team: 3
},
...
]
For each of my relations, I've go to do another request to get datas ...
So I putted a look in this Stack and in the doc.
Then I made this code :
let User = this.instance.define("User", {
Name: {
type: this.libraries.orm.STRING,
allowNull: false
}
});
let Team = this.instance.define("Team", {
Name: {
type: this.libraries.orm.STRING,
allowNull: false
}
});
let Relation = this.instance.define("Relation", {
State: {
type: this.libraries.orm.BOOLEAN,
allowNull: false,
defaultValue: 0
}
});
Relation.hasOne(User, {as: "FK_User", foreignKey: "id"});
Relation.hasOne(Team, {as: "FK_Team", foreignKey: "id"});
With this code, I haven't got any relation between tables... So I added theses two lines. I don't understand why I need to make a two direction relation, because I don't need to access Relation From User and Team ...
User.belongsTo(Relation, {foreignKey: 'FK_User_id'});
Team.belongsTo(Relation, {foreignKey: 'FK_Team_id'});
When I do that, I've a FK_User_id in the User table and a FK_Team_id in the Team table ... I don't know how to make this simple relation and get all I need with my futur request and the include: [User, Team]} line.
User.hasOne(Relation);
Team.hasOne(Relation);
Relation.belongsTo(Team);
Relation.belongsTo(User);
This code seems to work.
I don't know why ...
Here your associations are setup correctly you can join it with include :
Relation.findAll({
where : {
state : true
}
include:[
{
model : User
},
{
model : Team
}
]
})

sequelize: Not include model if where conditon of included model matches

I want to get all RecurringEvents that have no excludeDates for today.
I have following models:
Recurring events
const RecurringEvent = sequelize.definge('recurringEvent,{
id: {type: Sequelize.INTEGER, primaryKey:true},
title: Sequelize.STRING
});
And ExcludeDates with a foreign key recurringEventId
const ExcludeDate = sequelize.define('exclude_date',{
id: {type: Sequelize.INTEGER, primaryKey:true},
recurringEventId: Sequelize.INTEGER,
date: Sequelize.DATE
});
As a relationship i defined
RecurringEvent.hasMany(ExcludeDate, {foreignKey: 'recurringEventId'});
I can get all my RecurringEvents including the the excludeDates with
RecurringEvent.findAll({include:[{model:ExcludeDate}]});
That will give me an output like:
[
{
"id": 1,
"title": "Event1",
"exclude_dates": [
{
"id": 1,
"date": "2019-02-13",
"recurringEventId": 1,
},
{
"id": 2,
"date": "2019-02-14",
"recurringEventId": 1,
}
]
Now i would like to get the Recurring events but only if there is no exclude date for today.
so far i have tried
RecurringEvent.findAll({
include: [{
model: ExcludeDate,
where: {
date: {
[Op.ne]: moment().format('YYYY-MM-DD')
}
}
}]
})
But that only leaves out the ExcludeDate entry with the current Date like that :
[
{
"id": 1,
"title": "Event1",
"exclude_dates": [
{
"id": 2,
"date": "2019-02-14",
"recurringEventId": 1,
}
]
How can i exclude the whole RecurringEvent if and ExcludeDate for it is set for today?
Edit:
I also read in the docs
To move the where conditions from an included model from the ON condition to the top level WHERE you can use the '$nested.column$'
So i have tried this:
RecurringEvent.findAll({where:{
'$exclude_dates.date$':{
[Op.ne]: moment().format('YYYY-MM-DD')
}
},
include: [{model: ExcludeDate}]
})
But without any luck, i'm still getting RecurringEvents just without the one exclude date in the exclude_dates property
Try to add required: true, to your include model to make inner join instead of left join. e.g.
RecurringEvent.findAll({
include: [{
model: ExcludeDate,
required: true,
where: {
date: {
[Op.ne]: moment().format('YYYY-MM-DD')
}
}
}]
})

sequelize: find in json array

In sequelize i have a model:
{
modelId: {
type: DataTypes.UUID ,
allowNull: false,
primaryKey: true,
defaultValue: DataTypes.UUIDV4
}
name: DataTypes.STRING(1024),
type: DataTypes.STRING,
obj: DataTypes.JSON
}
and in DB, my obj is an array like this:
[{
id: '123456',
text: 'test',
name 'xpto'
},{
id: '32554',
text: 'test2',
name 'xpte'
},{
id: '36201',
text: 'test3',
name 'xpta'
}]
i tried these:
btp.findAll({
where: {
obj:{
[Op.contains]:[{id: req.body.id}]
}
},
attributes: ['modelId','name','type','obj']
})
but does not work, return this error:
{"name": "SequelizeDatabaseError",
"parent": {
"name": "error",
"length": 128,
"severity": "ERROR",
"code": "42704",
"file": "parse_coerce.c",
"line": "1832",
"routine": "enforce_generic_type_consistency",
"sql":"....."}
so, i need to find in database all entries have in obj, id: '123456'
my question is the same than this:
https://github.com/sequelize/sequelize/issues/7349
but thats does not working for me, i need to return all entries that contains...
i'm using "sequelize": "4.28.6", and "pg-hstore": "^2.3.2",
can any one help?
I'm not familiar with the specific error you're getting, but one potential issue is that you're using a JSON column instead of JSONB. In Postgres, JSON columns just store the raw JSON text, and so don't support the containment operator (#>) which is needed for Sequelize's "contains".
All you need to do to fix this is change the column definition in the model to:
obj: DataTypes.JSONB
The only other issue I can think of would be req.body.id having an invalid value. I'd suggest verifying that it's actually getting a valid ID string.
Beyond these 2 potential issues, the query you wrote should work.

UI layer pagination and sorting in extjs

I have my extjs application. As of now i am getting all my records from backend, full record set in 1 service request. I need to implement the pagination and sorting at UI level. Sorting seems be simple. How do i implement UI level pagination? Any example for this? I am getting 10-20k records so it is fine if i implement pagination at UI level? Can extjs6 handle the load?
I'd recommend you handle paging server-side. Right now you might only have 10-20k records, but what if it grows to 100k? or 1 million?
Take a look at this guide from Sencha: Grids - Paging. It explains a lot.
Good luck!
Sorting is implemented out of box. This is simple pagination example based on default ExtJs 6.2.0 application.
YourAppName.view.main.List
...
// bottom paging-bar definition. Use "tbar" for top bar, or define both.
bbar: {
xtype: 'pagingtoolbar',
displayInfo: true,
emptyMsg: 'No data to display',
items: ['->'],
prependButtons: true
}
...
items: [{
title: 'Home',
iconCls: 'fa-home',
layout: 'fit', // needed for scrolling
scrollable: true, // for scrollable items
items: [{
xtype: 'mainlist'
}]
}, {
...
YourAppName.store.Personnel
Ext.define('YourAppName.store.Personnel', {
extend: 'Ext.data.Store',
alias: 'store.banners',
autoLoad: true, // run ajax-query right after grid rendering
loadMask: true, // show preload image
pageSize: 100,
model: 'YourAppName.model.Person',
proxy: {
type: 'ajax',
url: '/personnel',
reader: {
type: 'json',
rootProperty: 'items',
totalProperty: 'total'
}
}
});
Create in app/model folder file Person.js with:
YourAppName.model.Person
Ext.define('YourAppName.model.Person', {
extend: 'Ext.data.Model',
fields: [
{ name: 'name', type: 'string', defaultValue: '' },
{ name: 'email', type: 'string', defaultValue: '' },
{ name: 'phone', type: 'string', defaultValue: '' }
]
});
As of store definition your web-server must be able to response on HTTP GET-request on URI /personnel with json like this:
{
"success": true,
"total": 20000,
"items": [
{ "name": "Jean Luc", "email": "jeanluc.picard#enterprise.com", "phone": "555-111-1111" },
{ "name": "Worf", "email": "worf.moghsson#enterprise.com", "phone": "555-222-2222" },
{ "name": "Deanna", "email": "deanna.troi#enterprise.com", "phone": "555-333-3333" },
{ "name": 'Data', "email": "mr.data#enterprise.com", "phone": "555-444-4444" }
...
]
}

Resources