Specification for my conference instance method:
getParticipants() : Promise -> Participant array
Conference model:
return sequelize.define('conference', {
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true
},
name: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
maxParticipants: {
type: Sequelize.INTEGER,
allowNull: false
},
fileShareSession: {
type: Sequelize.STRING,
defaultValue: null,
allowNull: true
},
startDate: {
type: Sequelize.DATE,
defaultValue: null,
allowNull: true
},
endDate: {
type: Sequelize.DATE,
defaultValue: null,
allowNull: true
},
state: {
type: Sequelize.ENUM(
ConferenceState.new,
ConferenceState.starting,
..
),
defaultValue: ConferenceState.new,
required: true,
allowNull: false
}
Participant model:
return sequelize.define('participant', {
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true
},
displayName: {
type: Sequelize.STRING,
defaultValue: null,
allowNull: true
},
mediaResourceId: {
type: Sequelize.STRING,
defaultValue: null,
allowNull: true
},
screenSharingId: {
type: Sequelize.STRING,
defaultValue: null,
allowNull: true
},
mediaType: {
type: Sequelize.ENUM(
MediaType.AUDIO_VIDEO),
defaultValue: MediaType.AUDIO_VIDEO,
allowNull: false
},
state: {
type: Sequelize.ENUM(
ParticipantState.new,
ParticipantState.joining,
..
),
defaultValue: ParticipantState.new,
required: true,
allowNull: false
}
Question:
So can I do a participant.findAll in my conferencing instance model or not? When yes, do I get an Array back with a findAll?
I would have done it like that:
// getParticipants() : Promise -> Participant array
getParticipants() {
return new Promise((resolve, reject) => {
var Participant = sequelize.models.participant;
Participant.findAll({
where: {
id: id
}
}).then(function(participant) {
if (_.isObject(participant)) {
resolve(participant);
} else {
throw new ResourceNotFound(conference.name, {id: id});
}
}).catch(function(err) {
reject(err);
});
});
},
LAZY loading is implemented by sequelize when you make relationships between tables. You could make a relationship as follows:
var Conference = sequelize.define('conference', { ... });
var Participant = sequelize.define('participant', { ... });
Conference.belongsToMany(Participant, { through: 'ConferenceParticipants'});
Participant.belongsToMany(Conference, { through: 'ConferenceParticipants'});
Then you can implement EAGER loading when you query your database like:
// Obtain the participant list included in the original object (EAGER)
var conference =
Conference.findOne({
attributes: ['field1', 'field2', ...],
where: {title: 'Conference A'},
includes: [{
attributes: ['field1', 'field2', ...],
model: Participant,
through: { model: 'ConferenceParticipants'} // You have to name the join table
}]
})
.then(function(conference) {
// Here you will have the conference with the list of participants
});
If you want to use LAZY loading, sequelize implement it for you, you just need to call below methods:
// Obtain the participant LAZY
conference.getParticipants().then(function(participants) {
// participants is an array of participant
})
// You can also pass filters to the getter method.
// They are equal to the options you can pass to a usual finder method.
conference.getParticipants({ where: 'id > 10' }).then(function(participants) {
// participants with an id greater than 10 :)
})
// You can also only retrieve certain fields of a associated object.
conference.getParticipants({attributes: ['title']}).then(function(participants) {
// retrieve participants with the attributes "title" and "id"
})
You can get a reference to sequelize relationship implementation in next document.
Related
I have a products table and each product has some materials it is made of, the materials are referenced from Materials table, I have only their ids in the product record.
My problem is that that I can't do the association properly: I got an error saying the following:
throw new AssociationError(${source.name}.belongsToMany(${target.name}) requires through option, pass either a string or a model); AssociationError [SequelizeAssociationError]: product.belongsToMany(material) requires through option, pass either a string or a model
when I pass this code models.material I know it is a string because I printed it on the terminal, so why am I getting this error? How can I make proper associations?
here the whole code:
class Product extends Model {
static associate(models) {
Product.belongsToMany(models.material, {
foreignKey: 'id',
sourceKey: 'materials',
constraints: false,
});
Product.belongsTo(models.user, {
foreignKey: 'id',
sourceKey: 'creator',
});
}
}
Product.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
quantity: {
type: DataTypes.DECIMAL,
allowNull: false,
defaultValue: 0.0,
},
materials: {
type: DataTypes.ARRAY(DataTypes.UUID),
},
creator: {
type: DataTypes.UUID,
},
updater: {
type: DataTypes.UUID,
},
},
{
sequelize,
modelName: 'product',
}
);
When making many to many relationships, way to go is to create some throughput table where both Product and Material model will be connected.
We can call it inventory and inventory will have both productId and materialId.
As for relations, product will have many inventories and material will also have many inventories.
Inventory would look like this:
module.exports = (sequelize, DataTypes) => {
class Inventory extends Model {
static associate(models) {
this.Product = this.belongsTo(models.Product, {foreignKey:
'productId'});
this.Material = this.belongsTo(models.Material , {foreignKey:
'materialId'}); } }
Inventory.init({
id: { // you can just keep id of inventory too
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER,},
productId: { allowNull: false, type: DataTypes.STRING},
materialId: { allowNull: false, type: DataTypes.STRING}
}, { sequelize, modelName: 'Inventory', createdAt: 'created_at', updatedAt: 'updated_at',});
return Inventory; }
And then is products and materials model you would have something like this
// in product table
this.Inventory= this.hasMany(models.Inventory, {foreignKey: 'productId'});
// in inventory table
this.Inventory= this.hasMany(models.Inventory, {foreignKey: 'materialId'});
This should work and hope it all makes sense
I solved it in a similar way #Edward
class Product extends Model {
static associate(models) {
const materials_list = sequelize.define('materials_list', {
materialId: {
type: DataTypes.UUID,
references: {
model: models.material,
key: 'id',
},
},
productId: {
type: DataTypes.UUID,
references: {
model: models.product,
key: 'id',
},
},
});
Product.belongsToMany(models.material, {
through: materials_list,
});
Product.belongsTo(models.user, {
foreignKey: 'creator',
targetKey: 'id',
});
Product.belongsTo(models.user, {
foreignKey: 'updater',
targetKey: 'id',
});
}
}
Product.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
quantity: {
type: DataTypes.DECIMAL,
allowNull: false,
defaultValue: 0.0,
},
material_list: {
type: DataTypes.ARRAY(DataTypes.UUID),
},
creator: {
type: DataTypes.UUID,
},
updater: {
type: DataTypes.UUID,
},
},
{
sequelize,
modelName: 'product',
}
);
return Product;
};
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.
I have a classical many-to-many relationship for users which own assets: assets can be transfered to other users during their life so a window time is recorded in the AssetUser "through table",
adding STARTDATE and ENDDATE attributes.
User Table
const User = sequelize.define('User', {
ID: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
FIRSTNAME: {
type: DataTypes.STRING,
allowNull: false
},
LASTNAME: {
type: DataTypes.STRING,
allowNull: false
}},{ timestamps: false }});
Asset Table
const Asset = sequelize.define('Asset', {
ID: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
DESCRIPTION: {
type: DataTypes.STRING,
allowNull: false
}},{ timestamps: false }});
AssetUser Join Table
const AssetUser = sequelize.define('AssetUser', {
id: {
type: DataTypes.INTEGER.UNSIGNED,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
UserID: {
type: DataTypes.INTEGER.UNSIGNED,
references: {
model: User,
key: 'ID'
}
},
AssetID: {
type: DataTypes.INTEGER.UNSIGNED,
references: {
model: Asset,
key: 'ID'
}
},
STARTDATE: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
ENDDATE: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: null
}},{ timestamps: false });
The models are created here:
User.belongsToMany(Asset, { through: { model: AssetUser, unique: false }, uniqueKey: 'id' });
Asset.belongsToMany(User, { through: { model: AssetUser, unique: false }, uniqueKey: 'id' });
My problem is that I want to query and find all the results where one asset, owned by one user, during a restricted period. I am not able to query the join-table but only User and Assets tables.
How can I add a "where" condition for the AssetUser table inside my query? How should I insert a STARTDATE and/or ENDDATE condition below?
Asset.findAll({
where: {
DESCRIPTION: 'Personal computer'
},
include: {
model: User,
where: {
FIRSTNAME: 'Marcello'
}
}});
Thanks for your help.
I found the solution
Asset.findAll({ where: { DESCRIPTION: 'Personal computer' }, include: { model: User, through: { where: { FIRSTNAME: 'Marcello' } } }});
i am new to sequelize, i have a user table , address table and address type table as given below.
A user can have 2 a different address , permanent and current address, and the type of address (permanent or current ) is specified in the table address type.
I have tried to access the data from mapping table (address_type) in the resolver based on schema and set hasMany relation from user -> address table , but graphql shows association not found error.
How can we get the relation properly in order to get the mapping address type name.
type User{
id:Int
name:String
}
type Address {
id: ID!
user_id:Int
city: String
addr_type:AddressType
}
type AddressType{
id : Int
name:String (permanent|current)
}
table definition
module.exports = function(sequelize, DataTypes) {
return sequelize.define('user', {
id: {
type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true
},
name: {
type: DataTypes.INTEGER, allowNull: false,
},
}, {
tableName: 'user',
timestamps: false
});
};
module.exports = function(sequelize, DataTypes) {
return sequelize.define('address', {
id: {
type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true
},
user_id: {
type: DataTypes.INTEGER, allowNull: false, field:"addr_type"
},
addr_type: {
type: DataTypes.INTEGER, allowNull: false, field:"addr_type"
},
city: {
type: DataTypes.STRING, allowNull: false,
},
}, {
tableName: 'address',
timestamps: false
});
};
module.exports = function(sequelize, DataTypes) {
return sequelize.define('address_types', {
id: {
type: DataTypes.INTEGER, allowNull: false, primaryKey: true, autoIncrement: true
},
name: {
type: DataTypes.STRING, allowNull: false,
},
}, {
tableName: 'address_type',
timestamps: false
});
};
relationship
db.user.hasMany(db.address,{foreignKey: 'user_id'});
db.address.belongsTo(db.user,{foreignKey: 'user_id'});
db.address.belongsTo(db.address_types,{foreignKey: 'addr_type'});
resolver code
userts: async (obj, args, context, info ) => User.findAll( {
where: { user_status: 1 },
,
raw: true,
nest: true,
} ).then(userts => {
const response = userts.map(usert => {
return{
// i have 15 fields for a user, if i can access the schema of the corresponsing resolver i can dynamically build the response out put
id: usert.id,
firstName: usert.firstName,
lastName: usert.lastName,
middleName: usert.middleName,
}
})
return response;
}),
You should turn off the option raw in order to get associated objects and use the include option to indicate what associated models you wish to load.
User.findAll( {
where: { user_status: 1 },
include: [{
model: Address,
include: AddressType
}],
raw: false,
nest: true,
}
i have these 2 models:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('services_prices', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true
},
service_id: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'services',
key: 'id'
}
},
created_at: {
type: DataTypes.DATE,
allowNull: false
},
limit: {
type: DataTypes.INTEGER(11),
allowNull: true
},
price: {
type: DataTypes.INTEGER(11),
allowNull: true
}
});
};
which is parent of this model: (services_user_prices can override services_prices )
module.exports = function(sequelize, DataTypes) {
return sequelize.define('services_user_prices', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
user_id: {
type: DataTypes.INTEGER(11),
allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: false
},
currency: {
type: DataTypes.STRING(255),
allowNull: true
},
is_active: {
type: DataTypes.INTEGER(1),
allowNull: true,
defaultValue: '0'
},
is_trial: {
type: DataTypes.INTEGER(1),
allowNull: true,
defaultValue: '0'
},
start_date: {
type: DataTypes.DATE,
allowNull: false
},
end_date: {
type: DataTypes.DATE,
allowNull: true
},
price: {
type: DataTypes.INTEGER(11),
allowNull: true
},
bundle_price_id: {
type: DataTypes.INTEGER(11),
allowNull: true,
references: {
model: 'services_prices',
key: 'id'
}
}
});
};
when trying to join them i get an error:
EagerLoadingError: services_prices is not associated to services_user_prices!
const result= await db.services_user_prices.findOne({
where: { is_active: 1, user_id: 123 }, include:[{db.services_prices}]
});
in the db services_user_prices has foreign key to services_prices table
what am i doing wrong?
Well if you are using sequelize then you need to update your model because
by default, sequelize will be looking for foreign key starts with model name like
you have defined bundle_price_id as a foreign key for services_prices.
You need to change your column name to services_price_id then it will get fixed.
or if you want to use bundle_price_id you need to define it in your model relation as.
Model.belongsTo(models.ModelName, { foreignKey: 'your_key'} )
Please feel free if you need to ask anything else.
As complement of the above answer you need to add an identifier with as: on the association like this:
Model.belongsTo(models.ModelName, { foreignKey: 'your_key', as:'your_identifier' } )
Then when you do the include on the method you also call the identifier:
await db.services_user_prices.findOne({
where: { is_active: 1, user_id: 123 },
include:[{
model: db.services_prices
as: 'your_identifier'
}]
});
If you don't define the foreignKey field, the as field will set the column name.