Populating one to many relationship in sails js without using primary key - mysql

Every example code of one to many in sails/waterline documentation assumes the primary key is the association between two models (I think).
http://sailsjs.org/documentation/concepts/models-and-orm/associations/one-to-many
However i have models that have a referral column that references some other values similar to
User
{
id (primary): <int>
email: <string>
}
recordings
{
id (primary): <int>
email: <that email from user>
}
atm im trying
userModel.js
{
attributes: {
email: {
type: 'string',
size: 255,
unique: true
},
},
queries: {
columnName: 'email',
model: 'recordings'
}
.....
}
}
recordingsModel.js
{
attributes: {
email: {
type: 'string',
size: 255,
},
},
queries: {
model: 'user'
}
.....
}
}
and in the Controller
sails.models.user
.find()
.populate('queries')
.exec(function (err, results) {
});
But i get the error
: ER_BAD_FIELD_ERROR: Unknown column '__queries.queries' in 'field list'
Does anyone have a good tutorial for one to many relationships in waterline because the documentation on there site is pretty bad so i feel im just not understanding how to design the models.

From what I can gather, what you want is to be able to set up your userModel and recordingsModel models such that, by giving a recording a certain value for email, it will automatically be associated with any users that share that email. This is not something that the Waterline (the Sails ORM) supports.
Instead, your best option is to set the models up as indicated in the documentation:
// userModel.js
{
attributes: {
email: {
type: 'string',
size: 255,
unique: true
},
},
recordings: {
collection: 'recordingsModel',
via: 'user'
}
.....
}
}
// recordingsModel.js
{
attributes: {
email: {
type: 'string',
size: 255,
},
},
user: {
model: 'userModel'
}
}
}
My guess is that you're trying to avoid having to look up a user ID in order to associate a new recording with it. You can do this if you make email the primary key of the userModel; then when you create a new recording, you can set its user to an email address and voila: the two are linked.

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

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

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

Sails js sort by populated field

I need to sort data from a MySQL database on related table row.
Suppose we have two models:
ModelOne:
module.exports = {
tableName: 'modelOne',
attributes: {
// some atributes.........
modelTwo{
model: 'ModelTwo',
columnName: 'model_two_id'
}
}
}
ModelTwo:
module.exports = {
tableName: 'modelTwo',
attributes: {
// some atributes.........
model_two_id: {
type: 'integer',
autoIncrement: true,
primaryKey: true
},
name: 'string'
}
}
I would like to do something like:
ModelOne
.find(find_criteria)
.populateAll()
.paginate({page: page, limit: limit})
.sort('modelTwo.name')
.then(...)
Is there possibility to do this or I need to write an SQL query without using Waterline functions?
This is how I do it...
ModelOne
.find(find_criteria)
.populate('modelTwo', {sort: 'name ASC'})
.paginate({page: page, limit: limit})
.then(...)
As you can see, you can pass {sort} object to populate method.
No. Deep-population-sorting is on future list https://github.com/balderdashy/waterline/issues/266

Populate model of the model with sails js

I'm trying to populate model of the model with sails unfortunally it doesn't work.
I have 3 models
/**
Conversation.js
**/
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'conversation',
attributes: {
idConversation:{
columnName:'IDCONVERSATION',
primaryKey:true,
autoIncrement:true,
unique:true,
type:'integer',
index:true
},
dateStartConversation:{
columnName:'DATEDEBUT',
type:'date',
index:true
},
user1:{
columnName:'IDUSER1',
model:'user',
notNull:true
},
user2:{
columnName:'IDUSER2',
model:'user',
notNull:true
},
article:
{
model:'article',
columnName:'IDARTICLE',
notNull:true
}
}
};
/**
Article.js
**/
module.exports = {
autoPK: false,
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'article',
attributes: {
idArticle:{
type:'integer',
unique:true,
columnName:'IDARTICLE',
autoIncrement:true,
primaryKey:true
},
title:{
type:'string',
required:true,
columnName:'TITRE',
index:true,
notNull:true
},
utilisateur:{
model:'utilisateur',
columnName:'IDUTILISATEUR',
required:true,
notNull:true,
dominant:true
},
images:{
collection:'image',
via:'article'
},
conversation:{
collection:'conversation',
via:'article'
}
}
};
/**
Image.js
**/
module.exports = {
autoCreatedAt: false,
autoUpdatedAt: false,
tableName:'image',
attributes: {
idImage:{
columnName:'IDIMAGE',
primaryKey:true,
autoIncrement:true,
unique:true,
type:'integer'
},
pathImage:{
columnName:'PATHIMAGE',
required:true,
type:'string',
notNull:true
},
article:{
model:'article',
columnName:'IDARTICLE',
notNull:true,
dominant:true
}
}
};
As you can see in my model, an conversation its between Two user, about one article, and those article cas have one or many Images.
So I want to get all conversations of one user and I able to populate with article but I'm not able to populate article with Image below how I proceed
Conversation.find().populate('article').populate('user1').populate('user2').where({
or : [
{ user1: iduser },
{ user2: iduser }
]})
.then(function( conversations) {
var i=0;
conversations.forEach(function(element,index){
i++;
console.log("article "+index+" "+JSON.stringify(element.article));
Article.findOne({
idArticle:element.article.idArticle
}).populate('images').then(function(newArticle){
//I try to set article with the newArticle but it don't work
element.article=newArticle;
})
if(i==conversations.length){
res.json({
hasConversation:true,
conversation:conversations
});
}
});
})
Because deep populate is not possible using sails, I try to use a loop to populate each article with associate Images and set it in conversation, But article is never set in conversation.
How can I fix it ?
Judging by the if(i==conversations.length) at the end, you seem to have an inkling that you need to write asynchronous code. But you're iterating i inside of the synchronous forEach loop, so your response is happening before any of the database queries even run. Move the i++ and the if inside of the callback for Article.findOne:
Conversation.find().populate('article').populate('user1').populate('user2').where({
or : [
{ user1: iduser },
{ user2: iduser }
]})
.then(function( conversations) {
var i=0;
conversations.forEach(function(element,index){
console.log("article "+index+" "+JSON.stringify(element.article));
Article.findOne({
idArticle:element.article.idArticle
}).populate('images').then(function(newArticle){
// Associate the article with the conversation,
// calling `toObject` on it first
element.article= newArticle.toObject();
// Done processing this conversation
i++;
// If we're done processing ALL of the conversations, send the response
if(i==conversations.length){
res.json({
hasConversation:true,
conversation:conversations
});
}
})
});
})
You'll also need to call toObject on the newArticle instance before assigning it to the conversation, because it contains getters and setters on the images property which behave unexpectedly when copied.
I'd also recommend refactoring this to use async.each, which will make it more readable.
Until this is resolved (https://github.com/balderdashy/sails-mongo/issues/108), you can use this function that I developed to solve this: https://gist.github.com/dinana/52453ecb00d469bb7f12