Sails / WaterLine ORM / Has Many Through: Insert data into join table - mysql

I'm newbie with Sails/WaterLine ORM
I'm following http://sailsjs.org/documentation/concepts/models-and-orm/associations/through-associations
One question.
How way to insert data into a join table ?
For example: User m - m Pet
User model
module.exports = {
attributes: {
name: {
type: 'string'
},
pets:{
collection: 'pet',
via: 'owner',
through: 'petuser'
}
}
Pet model
module.exports = {
attributes: {
name: {
type: 'string'
},
color: {
type: 'string'
},
owners:{
collection: 'user',
via: 'pet',
through: 'petuser'
}
}
PetUser model (join table)
module.exports = {
attributes: {
owner:{
model:'user'
},
pet: {
model: 'pet'
}
}
}
Pet data is available (some record with ID1, ID2, ID3...)
I want to add new one user with some pets
PetUser ( id , id_of_user, id_of_pet)
1, U1, P1
2, U1, P2
{
"name" : "John",
"pets" : [2,3]
}
UserController
module.exports = {
addUserWithPets: function(req, res) {
User.create(req.body).exec(function(err, user) {
if(err){
throw err;
}else {
/*pets.forEach(function(pet, index){
user.pets.add(pet);
})
user.save(function(err) {});*/
user.pets.add(data);
user.save(function(err) {});
}
return res.ok({
data: user
});
})
}
};
Thanks!

I think this hasn't been implemented yet in sails.
Refer to this question: through associations in sails.js on SO.
Here is what waterline docs say:
Many-to-Many through associations behave the same way as many-to-many associations with the exception of the join table being automatically created for you. This allows you to attach additional attributes onto the relationship inside of the join table.
Coming Soon

Related

Using explicit Many to Many Relation in Prisma

I have the following User and Group models that share a many-to-many relation:
model User {
id String #id #default(uuid())
email String #unique
groups UsersGroups[]
##map("user")
}
model Group {
id String #id #default(uuid())
name String
users UsersGroups[]
##map("group")
}
model UsersGroups {
user User #relation(fields: [userId], references: [id])
userId String #map(name: "user_id")
group Group #relation(fields: [groupId], references: [id])
groupId String #map(name: "group_id")
##id([userId, groupId])
##map("users_groups")
}
I'm having trouble using the connect API in Prisma to connect the users and groups. Here's what I have:
await prisma.group.update({
where: {
id: groupId,
},
data: {
users: {
connect: users.map((user) => ({ id: user.id })),
},
},
include: { users: true },
});
That doesn't work and here is the error I'm getting in the console:
PrismaClientValidationError:
Invalid `prisma.group.update()` invocation:
{
where: {
id: '64ce24c7-3054-42f2-b49f-4cdb52cf1bc7'
},
data: {
users: {
connect: [
{
id: '0b3f4a51-0efe-4b0a-8763-e71bc8091b86'
~~
}
]
}
},
include: {
users: true
}
}
Unknown arg `id` in data.users.connect.0.id for type UsersGroupsWhereUniqueInput. Available args:
type UsersGroupsWhereUniqueInput {
userId_groupId?: UsersGroupsUserIdGroupIdCompoundUniqueInput
}
From that above, it looks as though it's attempting to connect a user with id: '0b3f4a51-0efe-4b0a-8763-e71bc8091b86' (which is a user that exists) to the group with id: '64ce24c7-3054-42f2-b49f-4cdb52cf1bc7' (which also exists).
I'd be very grateful if someone could point out where I'm going wrong as I've been going in circles with this for a while now...
You are using an explicit many-to-many relation, cf. https://www.prisma.io/docs/concepts/components/prisma-schema/relations/many-to-many-relations#explicit-many-to-many-relations
I.e. you have defined the model UsersGroups yourself.
As a consequence, you would have to manage/create the records in this table yourself and connect it with the entry in the third table, e.g. like this (haven't tested it):
prisma.group.update({
where: {
id: groupId,
},
data: {
users: { create: { user: { connect: { id: userId } } } },
},
include: { users: true },
});
or if you want to loop over an list:
prisma.group.update({
where: {
id: groupId,
},
data: {
users: {
create: users.map((user) => ({
user: { connect: { id: user.id } },
})),
},
},
include: { users: true },
});
I would suggest to replace groups UsersGroups[] and users UserGroups[] with
userGroups UsersGroups[] in the schema to make it clearer.
As an alternative to explicit relationships you could try to use implicit many-to-many relations in the schema like this:
model User {
id String #id #default(uuid())
email String #unique
groups Group[]
##map("user")
}
model Group {
id String #id #default(uuid())
name String
users User[]
##map("group")
}
cf. https://www.prisma.io/docs/concepts/components/prisma-schema/relations/many-to-many-relations#implicit-many-to-many-relations

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

Sequelize multiple joins for fetching data

I am working on a CRUD application with Sequelize and ExpressJS that has the following tables:
Parents
Students
ParentStudents
id
id
id
name
name
idParent
idStudent
I want to query the Parents table and have students key created by a left join between students and ParentStudents on idParent;
I want to get data in the following way:
{
"data":[
{
"name":"nameParent2",
"students":[
{
"name":"Student1"
},
{
"name":"Student2"
}
]
},
{
"name":"nameParent2",
"students":[
{
"name":"Student1"
},
{
"name":"Student2"
}
]
}
]
}
I tried something like this, but is wrong and giving data from ParentStudents:
models.parents.findAll({
include: [{
model: models.parentStudents
}]
}).then(data => {
res.json({
message: "Hello from server!!!",
data: data
});
});
Like this is failing to make the association between Students and ParentStudents. (students is not associated to parentStudents!)
models.parents.findAll({
include: [{
model: models.parentStudents,
include: [{
model: models.students
}]
}]
}).then(data => {
res.json({
message: "Hello from server!!!",
data: data
});
});
My relationships are done like this:
db.parents.hasMany(db.parentStudents);
db.students.hasMany(db.parentStudents);
I also tried Many-to-Many, but still doesn't work:
db.students.belongsToMany(db.parents, { through: db.parentStudents });
db.parents.belongsToMany(db.students, { through: db.parentStudents });
Does someone know how this can be done?
One workaround is:
let parents = await models.parents.findAll();
for (let parent of parents) {
let idParent = parent.dataValues.id;
let students = await models.sequelize.query(`
SELECT * FROM students s
LEFT JOIN parentStudents ps
ON s.id = ps.studentId
WHERE ps.parentId =${idParent};
`);
parent.dataValues['students'] = students;
}

How to prevent duplicate entities in many-to-many table with Angular

How can I prevent duplication of records with the same combinations of entities, here : id_product and id_customer. When I click "save relation" a relation (many-to-many) between product and customer is created and this relation has its own id, id_product and id_customer. Is there any solution to block creation of relation between product and customer if such combination already exists in MySQL database ?
public saveRelation = (relationFormValue) => {
const newRelation = {
id_product: relationFormValue.id_product ,
id_customer: relationFormValue.id_customer
};
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
maxWidth: "400px",
data: new ConfirmDialogModel("Please confirm",'Are you sure to save this relation ?')
});
dialogRef.afterClosed().subscribe(dialogResult => {
if (dialogResult==true) {
this.relationService.create(newRelation)
.subscribe(
response => {
this.dialogRef.close(true);
},
error => {
this.errorService.handleError(error);
});
}
});
}

Sequelize delete multiple associations

Hi I have the following tables
models.User= sequelize.define("User", {
name: {
type: DataTypes.STRING, allowNull: false, unique: true
}
});
models.Group= sequelize.define("Group", {
name: {
type: DataTypes.STRING, allowNull: false, unique: true
}
});
db.User.belongsToMany(db.Group, {
"through": "UsersGroup"
});
db.Group.belongsToMany(db.User, {
"through": "UsersGroup"
});
Is there a way to delete at once many user from a group, given the group name , and the names (users) to delete?
like:
var groupName = 'Work';
var users = ['Alice','Bob']
So I need to delete the association between Work group and {Alice and Bob}
Using "through": "UsersGroup" will automatically create a new Model UsersGroup however it won't associate it with User and Group directly. So what i'd do is to create a new sequelize model UsersGroup with whatever attributes and keys and define the association to User and Group. Then instead of passing "UsersGroup" string to belongsToMany you use the model directly as db.UsersGroup.
models.UsersGroup = sequelize.define("UsersGroup", {});
db.UsersGroup.belongsTo(db.User);
db.UsersGroup.belongsTo(db.Group);
db.UsersGroup.findAll({attributes:['id'], include:[
{model: db.User, where: {name: {$in:['Alice','Bob']}}},
{mode: db.Group, where: {name: 'Work'}}
]}).then(function(toBeDeleted){
return db.UsersGroups.destroy({where:{id:{$in:toBeDeleted.map(function(d){ return d.id})}}})
}).then(function(){
....
}).catch(function(dbErr){throw err;})
You also might want to check if toBeDeleted.length > 0 as i remember sequelize was acting funny when an empty array is passed.
Also in case you don't have id as primary key in usersgroup table you can easily modify the code above to use combination of user_id and group_id - Just modify the where statements.
The other approach could be selecting Users and Groups separately and use resulting sets in UsersGroups.destroy(). But i prefer to have an association even from a through model.
If you were to delete associations just based on group name OR user name you'd be able to do that using removeAssociation or removeAssociations have a looj here
Ended up doing that(role-> group , action->user)
var role = JSON.parse(req.body.role);
var actions = JSON.parse(req.body.actions);
var actionNames = _.map(actions, function (action) { return action.name });
models.Role.findOne({ where: { name: role.name } }).then(function (_role) {
if (_role == null) {
res.status(500).send("role was not found");
}
else {
models.Action.findAll({ where: { name: actionNames } }).then(function (actionsToRemove) {
if (actionsToRemove == null) {
res.status(500).send("No action [" + action.name + "] was found");
}
else {
_role.removeActions(actionsToRemove).then(function (removedActions) {
if (removedActions == null || removedActions != actions.length) {
res.status(500).send(actions.length + " Action expected to be removed , but only [" + removedActions + "] were removed");
}
else {
res.send("" + removedActions);
}
}).catch(function (err) {
throw err;
})
}
})
}
}).catch(function (err) {
res.status(500).send(err);
});