Sails.js/Waterline cascading delete for a many-to-many association - sails.js

As shown in that stackoverflow answer, having no support for cascading (cascading deletes in particular) in Waterline there is a workaround for one-to-many associations by using the afterDestroy (or afterUpdate for soft-delete) lifecycle callback and deleting the associated records with a second query. This is possible via ManyModel.destroy({ oneModel: _.pluck(destroyedOneModels, "id") }) from within afterDestroy.
How do we do that for a many-to-many relationship (having in mind that a junction table is used internally and we have to delete records from it)?

I did some tests using the Pet / User example from the documentation with sails 0.11.
Writting this lifecycle callback in the Pet model deletes all the users associated to a pet before deleting it.
// models/Pet.js
module.exports = {
attributes: {
name:'string',
color:'string',
owners: {
collection: 'user',
via: 'pets'
}
},
beforeDestroy: function(criteria, cb) {
// Destroy any user associated to a deleted pet
Pet.find(criteria).populate('owners').exec(function (err, pets){
if (err) return cb(err);
pets.forEach(function(recordToDestroy) {
User.destroy({id: _.pluck(recordToDestroy.owners, 'id')}).exec(function(err) {
console.log('The users associated to the pet ' + recordToDestroy.name + ' have been deleted');
});
});
cb();
})
}
};
I couldn't do it in the afterDestroy lifecycle callback because the many-to-many properties of the deleted records are missing there.
Waterline is deleting the records of the junction table automatically.
The problem with this feature is that it probably would delete too much things if some pets share some owners. Following the example of the documentation, if you delete the pet Rainbow Dash, you will delete the users Mike, Cody and Gabe, and the pets Pinkie Pie and Applejack would be orphans.
If you define a many-to-many relation like this one but you know that the pets cannot have any owner in common, then it works fine. Otherwise, you should add a test to check that you will not make another pet an orphan.

Related

Nested Hasura GraphQL Upsert mutation is there a way to stop nesting on conflict?

I use Hasura and I have a social-network like situation.
In which I have a "User" object and a "Feed" object.
Every user has a feed.
I have a relationship from user.id to feed.id.
The relevant mutation is UpsertUserDetails as follows:
mutation UserDetailsUpsert(
$email: String!
$picture: String
) {
insert_users_one(
object: {
email: $email
feed: { data: {} }
picture: $picture
}
on_conflict: { constraint: users_tid_email_key, update_columns: [picture] }
) {
id
}
}
So when I create a new user it also creates a feed for it.
But when I only update user details I don't want it to create a new feed.
I would like to stop the upsert from going through to relationships instead of the above default behavior.
and according to this manual I don't see if its even possible: https://hasura.io/docs/latest/graphql/core/databases/postgres/mutations/upsert.html#upsert-in-nested-mutations
To allow upserting in nested cases, set update_columns: []. By doing this, in case of a conflict, the conflicted column/s will be updated with the new value (which is the same values as they had before and hence will effectively leave them unchanged) and will allow the upsert to go through.
Thanks!
I'd recommend that you design your schema such that bad data cannot be entered in the first place. You can put partial unique indices on the feed table in order to prevent duplicate feeds from ever being created. Since you have both users and groups you can implement it with 2 partial indices.
CREATE UNIQUE INDEX unique_feed_per_user ON feed (user_id)
WHERE user_id IS NOT NULL;
CREATE UNIQUE INDEX unique_feed_per_group ON feed (group_id)
WHERE group_id IS NOT NULL;

How to craft a GET and POST request for a One-To-Many Associated Database in FeathersJS?

