Ability to run lifecycle method on relationship update

Background Details

  1. Foo and users are two models, which are related to each other through many-many relationship.

Issue
When the relationship between foo and users is updated, I want to update a field in users. These user changes can be made in the client-side UI/Strapi UI.

Possible Solutions Tried

  1. Lifecycle methods on foo model did not execute, as no detail of the foo model is updated.
  2. Second solution implemented was adding a method in the foo controller. Controller code is not executed when changes are made in Strapi UI directly.

Note: I am trying to avoid writing a CRON job to achieve this functionality.

Question
Is there a way to hook into lifecycle methods when relationships update?

System Information
  • Strapi Version: 3.2.4
  • Operating System: macOS Big Sur
  • Database: Postgres
  • Node Version: 14.15.4
  • NPM Version: 6.14.10
  • Yarn Version: 1.22.10

1 Like

Hi @Sahiba_K, welcome to the community forum!
Do you have an example of a data model for this question/a scenario I can go off of? Knowing more about your use-case may help me understand what you’re asking better.

1 Like

Hi Richard, thanks for taking time to look into the issue.

I have listed the 2 models below. The relationship between users and presentation (~foo) is stored in a relational Postgres table.

I want to update numberOfPresentations every-time the relation between presentation and users is updated (users are added/removed as presenters/presentation is deleted).

Users model

{
   "kind": "collectionType",
   "collectionName": "users-permissions_user",
   "info": {
     "name": "user",
     "description": ""
   },
   "attributes": {
     "username": {
       "type": "string",
     },
     "name": {
       "type": "string"
     },
     "presentation": {
       "via": "presenters",
       "collection": "presentation"
     },
   "numberOfPresentations":{
       "type":"integer"
     }
   }
 }

Presentation model

{
  "kind": "collectionType",
  "collectionName": "presentation",
  "info": {
    "name": "presentation",
    "description": ""
  },
  "attributes": {
    "name": {
      "type": "string",
      "required": true
    },
    "presenters": {
      "plugin": "users-permissions",
      "collection": "user",
      "via": "presentation",
      "dominant": true
    }
  }
}

Hi @Sahiba_K I have a few recommendations.
TL;DR here’s a possible solution to your requirements. Refer to this guide for more information on model lifecycles.

// api/presentation/models/presentation.js
module.exports = {
	lifecycles: {
		async afterCreate(result, data) {
			// since items or in your case 'presentations' is an array, you have to loop through it
			data.owners.forEach(async (id)=>{
				// find the user so that you can fetch the current number of items/presentations
				const user = strapi.query("user", "users-permissions").findOne({id});
				// get the number of presentations and increment it
				// awaiting because you might want to. 
				await strapi.query("user", "users-permissions").update({id}, {number_of_items: user.number_of_items || 0 + 1});
			});
		},
		// make one for afterUpdate
		// make one for afterDelete
	}
};

The relation you have outlined is not a many-to-many relation. It is a one-to-many relation. You should consider using a many-to-many relation which then would allow you to have the ability to aggregate your data. Relational databases are good at counting (to a certain extent) so if you’re not building a large data-set, your data model will benefit from taking advantage of that.

Your requirements will give you an unnecessary (for a simple relational data model) amount of maintenance work to do to keep the data consistent. If two ‘presentations’ are created at the same time, for instance, they could both pull the same value for ‘numberOfPresentations’. This will offset the accuracy of this value. You will then have to implement eventual consistency by way of a CRON job to correct that value over time.

Thank you Richard, however my problem is still unresolved. I wanted to provide additional details/clarity to the problem.

  1. Users and presenters have a many-many relationship. Relationship between presentations and users is saved in a separate table (presentations_presenters_users), and is not available in the presentation or user tables. I have attached the screenshot of the presentations_presenters_users table below for reference.

  2. Presentation record can updated with presenters (users) after creation as well. Users can be added/removed as presenters.
    When I apply the solution listed above to the presentation model, I am unable to capture the following case of update - When a presenter is removed from the presentation.

    • afterUpdate lifecycle method only has data pertaining to the updated record (ie information regarding the user/presenter that was removed is not available). As a result, I cannot update the count for the effected user.

    • beforeUpdate lifecycle method does not provide any details as to what update/modification will be made to the record. Hence, I am unable to update count (+/-).

Thank you for helping!

Users and presenters have a many-many relationship. Relationship between presentations and users is saved in a separate table (presentations_presenters_users)

  1. can you please explain why they are stored in a seprate table ?
    i think you are creating collections based on an ERD diagram, whereas you don’t need to create it in that way.

    • afterUpdate lifecycle method only has data pertaining to the updated record (ie information regarding the user/presenter that was removed is not available). As a result, I cannot update the count for the effected user.
      There are 2 parameters in the afterUpdate hook, one is the data, and the other is the result.
      result will have the latest updated record, whereas data will have the record which was sent to update via frontend
  2. beforeUpdate lifecycle method does not provide any details as to what update/modification will be made to the record. Hence, I am unable to update count

beforeUpdate will have only one parameter, which is the data sent from the frontEnd, since the record is updated yet, so there will be no result in it