I am using FeatherJS with feathers-sequelize and postgresql to expose an endpoint and store information in a database. I think I have been able to set up a 1 to Many relation in my Postgresql database, but I do not know how to test it.
I followed the POST request format in this tutorial: https://www.djamware.com/post/5bb1f05280aca74669894417/node-express-sequelize-and-postgresql-association-example
but in Postman instead. IN pgAdmin, my company table is populated, but my branch table remains empty.
I set up the association like this:
In my Company script:
(company as any).associate = function (models: any) {
company.hasMany(models.branches, { foreignKey: 'companyId', sourceKey: "companyId" });
};
In my Branch script:
(branch as any).associate = function (models: any) {
branch .belongsTo(models.company, { foreignKey: 'companyId', targetKey: 'companyId' });
};
I expected both my tables to be populated with companyId as a foreign key in my branch table. However, only my company table is populated with the information from the POST request, while my branch table remains empty.
Have a look at this FAQ how to retrieve associations with Sequelize:
// Find Hulk Hogan and include all the messages he sent
app.service('branches').find({
sequelize: {
include: [{
model: Company
}]
}
});
More information can be found in the adapter documentation.

How can I get an model of foreign key along with the model. I tried using include

I am trying to get the company details along with every employee. using node.js
I tried using "include".
return Employee.db.findAll({where: {salary: amount}}, include: Company.db],
});
Note: empId is the foreign key to employee(id).
in db intialization:
EmployeeDb.belongsTo(CompanyDb, {foreignKey: 'companyId', targetKey: 'id'});
CompanyDb.hasMany(EmployeeDb, {foreignKey: 'companyId', sourceKey: 'id'});
I was expecting company object inside all the employee objects. But the company details are not retrieved
The below should work for you.
Changed Employee.db to EmployeeDb and Company.db to CompanyDb
include expects an Array.
return EmployeeDb.findAll(
{
where: {salary: amount},
include: [CompanyDb]
}
);

Entity Framework Many to Many and existing data post from angular

Entity Framework from Database First created the Table model classes having many to many relationships in C# WebApi.
Table ACCOUNTS and table METADATA have a many-to-many relationship between them.
I want to add a new entry on ACCOUNTS table and link this entry with some existing entries from METADATA table. How can I do this using AngularJS to post data?
I am sending this data on $http:
var account: {
Title: 'Title',
User: 'User',
METADATA: [
{
Name: 'value1'
},
{
Name: 'value2'
}]
}
The account variable above is based on ACCOUNTS class which is being read by the C# web api using POST and [FromBody] like this:
[HttpPost]
public async Task<IHttpActionResult> Add([FromBody]ACCOUNTS account)
{
db.ACCOUNTS.Add(account);
await db.SaveChangesAsync();
int accountId = account.AccountId;
return Ok(accountId);
}
I am getting an error of primary key violation of existence of value1 and value2 on table METADATA.
This is correct because the values exist in the table.
But actually I want these values to be linked to the "intermediate table" which links ACCOUNTS and METADATA as many-to-many relationship, and not to be added.
Any solution to this scenario?
Before inserting the passed disconnected account object, you need to map the child METADATA objects to existing database entities. For instance, using something like this:
var medataNames = account.METADATA.Select(e => e.Name);
account.METADATA = db.METADATA.Where(e => metadataNames.Contains(e.Name)).ToList();
db.ACCOUNTS.Add(account);
// ...

Associations with combination foreign key in Sails.js/Waterline

I need to have a model called 'Package' which can have one or more children of several different types. For example, a package can contain guides, as well as forms and other content types (some of which will be added later). Each of these content items, from different tables, can be in multiple packages. So I have the following schema:
Package table
=============
id
name
....
PackageContent table
=============
id
packageId
contentType
contentId
Guide table
=============
id
name
...
Form table
=============
id
name
How can I define the 'content' association for my packages in my Package.js model file in sails.js? I have not been able to find any information on combination foreign keys in sails.js or Waterline. I would hope to find something along the lines of:
// models/Package.js
module.exports = {
attributes = {
name: 'text',
....
content: {
through: 'PackageContent',
collection: contentType,
via: 'contentId'
}
}
};
I have a similar problem recently. My solution is to create different foreign keys in the intermediary model and set attributes as 'required:false'. In your example, the PackageContent table could look like this:
//PackageContent.js
module.exports={
attributes={
package:{
model:'package',
},
guide:{
model:'guide'
require:false
},
form:{
model:'form'
require:false
}
//other attributes...
}
}
In order to avoid duplicated package+content combination, you may need to write a beforeValidate method to check the duplication. I am not sure if this is a 'good design', but it solves the probelm