From ebee707ec410310f2eb409796b884f5542085e93 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Mon, 20 Apr 2020 22:27:20 +0200 Subject: [PATCH 01/12] Add basic proto of lifecycles Signed-off-by: Alexandre Bodin --- .../api/restaurant/models/Restaurant.js | 42 ++++++++++---- .../lib/mount-models.js | 56 +++++++++---------- .../lib/queries/create-query.js | 55 ++++++++++++++---- 3 files changed, 104 insertions(+), 49 deletions(-) diff --git a/examples/getstarted/api/restaurant/models/Restaurant.js b/examples/getstarted/api/restaurant/models/Restaurant.js index 0ffd3a3569..968936b2a8 100755 --- a/examples/getstarted/api/restaurant/models/Restaurant.js +++ b/examples/getstarted/api/restaurant/models/Restaurant.js @@ -13,32 +13,54 @@ module.exports = { // afterSave: async (model, response, options) => {}, // Before fetching a value. // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, + beforeFetch: async (...args) => { + console.log('beforeFetch', ...args); + }, // After fetching a value. // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, + afterFetch: async (...args) => { + console.log('afterFetch', ...args); + }, // Before fetching all values. // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, + beforeFetchAll: async (...args) => { + console.log('beforeFetchAll', ...args); + }, // After fetching all values. // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, + afterFetchAll: async (...args) => { + console.log('afterFetchAll', ...args); + }, // Before creating a value. // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, + beforeCreate: async (...args) => { + console.log('beforeCreate', ...args); + args[0].name += ' - Coucou'; + }, // After creating a value. // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, + afterCreate: async (...args) => { + console.log('afterCreate', ...args); + }, // Before updating a value. // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, + beforeUpdate: async (...args) => { + console.log('beforeUpdate', ...args); + args[1].name += ' - Coucou'; + }, // After updating a value. // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, + afterUpdate: async (...args) => { + console.log('afterUpdate', ...args); + }, // Before destroying a value. // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, + beforeDestroy: async (...args) => { + console.log('beforeDestroy', ...args); + }, // After destroying a value. // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} + afterDestroy: async (...args) => { + console.log('afterDestroy', ...args); + }, }; diff --git a/packages/strapi-connector-bookshelf/lib/mount-models.js b/packages/strapi-connector-bookshelf/lib/mount-models.js index a62390927a..11a18a66c4 100644 --- a/packages/strapi-connector-bookshelf/lib/mount-models.js +++ b/packages/strapi-connector-bookshelf/lib/mount-models.js @@ -16,20 +16,20 @@ const populateFetch = require('./populate'); const PIVOT_PREFIX = '_pivot_'; -const LIFECYCLES = { - creating: 'beforeCreate', - created: 'afterCreate', - destroying: 'beforeDestroy', - destroyed: 'afterDestroy', - updating: 'beforeUpdate', - updated: 'afterUpdate', - fetching: 'beforeFetch', - 'fetching:collection': 'beforeFetchAll', - fetched: 'afterFetch', - 'fetched:collection': 'afterFetchAll', - saving: 'beforeSave', - saved: 'afterSave', -}; +// const LIFECYCLES = { +// creating: 'beforeCreate', +// created: 'afterCreate', +// destroying: 'beforeDestroy', +// destroyed: 'afterDestroy', +// updating: 'beforeUpdate', +// updated: 'afterUpdate', +// fetching: 'beforeFetch', +// 'fetching:collection': 'beforeFetchAll', +// fetched: 'afterFetch', +// 'fetched:collection': 'afterFetchAll', +// saving: 'beforeSave', +// saved: 'afterSave', +// }; const getDatabaseName = connection => { const dbName = _.get(connection.settings, 'database'); @@ -566,28 +566,28 @@ module.exports = ({ models, target }, ctx) => { // Load bookshelf plugin arguments from model options this.constructor.__super__.initialize.apply(this, arguments); - _.forEach(LIFECYCLES, (fn, key) => { - if (_.isFunction(target[model.toLowerCase()][fn])) { - this.on(key, target[model.toLowerCase()][fn]); - } - }); + // _.forEach(LIFECYCLES, (fn, key) => { + // if (_.isFunction(target[model.toLowerCase()][fn])) { + // this.on(key, target[model.toLowerCase()][fn]); + // } + // }); // Update withRelated level to bypass many-to-many association for polymorphic relationshiips. // Apply only during fetching. this.on('fetching fetching:collection', (instance, attrs, options) => { populateFetch(definition, options); - return _.isFunction(target[model.toLowerCase()]['beforeFetchAll']) - ? target[model.toLowerCase()]['beforeFetchAll'] - : Promise.resolve(); + // return _.isFunction(target[model.toLowerCase()]['beforeFetchAll']) + // ? target[model.toLowerCase()]['beforeFetchAll'] + // : Promise.resolve(); }); this.on('saving', (instance, attrs) => { instance.attributes = _.assign(instance.attributes, mapper(attrs)); - return _.isFunction(target[model.toLowerCase()]['beforeSave']) - ? target[model.toLowerCase()]['beforeSave'] - : Promise.resolve(); + return _.isFunction(target[model.toLowerCase()]['beforeSave']); + // ? target[model.toLowerCase()]['beforeSave'] + // : Promise.resolve(); }); const formatValue = createFormatter(definition.client); @@ -625,9 +625,9 @@ module.exports = ({ models, target }, ctx) => { this.on(event.name, instance => { formatOutput(instance); - return _.isFunction(target[model.toLowerCase()][event.target]) - ? target[model.toLowerCase()][event.target] - : Promise.resolve(); + // return _.isFunction(target[model.toLowerCase()][event.target]) + // ? target[model.toLowerCase()][event.target] + // : Promise.resolve(); }); }); }; diff --git a/packages/strapi-database/lib/queries/create-query.js b/packages/strapi-database/lib/queries/create-query.js index 17c41c418f..3ce86e962b 100644 --- a/packages/strapi-database/lib/queries/create-query.js +++ b/packages/strapi-database/lib/queries/create-query.js @@ -1,5 +1,7 @@ 'use strict'; +const _ = require('lodash'); + const { replaceIdByPrimaryKey } = require('../utils/primary-key'); module.exports = function createQuery(opts) { @@ -43,29 +45,60 @@ class Query { return mapping[this.model.orm].call(this, { model: this.model }); } - find(params = {}, ...args) { + async find(params = {}, ...args) { const newParams = replaceIdByPrimaryKey(params, this.model); - return this.connectorQuery.find(newParams, ...args); + + await this.executeHook('beforeFetchAll', newParams, ...args); + const results = await this.connectorQuery.find(newParams, ...args); + await this.executeHook('afterFetchAll', results); + + return results; } - findOne(params = {}, ...args) { + async findOne(params = {}, ...args) { const newParams = replaceIdByPrimaryKey(params, this.model); - return this.connectorQuery.findOne(newParams, ...args); + + await this.executeHook('beforeFetch', newParams, ...args); + const result = await this.connectorQuery.findOne(newParams, ...args); + await this.executeHook('afterFetch', result); + + return result; } - create(params = {}, ...args) { - const newParams = replaceIdByPrimaryKey(params, this.model); - return this.connectorQuery.create(newParams, ...args); + async executeHook(hook, ...args) { + if (_.has(this.model, hook)) { + await this.model[hook](...args); + } } - update(params = {}, ...args) { + async create(params = {}, ...args) { const newParams = replaceIdByPrimaryKey(params, this.model); - return this.connectorQuery.update(newParams, ...args); + + await this.executeHook('beforeCreate', newParams, ...args); + const entry = await this.connectorQuery.create(newParams, ...args); + await this.executeHook('afterCreate', entry); + + return entry; } - delete(params = {}, ...args) { + async update(params = {}, ...args) { const newParams = replaceIdByPrimaryKey(params, this.model); - return this.connectorQuery.delete(newParams, ...args); + + await this.executeHook('beforeUpdate', newParams, ...args); + const entry = await this.connectorQuery.update(newParams, ...args); + await this.executeHook('afterUpdate', entry); + + return entry; + } + + async delete(params = {}, ...args) { + const newParams = replaceIdByPrimaryKey(params, this.model); + + await this.executeHook('beforeDestroy', newParams, ...args); + const entry = await this.connectorQuery.delete(newParams, ...args); + await this.executeHook('afterDestroy', entry); + + return entry; } count(params = {}, ...args) { From de5d1d6d5428c93c228b15647ee92c6eb3f2f14c Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Tue, 21 Apr 2020 18:41:36 +0200 Subject: [PATCH 02/12] Add other hooks Signed-off-by: Alexandre Bodin --- .../lib/queries/create-query.js | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/strapi-database/lib/queries/create-query.js b/packages/strapi-database/lib/queries/create-query.js index 3ce86e962b..3a7204b9e8 100644 --- a/packages/strapi-database/lib/queries/create-query.js +++ b/packages/strapi-database/lib/queries/create-query.js @@ -26,6 +26,12 @@ class Query { return this.model.associations; } + async executeHook(hook, ...args) { + if (_.has(this.model, hook)) { + await this.model[hook](...args); + } + } + /** * Run custom database logic */ @@ -65,12 +71,6 @@ class Query { return result; } - async executeHook(hook, ...args) { - if (_.has(this.model, hook)) { - await this.model[hook](...args); - } - } - async create(params = {}, ...args) { const newParams = replaceIdByPrimaryKey(params, this.model); @@ -101,18 +101,33 @@ class Query { return entry; } - count(params = {}, ...args) { + async count(params = {}, ...args) { const newParams = replaceIdByPrimaryKey(params, this.model); - return this.connectorQuery.count(newParams, ...args); + + await this.executeHook('beforeCount', newParams, ...args); + const count = await this.connectorQuery.count(newParams, ...args); + await this.executeHook('afterCount', count); + + return count; } - search(params = {}, ...args) { + async search(params = {}, ...args) { const newParams = replaceIdByPrimaryKey(params, this.model); - return this.connectorQuery.search(newParams, ...args); + + await this.executeHook('beforeSearch', newParams, ...args); + const results = await this.connectorQuery.search(newParams, ...args); + await this.executeHook('afterSearch', results); + + return results; } - countSearch(params = {}, ...args) { + async countSearch(params = {}, ...args) { const newParams = replaceIdByPrimaryKey(params, this.model); - return this.connectorQuery.countSearch(newParams, ...args); + + await this.executeHook('beforeCountSearch', newParams, ...args); + const count = await this.connectorQuery.countSearch(newParams, ...args); + await this.executeHook('afterCountSearch', count); + + return count; } } From 9ddeaffed17df94e0ac5047e5514ee38b28eccf9 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Thu, 23 Apr 2020 18:14:12 +0200 Subject: [PATCH 03/12] Use a wrapper to cleanup Signed-off-by: Alexandre Bodin --- .../api/restaurant/models/Restaurant.js | 86 ++++---- .../lib/queries/create-query.js | 185 ++++++------------ packages/strapi-database/lib/utils/hooks.js | 20 ++ 3 files changed, 121 insertions(+), 170 deletions(-) create mode 100644 packages/strapi-database/lib/utils/hooks.js diff --git a/examples/getstarted/api/restaurant/models/Restaurant.js b/examples/getstarted/api/restaurant/models/Restaurant.js index 968936b2a8..c246a01d75 100755 --- a/examples/getstarted/api/restaurant/models/Restaurant.js +++ b/examples/getstarted/api/restaurant/models/Restaurant.js @@ -5,62 +5,52 @@ */ module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - // Before fetching a value. - // Fired before a `fetch` operation. - beforeFetch: async (...args) => { - console.log('beforeFetch', ...args); - }, - // After fetching a value. - // Fired after a `fetch` operation. - afterFetch: async (...args) => { - console.log('afterFetch', ...args); - }, - // Before fetching all values. - // Fired before a `fetchAll` operation. - beforeFetchAll: async (...args) => { - console.log('beforeFetchAll', ...args); - }, - // After fetching all values. - // Fired after a `fetchAll` operation. - afterFetchAll: async (...args) => { - console.log('afterFetchAll', ...args); - }, - // Before creating a value. - // Fired before an `insert` query. - beforeCreate: async (...args) => { + beforeCreate(...args) { console.log('beforeCreate', ...args); - args[0].name += ' - Coucou'; }, - // After creating a value. - // Fired after an `insert` query. - afterCreate: async (...args) => { + afterCreate(...args) { console.log('afterCreate', ...args); }, - // Before updating a value. - // Fired before an `update` query. - beforeUpdate: async (...args) => { + beforeUpdate(...args) { console.log('beforeUpdate', ...args); - args[1].name += ' - Coucou'; }, - // After updating a value. - // Fired after an `update` query. - afterUpdate: async (...args) => { + afterUpdate(...args) { console.log('afterUpdate', ...args); }, - // Before destroying a value. - // Fired before a `delete` query. - beforeDestroy: async (...args) => { - console.log('beforeDestroy', ...args); + beforeDelete(...args) { + console.log('beforeDelete', ...args); }, - // After destroying a value. - // Fired after a `delete` query. - afterDestroy: async (...args) => { - console.log('afterDestroy', ...args); + afterDelete(...args) { + console.log('afterDelete', ...args); + }, + beforeFind(...args) { + console.log('beforeFind', ...args); + }, + afterFind(...args) { + console.log('afterFind', ...args); + }, + beforeFindOne(...args) { + console.log('beforeFindOne', ...args); + }, + afterFindOne(...args) { + console.log('afterFindOne', ...args); + }, + beforeCount(...args) { + console.log('beforeCount', ...args); + }, + afterCount(...args) { + console.log('afterCount', ...args); + }, + beforeSearch(...args) { + console.log('beforeSearch', ...args); + }, + afterSearch(...args) { + console.log('afterSearch', ...args); + }, + beforeCountSearch(...args) { + console.log('beforeCountSearch', ...args); + }, + afterCountSearch(...args) { + console.log('afterCountSearch', ...args); }, }; diff --git a/packages/strapi-database/lib/queries/create-query.js b/packages/strapi-database/lib/queries/create-query.js index 3a7204b9e8..fe1015f727 100644 --- a/packages/strapi-database/lib/queries/create-query.js +++ b/packages/strapi-database/lib/queries/create-query.js @@ -1,133 +1,74 @@ 'use strict'; -const _ = require('lodash'); - const { replaceIdByPrimaryKey } = require('../utils/primary-key'); +const { executeBeforeHook, executeAfterHook } = require('../utils/hooks'); module.exports = function createQuery(opts) { - return new Query(opts); + const { model, connectorQuery } = opts; + + return { + get model() { + return model; + }, + + get orm() { + return model.orm; + }, + + get primaryKey() { + return model.primaryKey; + }, + + get associations() { + return model.associations; + }, + + /** + * Run custom database logic + */ + custom(mapping) { + if (typeof mapping === 'function') { + return mapping.bind(this, { model: this.model }); + } + + if (!mapping[this.orm]) { + throw new Error(`Missing mapping for orm ${this.orm}`); + } + + if (typeof mapping[this.orm] !== 'function') { + throw new Error(`Custom queries must be functions received ${typeof mapping[this.orm]}`); + } + + return mapping[this.model.orm].call(this, { model: this.model }); + }, + + create: wrapQuery({ hook: 'create', model, connectorQuery }), + update: wrapQuery({ hook: 'update', model, connectorQuery }), + delete: wrapQuery({ hook: 'delete', model, connectorQuery }), + find: wrapQuery({ hook: 'find', model, connectorQuery }), + findOne: wrapQuery({ hook: 'findOne', model, connectorQuery }), + count: wrapQuery({ hook: 'count', model, connectorQuery }), + search: wrapQuery({ hook: 'search', model, connectorQuery }), + countSearch: wrapQuery({ hook: 'countSearch', model, connectorQuery }), + }; }; -class Query { - constructor({ model, connectorQuery }) { - this.connectorQuery = connectorQuery; - this.model = model; - } +// wraps a connectorQuery call with: +// - param substitution +// - lifecycle hooks +const wrapQuery = ({ hook, model, connectorQuery }) => async (params, ...rest) => { + // substite id for primaryKey value in params + const newParams = replaceIdByPrimaryKey(params, model); - get orm() { - return this.model.orm; - } + // execute before hook + await executeBeforeHook(hook, model, newParams, ...rest); - get primaryKey() { - return this.model.primaryKey; - } + // execute query + const result = await connectorQuery[hook](newParams, ...rest); - get associations() { - return this.model.associations; - } + // execute after hook + await executeAfterHook(hook, model, result); - async executeHook(hook, ...args) { - if (_.has(this.model, hook)) { - await this.model[hook](...args); - } - } - - /** - * Run custom database logic - */ - custom(mapping) { - if (typeof mapping === 'function') { - return mapping.bind(this, { model: this.model }); - } - - if (!mapping[this.orm]) { - throw new Error(`Missing mapping for orm ${this.orm}`); - } - - if (typeof mapping[this.orm] !== 'function') { - throw new Error(`Custom queries must be functions received ${typeof mapping[this.orm]}`); - } - - return mapping[this.model.orm].call(this, { model: this.model }); - } - - async find(params = {}, ...args) { - const newParams = replaceIdByPrimaryKey(params, this.model); - - await this.executeHook('beforeFetchAll', newParams, ...args); - const results = await this.connectorQuery.find(newParams, ...args); - await this.executeHook('afterFetchAll', results); - - return results; - } - - async findOne(params = {}, ...args) { - const newParams = replaceIdByPrimaryKey(params, this.model); - - await this.executeHook('beforeFetch', newParams, ...args); - const result = await this.connectorQuery.findOne(newParams, ...args); - await this.executeHook('afterFetch', result); - - return result; - } - - async create(params = {}, ...args) { - const newParams = replaceIdByPrimaryKey(params, this.model); - - await this.executeHook('beforeCreate', newParams, ...args); - const entry = await this.connectorQuery.create(newParams, ...args); - await this.executeHook('afterCreate', entry); - - return entry; - } - - async update(params = {}, ...args) { - const newParams = replaceIdByPrimaryKey(params, this.model); - - await this.executeHook('beforeUpdate', newParams, ...args); - const entry = await this.connectorQuery.update(newParams, ...args); - await this.executeHook('afterUpdate', entry); - - return entry; - } - - async delete(params = {}, ...args) { - const newParams = replaceIdByPrimaryKey(params, this.model); - - await this.executeHook('beforeDestroy', newParams, ...args); - const entry = await this.connectorQuery.delete(newParams, ...args); - await this.executeHook('afterDestroy', entry); - - return entry; - } - - async count(params = {}, ...args) { - const newParams = replaceIdByPrimaryKey(params, this.model); - - await this.executeHook('beforeCount', newParams, ...args); - const count = await this.connectorQuery.count(newParams, ...args); - await this.executeHook('afterCount', count); - - return count; - } - - async search(params = {}, ...args) { - const newParams = replaceIdByPrimaryKey(params, this.model); - - await this.executeHook('beforeSearch', newParams, ...args); - const results = await this.connectorQuery.search(newParams, ...args); - await this.executeHook('afterSearch', results); - - return results; - } - - async countSearch(params = {}, ...args) { - const newParams = replaceIdByPrimaryKey(params, this.model); - - await this.executeHook('beforeCountSearch', newParams, ...args); - const count = await this.connectorQuery.countSearch(newParams, ...args); - await this.executeHook('afterCountSearch', count); - - return count; - } -} + // return result + return result; +}; diff --git a/packages/strapi-database/lib/utils/hooks.js b/packages/strapi-database/lib/utils/hooks.js new file mode 100644 index 0000000000..8cd266e62b --- /dev/null +++ b/packages/strapi-database/lib/utils/hooks.js @@ -0,0 +1,20 @@ +'use strict'; + +const _ = require('lodash'); + +const executeHook = async (hook, model, ...args) => { + if (_.has(model, hook)) { + await model[hook](...args); + } +}; + +const executeBeforeHook = (hook, model, ...args) => + executeHook(`before${_.upperFirst(hook)}`, model, ...args); + +const executeAfterHook = (hook, model, ...args) => + executeHook(`after${_.upperFirst(hook)}`, model, ...args); + +module.exports = { + executeBeforeHook, + executeAfterHook, +}; From ed060e2cea00227e021b3f3132f73b1734fdfae2 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Thu, 23 Apr 2020 18:23:27 +0200 Subject: [PATCH 04/12] Add arguments to after hook Signed-off-by: Alexandre Bodin --- packages/strapi-database/lib/queries/create-query.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/strapi-database/lib/queries/create-query.js b/packages/strapi-database/lib/queries/create-query.js index fe1015f727..fc6843b22e 100644 --- a/packages/strapi-database/lib/queries/create-query.js +++ b/packages/strapi-database/lib/queries/create-query.js @@ -59,15 +59,16 @@ module.exports = function createQuery(opts) { const wrapQuery = ({ hook, model, connectorQuery }) => async (params, ...rest) => { // substite id for primaryKey value in params const newParams = replaceIdByPrimaryKey(params, model); + const queryArguments = [newParams, ...rest]; // execute before hook - await executeBeforeHook(hook, model, newParams, ...rest); + await executeBeforeHook(hook, model, ...queryArguments); // execute query - const result = await connectorQuery[hook](newParams, ...rest); + const result = await connectorQuery[hook](...queryArguments); - // execute after hook - await executeAfterHook(hook, model, result); + // execute after hook with result and arguments + await executeAfterHook(hook, model, result, ...queryArguments); // return result return result; From 1edc581864150338e410895cf49e83e8a357ea18 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Fri, 24 Apr 2020 10:55:09 +0200 Subject: [PATCH 05/12] Remove lifecycles from connectors Signed-off-by: Alexandre Bodin --- .../lib/mount-models.js | 58 +------------------ .../lib/mount-models.js | 53 ----------------- .../lib/queries/create-query.js | 24 ++++---- 3 files changed, 13 insertions(+), 122 deletions(-) diff --git a/packages/strapi-connector-bookshelf/lib/mount-models.js b/packages/strapi-connector-bookshelf/lib/mount-models.js index 11a18a66c4..58296a28b5 100644 --- a/packages/strapi-connector-bookshelf/lib/mount-models.js +++ b/packages/strapi-connector-bookshelf/lib/mount-models.js @@ -16,21 +16,6 @@ const populateFetch = require('./populate'); const PIVOT_PREFIX = '_pivot_'; -// const LIFECYCLES = { -// creating: 'beforeCreate', -// created: 'afterCreate', -// destroying: 'beforeDestroy', -// destroyed: 'afterDestroy', -// updating: 'beforeUpdate', -// updated: 'afterUpdate', -// fetching: 'beforeFetch', -// 'fetching:collection': 'beforeFetchAll', -// fetched: 'afterFetch', -// 'fetched:collection': 'afterFetchAll', -// saving: 'beforeSave', -// saved: 'afterSave', -// }; - const getDatabaseName = connection => { const dbName = _.get(connection.settings, 'database'); const dbSchema = _.get(connection.settings, 'schema', 'public'); @@ -566,28 +551,12 @@ module.exports = ({ models, target }, ctx) => { // Load bookshelf plugin arguments from model options this.constructor.__super__.initialize.apply(this, arguments); - // _.forEach(LIFECYCLES, (fn, key) => { - // if (_.isFunction(target[model.toLowerCase()][fn])) { - // this.on(key, target[model.toLowerCase()][fn]); - // } - // }); - - // Update withRelated level to bypass many-to-many association for polymorphic relationshiips. - // Apply only during fetching. this.on('fetching fetching:collection', (instance, attrs, options) => { populateFetch(definition, options); - - // return _.isFunction(target[model.toLowerCase()]['beforeFetchAll']) - // ? target[model.toLowerCase()]['beforeFetchAll'] - // : Promise.resolve(); }); this.on('saving', (instance, attrs) => { instance.attributes = _.assign(instance.attributes, mapper(attrs)); - - return _.isFunction(target[model.toLowerCase()]['beforeSave']); - // ? target[model.toLowerCase()]['beforeSave'] - // : Promise.resolve(); }); const formatValue = createFormatter(definition.client); @@ -598,37 +567,12 @@ module.exports = ({ models, target }, ctx) => { }); } - function formatOutput(instance) { + this.on('saved fetched fetched:collection', instance => { if (Array.isArray(instance.models)) { instance.models.forEach(entry => formatEntry(entry)); } else { formatEntry(instance); } - } - - const events = [ - { - name: 'saved', - target: 'afterSave', - }, - { - name: 'fetched', - target: 'afterFetch', - }, - { - name: 'fetched:collection', - target: 'afterFetchAll', - }, - ]; - - events.forEach(event => { - this.on(event.name, instance => { - formatOutput(instance); - - // return _.isFunction(target[model.toLowerCase()][event.target]) - // ? target[model.toLowerCase()][event.target] - // : Promise.resolve(); - }); }); }; diff --git a/packages/strapi-connector-mongoose/lib/mount-models.js b/packages/strapi-connector-mongoose/lib/mount-models.js index e2f55dde6a..5df06149d8 100644 --- a/packages/strapi-connector-mongoose/lib/mount-models.js +++ b/packages/strapi-connector-mongoose/lib/mount-models.js @@ -82,19 +82,6 @@ module.exports = ({ models, target }, ctx) => { _.omitBy(definition.loadedModel, ({ type }) => type === 'virtual') ); - // Initialize lifecycle callbacks. - const preLifecycle = { - validate: 'beforeCreate', - find: 'beforeFetchAll', - findOne: 'beforeFetch', - findOneAndUpdate: 'beforeUpdate', - findOneAndRemove: 'beforeDestroy', - remove: 'beforeDestroy', - update: 'beforeUpdate', - updateOne: 'beforeUpdate', - save: 'beforeSave', - }; - const findLifecycles = ['find', 'findOne', 'findOneAndUpdate', 'findOneAndRemove']; /* @@ -102,7 +89,6 @@ module.exports = ({ models, target }, ctx) => { It allows us to make Upload.find().populate('related') instead of Upload.find().populate('related.item') */ - const morphAssociations = definition.associations.filter(isPolymorphicAssoc); const populateFn = createOnFetchPopulateFn({ @@ -115,45 +101,6 @@ module.exports = ({ models, target }, ctx) => { schema.pre(key, populateFn); }); - Object.keys(preLifecycle).forEach(key => { - const fn = preLifecycle[key]; - - if (_.isFunction(target[model.toLowerCase()][fn])) { - schema.pre(key, function() { - return target[model.toLowerCase()][fn](this); - }); - } - }); - - const postLifecycle = { - validate: 'afterCreate', - findOneAndRemove: 'afterDestroy', - remove: 'afterDestroy', - update: 'afterUpdate', - updateOne: 'afterUpdate', - find: 'afterFetchAll', - findOne: 'afterFetch', - save: 'afterSave', - }; - - // Mongoose doesn't allow post 'remove' event on model. - // See https://github.com/Automattic/mongoose/issues/3054 - Object.keys(postLifecycle).forEach(key => { - const fn = postLifecycle[key]; - - if (_.isFunction(target[model.toLowerCase()][fn])) { - schema.post(key, function(doc, next) { - target[model.toLowerCase()] - [fn](this, doc) - .then(next) - .catch(err => { - strapi.log.error(err); - next(err); - }); - }); - } - }); - // Add virtual key to provide populate and reverse populate _.forEach( _.pickBy(definition.loadedModel, model => { diff --git a/packages/strapi-database/lib/queries/create-query.js b/packages/strapi-database/lib/queries/create-query.js index fc6843b22e..7f7ea44506 100644 --- a/packages/strapi-database/lib/queries/create-query.js +++ b/packages/strapi-database/lib/queries/create-query.js @@ -42,33 +42,33 @@ module.exports = function createQuery(opts) { return mapping[this.model.orm].call(this, { model: this.model }); }, - create: wrapQuery({ hook: 'create', model, connectorQuery }), - update: wrapQuery({ hook: 'update', model, connectorQuery }), - delete: wrapQuery({ hook: 'delete', model, connectorQuery }), - find: wrapQuery({ hook: 'find', model, connectorQuery }), - findOne: wrapQuery({ hook: 'findOne', model, connectorQuery }), - count: wrapQuery({ hook: 'count', model, connectorQuery }), - search: wrapQuery({ hook: 'search', model, connectorQuery }), - countSearch: wrapQuery({ hook: 'countSearch', model, connectorQuery }), + create: createQueryWithHooks({ query: 'create', model, connectorQuery }), + update: createQueryWithHooks({ query: 'update', model, connectorQuery }), + delete: createQueryWithHooks({ query: 'delete', model, connectorQuery }), + find: createQueryWithHooks({ query: 'find', model, connectorQuery }), + findOne: createQueryWithHooks({ query: 'findOne', model, connectorQuery }), + count: createQueryWithHooks({ query: 'count', model, connectorQuery }), + search: createQueryWithHooks({ query: 'search', model, connectorQuery }), + countSearch: createQueryWithHooks({ query: 'countSearch', model, connectorQuery }), }; }; // wraps a connectorQuery call with: // - param substitution // - lifecycle hooks -const wrapQuery = ({ hook, model, connectorQuery }) => async (params, ...rest) => { +const createQueryWithHooks = ({ query, model, connectorQuery }) => async (params, ...rest) => { // substite id for primaryKey value in params const newParams = replaceIdByPrimaryKey(params, model); const queryArguments = [newParams, ...rest]; // execute before hook - await executeBeforeHook(hook, model, ...queryArguments); + await executeBeforeHook(query, model, ...queryArguments); // execute query - const result = await connectorQuery[hook](...queryArguments); + const result = await connectorQuery[query](...queryArguments); // execute after hook with result and arguments - await executeAfterHook(hook, model, result, ...queryArguments); + await executeAfterHook(query, model, result, ...queryArguments); // return result return result; From c364fcd0e21ab0cb84b81e0a580821a67e7ec28c Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Mon, 27 Apr 2020 19:54:57 +0200 Subject: [PATCH 06/12] Move lifecycles to sub field and added documentation Signed-off-by: Alexandre Bodin --- docs/3.0.0-beta.x/concepts/models.md | 271 +++++++++++++----- .../getstarted/api/address/models/Address.js | 39 +-- .../api/category/models/Category.js | 39 +-- .../getstarted/api/country/models/Country.js | 39 +-- .../api/homepage/models/homepage.js | 39 +-- examples/getstarted/api/like/models/Like.js | 39 +-- examples/getstarted/api/menu/models/Menu.js | 39 +-- .../api/menusection/models/Menusection.js | 39 +-- .../api/restaurant/models/Restaurant.js | 96 ++++--- .../getstarted/api/review/models/Review.js | 39 +-- packages/strapi-admin/models/Administrator.js | 38 +-- .../lib/queries/create-query.js | 6 +- packages/strapi-database/lib/utils/hooks.js | 20 -- .../strapi-database/lib/utils/lifecycles.js | 20 ++ .../templates/model.template | 53 +--- .../templates/model.template | 53 +--- packages/strapi-plugin-upload/models/File.js | 49 +--- .../models/Permission.js | 49 +--- .../models/Role.js | 49 +--- .../models/User.js | 49 +--- 20 files changed, 288 insertions(+), 777 deletions(-) delete mode 100644 packages/strapi-database/lib/utils/hooks.js create mode 100644 packages/strapi-database/lib/utils/lifecycles.js diff --git a/docs/3.0.0-beta.x/concepts/models.md b/docs/3.0.0-beta.x/concepts/models.md index 740682ecb5..555df42e1c 100644 --- a/docs/3.0.0-beta.x/concepts/models.md +++ b/docs/3.0.0-beta.x/concepts/models.md @@ -4,21 +4,18 @@ ### Content Type's models -Models are a representation of the database's structure and life cycle. They are split into two separate files. A JavaScript file that contains the life cycle callbacks, and a JSON one that represents the data stored in the database and their format. The models also allow you to define the relationships between them. +Models are a representation of the database's structure. They are split into two separate files. A JavaScript file that contains the model options (e.g: lifecycle hooks), and a JSON one that represents the data structure stored in the database. **Path โ€”** `./api/restaurant/models/Restaurant.js`. ```js module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - beforeSave: (model, attrs, options) => {}, - - // After saving a value. - // Fired after an `insert` or `update` query. - afterSave: (model, attrs, options) => {}, - - // ... and more + lifecycles: { + // Called before an entry is created + beforeCreate(data) {}, + // Called after an entry is created + afterCreated(result) {}, + }, }; ``` @@ -894,69 +891,203 @@ xhr.send( :::: -## Life cycle callbacks +## Lifecycle hooks -::: warning -The life cycle functions are based on the ORM life cycle and not on the Strapi request. -We are currently working on it to make it easier to use and understand. -Please check [this issue](https://github.com/strapi/strapi/issues/1443) on GitHub. -::: +The lifecycle hooks are functions that get triggered when the Strapi [`queries`](../concepts/queries.md) are called. They will get triggered automatically when you manage your content in the Admin Panel or when you develop custom code using `queries`ยท -The following events are available by default: +To configure a `ContentType` lifecycle hooks you can set a `lifecycles` key in the `{modelName}.js` file located at `./api/{apiName}/models/{modelName}.js` folder. -Callbacks on: +### Available Lifecycle hooks :::: tabs -::: tab save +::: tab find -`save` +**`beforeFind(params, populate)`** -- beforeSave -- afterSave +_Parameters:_ + +| Name | Type | Description | +| ------ | ------ | ----------------------------------- | +| params | Object | Find params _(e.g: limit, filters)_ | + +--- + +**`afterFind(results, params, populate)`** + +_Parameters:_ + +| Name | Type | Description | +| -------- | ------------- | -------------------------------------- | +| results | Array{Object} | The results found for the `find` query | +| params | Object | Find params _(e.g: limit, filters)_ | +| populate | Array{string} | Populate specific relations | ::: -::: tab fetch +::: tab findOne -`fetch` +**`beforeFindOne(params, populate)`** -- beforeFetch -- afterFetch +_Parameters:_ -::: tab fetchAll +| Name | Type | Description | +| ------ | ------ | ---------------------------- | +| params | Object | Find params _(e.g: filters)_ | -`fetchAll` +--- -- beforeFetchAll -- afterFetchAll +**`afterFindOne(result, params, populate)`** + +_Parameters:_ + +| Name | Type | Description | +| -------- | ------------- | ----------------------------------------- | +| result | Object | The results found for the `findOne` query | +| params | Object | Find params _(e.g: filters)_ | +| populate | Array{string} | Populate specific relations | ::: ::: tab create -`create` +**`beforeCreate(data)`** -- beforeCreate -- afterCreate +_Parameters:_ + +| Name | Type | Description | +| ---- | ------ | ---------------------------------------- | +| data | Object | Input data to the entry was created with | + +--- + +**`afterCreate(result, data)`** + +_Parameters:_ + +| Name | Type | Description | +| ------ | ------ | ---------------------------------------- | +| result | Object | Created entry | +| data | Object | Input data to the entry was created with | ::: ::: tab update -`update` +**`beforeUpdate(params, data)`** -- beforeUpdate -- afterUpdate +_Parameters:_ + +| Name | Type | Description | +| ------ | ------ | ---------------------------------------- | +| params | Object | Find params _(e.g: filters)_ | +| data | Object | Input data to the entry was created with | + +--- + +**`afterUpdate(result, params, data)`** + +_Parameters:_ + +| Name | Type | Description | +| ------ | ------ | ---------------------------------------- | +| result | Object | Updated entry | +| params | Object | Find params _(e.g: filters)_ | +| data | Object | Input data to the entry was created with | ::: -::: tab destroy +::: tab delete -`destroy` +**`beforeDeleted(params)`** -- beforeDestroy -- afterDestroy +_Parameters:_ + +| Name | Type | Description | +| ------ | ------ | ---------------------------- | +| params | Object | Find params _(e.g: filters)_ | + +--- + +**`afterDeleted(result, params)`** + +_Parameters:_ + +| Name | Type | Description | +| ------ | ------ | ---------------------------- | +| result | Object | Deleted entry | +| params | Object | Find params _(e.g: filters)_ | + +::: + +::: tab count + +**`beforeCount(params)`** + +_Parameters:_ + +| Name | Type | Description | +| ------ | ------ | ---------------------------- | +| params | Object | Find params _(e.g: filters)_ | + +--- + +**`afterCount(result, params)`** + +_Parameters:_ + +| Name | Type | Description | +| ------ | ------- | ---------------------------- | +| result | Integer | The count matching entries | +| params | Object | Find params _(e.g: filters)_ | + +::: + +::: tab search + +**`beforeSearch(params, populate)`** + +_Parameters:_ + +| Name | Type | Description | +| -------- | ------------- | ---------------------------- | +| params | Object | Find params _(e.g: filters)_ | +| populate | Array{string} | Populate specific relations | + +--- + +**`afterSearch(result, params)`** + +_Parameters:_ + +| Name | Type | Description | +| -------- | ------------- | ---------------------------- | +| results | Array{Object} | The entries found | +| params | Object | Find params _(e.g: filters)_ | +| populate | Array{string} | Populate specific relations | + +::: + +::: tab countSearch + +**`beforeCountSearch(params)`** + +_Parameters:_ + +| Name | Type | Description | +| ------ | ------ | ---------------------------- | +| params | Object | Find params _(e.g: filters)_ | + +--- + +**`afterCountSearch(result, params)`** + +_Parameters:_ + +| Name | Type | Description | +| ------ | ------- | ---------------------------- | +| result | Integer | The count matching entries | +| params | Object | Find params _(e.g: filters)_ | ::: @@ -964,14 +1095,6 @@ Callbacks on: ### Example -:::: tabs - -::: tab Mongoose - -#### Mongoose - -The entry is available through the `model` parameter. - **Path โ€”** `./api/user/models/User.js`. ```js @@ -979,43 +1102,43 @@ module.exports = { /** * Triggered before user creation. */ - beforeCreate: async model => { - // Hash password. - const passwordHashed = await strapi.api.user.services.user.hashPassword(model.password); - - // Set the password. - model.password = passwordHashed; + lifecycles: { + async beforeCreate(data) { + const passwordHashed = await strapi.api.user.services.user.hashPassword(data.password); + data.password = passwordHashed; + }, }, }; ``` -::: +::: tip +You can mutate one of the parameters to change its properties. Make sure not to reassign the parameter as it will have no effect: -::: tab Bookshelf - -#### Bookshelf - -Each of these functions receives three parameters `model`, `attrs` and `options`. You have to return a Promise. - -**Path โ€”** `./api/user/models/User.js`. +**This will Work** ```js module.exports = { - /** - * Triggered before user creation. - */ - beforeCreate: async (model, attrs, options) => { - // Hash password. - const passwordHashed = await strapi.api.user.services.user.hashPassword( - model.attributes.password - ); + lifecycles: { + beforeCreate(data) { + data.name = 'Some fixed name'; + }, + }, +}; +``` - // Set the password. - model.set('password', passwordHashed); +**This will NOT Work** + +```js +module.exports = { + lifecycles: { + beforeCreate(data) { + data = { + ...data, + name: 'Some fixed name', + }; + }, }, }; ``` ::: - -::::: diff --git a/examples/getstarted/api/address/models/Address.js b/examples/getstarted/api/address/models/Address.js index ce6fb1e32c..bc52d8a341 100755 --- a/examples/getstarted/api/address/models/Address.js +++ b/examples/getstarted/api/address/models/Address.js @@ -4,41 +4,4 @@ * Lifecycle callbacks for the `Address` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/examples/getstarted/api/category/models/Category.js b/examples/getstarted/api/category/models/Category.js index d0df0e92ba..62617ba29a 100755 --- a/examples/getstarted/api/category/models/Category.js +++ b/examples/getstarted/api/category/models/Category.js @@ -4,41 +4,4 @@ * Lifecycle callbacks for the `Category` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/examples/getstarted/api/country/models/Country.js b/examples/getstarted/api/country/models/Country.js index 3c3ab5978f..58a2c55910 100755 --- a/examples/getstarted/api/country/models/Country.js +++ b/examples/getstarted/api/country/models/Country.js @@ -4,41 +4,4 @@ * Lifecycle callbacks for the `Country` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/examples/getstarted/api/homepage/models/homepage.js b/examples/getstarted/api/homepage/models/homepage.js index be58617b7b..edf9c6ec1f 100644 --- a/examples/getstarted/api/homepage/models/homepage.js +++ b/examples/getstarted/api/homepage/models/homepage.js @@ -4,41 +4,4 @@ * Lifecycle callbacks for the `homepage` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/examples/getstarted/api/like/models/Like.js b/examples/getstarted/api/like/models/Like.js index 8d455e64de..0795c2a8cf 100755 --- a/examples/getstarted/api/like/models/Like.js +++ b/examples/getstarted/api/like/models/Like.js @@ -4,41 +4,4 @@ * Lifecycle callbacks for the `Like` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/examples/getstarted/api/menu/models/Menu.js b/examples/getstarted/api/menu/models/Menu.js index fba5d9427e..0b7720d725 100755 --- a/examples/getstarted/api/menu/models/Menu.js +++ b/examples/getstarted/api/menu/models/Menu.js @@ -4,41 +4,4 @@ * Lifecycle callbacks for the `Menu` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/examples/getstarted/api/menusection/models/Menusection.js b/examples/getstarted/api/menusection/models/Menusection.js index 87e8c61b42..1dec860efc 100755 --- a/examples/getstarted/api/menusection/models/Menusection.js +++ b/examples/getstarted/api/menusection/models/Menusection.js @@ -4,41 +4,4 @@ * Lifecycle callbacks for the `Menusection` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/examples/getstarted/api/restaurant/models/Restaurant.js b/examples/getstarted/api/restaurant/models/Restaurant.js index c246a01d75..46428ac4ce 100755 --- a/examples/getstarted/api/restaurant/models/Restaurant.js +++ b/examples/getstarted/api/restaurant/models/Restaurant.js @@ -5,52 +5,54 @@ */ module.exports = { - beforeCreate(...args) { - console.log('beforeCreate', ...args); - }, - afterCreate(...args) { - console.log('afterCreate', ...args); - }, - beforeUpdate(...args) { - console.log('beforeUpdate', ...args); - }, - afterUpdate(...args) { - console.log('afterUpdate', ...args); - }, - beforeDelete(...args) { - console.log('beforeDelete', ...args); - }, - afterDelete(...args) { - console.log('afterDelete', ...args); - }, - beforeFind(...args) { - console.log('beforeFind', ...args); - }, - afterFind(...args) { - console.log('afterFind', ...args); - }, - beforeFindOne(...args) { - console.log('beforeFindOne', ...args); - }, - afterFindOne(...args) { - console.log('afterFindOne', ...args); - }, - beforeCount(...args) { - console.log('beforeCount', ...args); - }, - afterCount(...args) { - console.log('afterCount', ...args); - }, - beforeSearch(...args) { - console.log('beforeSearch', ...args); - }, - afterSearch(...args) { - console.log('afterSearch', ...args); - }, - beforeCountSearch(...args) { - console.log('beforeCountSearch', ...args); - }, - afterCountSearch(...args) { - console.log('afterCountSearch', ...args); + lifecycles: { + beforeCreate(...args) { + // console.log('beforeCreate', ...args); + }, + afterCreate(...args) { + // console.log('afterCreate', ...args); + }, + beforeUpdate(...args) { + // console.log('beforeUpdate', ...args); + }, + afterUpdate(...args) { + // console.log('afterUpdate', ...args); + }, + beforeDelete(...args) { + // console.log('beforeDelete', ...args); + }, + afterDelete(...args) { + // console.log('afterDelete', ...args); + }, + beforeFind(...args) { + // console.log('beforeFind', ...args); + }, + afterFind(...args) { + // console.log('afterFind', ...args); + }, + beforeFindOne(...args) { + // console.log('beforeFindOne', ...args); + }, + afterFindOne(...args) { + // console.log('afterFindOne', ...args); + }, + beforeCount(...args) { + // console.log('beforeCount', ...args); + }, + afterCount(...args) { + // console.log('afterCount', ...args); + }, + beforeSearch(...args) { + // console.log('beforeSearch', ...args); + }, + afterSearch(...args) { + // console.log('afterSearch', ...args); + }, + beforeCountSearch(...args) { + // console.log('beforeCountSearch', ...args); + }, + afterCountSearch(...args) { + // console.log('afterCountSearch', ...args); + }, }, }; diff --git a/examples/getstarted/api/review/models/Review.js b/examples/getstarted/api/review/models/Review.js index cfde432548..ceec8280d6 100755 --- a/examples/getstarted/api/review/models/Review.js +++ b/examples/getstarted/api/review/models/Review.js @@ -4,41 +4,4 @@ * Lifecycle callbacks for the `Review` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/packages/strapi-admin/models/Administrator.js b/packages/strapi-admin/models/Administrator.js index eac6b1c04f..d2f6e2f054 100644 --- a/packages/strapi-admin/models/Administrator.js +++ b/packages/strapi-admin/models/Administrator.js @@ -4,40 +4,4 @@ * Lifecycle callbacks for the `Admin` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model) => {}, - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, result) => {}, - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model) => {}, - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, results) => {}, - // Fired before a `fetch` operation. - // beforeFetch: async (model) => {}, - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, result) => {}, - // Before creating a value. - // Fired before `insert` query. - // beforeCreate: async (model) => {}, - // After creating a value. - // Fired after `insert` query. - // afterCreate: async (model, result) => {}, - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model) => {}, - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, result) => {}, - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model) => {}, - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, result) => {} -}; +module.exports = {}; diff --git a/packages/strapi-database/lib/queries/create-query.js b/packages/strapi-database/lib/queries/create-query.js index 7f7ea44506..57b4227020 100644 --- a/packages/strapi-database/lib/queries/create-query.js +++ b/packages/strapi-database/lib/queries/create-query.js @@ -1,7 +1,7 @@ 'use strict'; const { replaceIdByPrimaryKey } = require('../utils/primary-key'); -const { executeBeforeHook, executeAfterHook } = require('../utils/hooks'); +const { executeBeforeLifecycleHook, executeAfterLifecycleHook } = require('../utils/lifecycles'); module.exports = function createQuery(opts) { const { model, connectorQuery } = opts; @@ -62,13 +62,13 @@ const createQueryWithHooks = ({ query, model, connectorQuery }) => async (params const queryArguments = [newParams, ...rest]; // execute before hook - await executeBeforeHook(query, model, ...queryArguments); + await executeBeforeLifecycleHook(query, model, ...queryArguments); // execute query const result = await connectorQuery[query](...queryArguments); // execute after hook with result and arguments - await executeAfterHook(query, model, result, ...queryArguments); + await executeAfterLifecycleHook(query, model, result, ...queryArguments); // return result return result; diff --git a/packages/strapi-database/lib/utils/hooks.js b/packages/strapi-database/lib/utils/hooks.js deleted file mode 100644 index 8cd266e62b..0000000000 --- a/packages/strapi-database/lib/utils/hooks.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -const _ = require('lodash'); - -const executeHook = async (hook, model, ...args) => { - if (_.has(model, hook)) { - await model[hook](...args); - } -}; - -const executeBeforeHook = (hook, model, ...args) => - executeHook(`before${_.upperFirst(hook)}`, model, ...args); - -const executeAfterHook = (hook, model, ...args) => - executeHook(`after${_.upperFirst(hook)}`, model, ...args); - -module.exports = { - executeBeforeHook, - executeAfterHook, -}; diff --git a/packages/strapi-database/lib/utils/lifecycles.js b/packages/strapi-database/lib/utils/lifecycles.js new file mode 100644 index 0000000000..f81795e5fb --- /dev/null +++ b/packages/strapi-database/lib/utils/lifecycles.js @@ -0,0 +1,20 @@ +'use strict'; + +const _ = require('lodash'); + +const executeLifecycleHook = async (lifecycle, model, ...args) => { + if (_.has(model, `lifecycles.${lifecycle}`)) { + await model.lifecycles[lifecycle](...args); + } +}; + +const executeBeforeLifecycleHook = (lifecycle, model, ...args) => + executeLifecycleHook(`before${_.upperFirst(lifecycle)}`, model, ...args); + +const executeAfterLifecycleHook = (lifecycle, model, ...args) => + executeLifecycleHook(`after${_.upperFirst(lifecycle)}`, model, ...args); + +module.exports = { + executeBeforeLifecycleHook, + executeAfterLifecycleHook, +}; diff --git a/packages/strapi-generate-api/templates/model.template b/packages/strapi-generate-api/templates/model.template index 2d9a6c98ee..d354d32f6f 100644 --- a/packages/strapi-generate-api/templates/model.template +++ b/packages/strapi-generate-api/templates/model.template @@ -1,55 +1,8 @@ 'use strict'; /** - * Lifecycle callbacks for the `<%= name %>` model. + * Read the documentation (https://strapi.io/documentation/3.0.0-beta.x/concepts/models.html#life-cycle-callbacks) + * to customize this model */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model, columns, options) => {}, - - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, response, options) => {}, - - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/packages/strapi-generate-model/templates/model.template b/packages/strapi-generate-model/templates/model.template index efcdb9e76a..d354d32f6f 100644 --- a/packages/strapi-generate-model/templates/model.template +++ b/packages/strapi-generate-model/templates/model.template @@ -1,55 +1,8 @@ 'use strict'; /** - * Lifecycle callbacks for the `<%= name %>` model. + * Read the documentation (https://strapi.io/documentation/3.0.0-beta.x/concepts/models.html#life-cycle-callbacks) + * to customize this model */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model, attrs, options) => {}, - - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, response, options) => {}, - - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchCollection: async (model, columns, options) => {}, - - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchCollection: async (model, columns, options) => {}, - - // Before fetching a value. - // Fired before a `fetch` operation. - // beforeFetch: async (model, columns, options) => {}, - - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, response, options) => {}, - - // Before creating a value. - // Fired before an `insert` query. - // beforeCreate: async (model, attrs, options) => {}, - - // After creating a value. - // Fired after an `insert` query. - // afterCreate: async (model, attrs, options) => {}, - - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model, attrs, options) => {}, - - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, attrs, options) => {}, - - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model, attrs, options) => {}, - - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, attrs, options) => {} -}; +module.exports = {}; diff --git a/packages/strapi-plugin-upload/models/File.js b/packages/strapi-plugin-upload/models/File.js index 63679b6d9e..507a6dd8b3 100644 --- a/packages/strapi-plugin-upload/models/File.js +++ b/packages/strapi-plugin-upload/models/File.js @@ -4,51 +4,4 @@ * Lifecycle callbacks for the `File` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model) => {}, - - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, result) => {}, - - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model) => {}, - - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, results) => {}, - - // Fired before a `fetch` operation. - // beforeFetch: async (model) => {}, - - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, result) => {}, - - // Before creating a value. - // Fired before `insert` query. - // beforeCreate: async (model) => {}, - - // After creating a value. - // Fired after `insert` query. - // afterCreate: async (model, result) => {}, - - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model) => {}, - - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, result) => {}, - - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model) => {}, - - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, result) => {} -}; +module.exports = {}; diff --git a/packages/strapi-plugin-users-permissions/models/Permission.js b/packages/strapi-plugin-users-permissions/models/Permission.js index 3bfb4f3730..11224b309a 100644 --- a/packages/strapi-plugin-users-permissions/models/Permission.js +++ b/packages/strapi-plugin-users-permissions/models/Permission.js @@ -4,51 +4,4 @@ * Lifecycle callbacks for the `Permission` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model) => {}, - - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, result) => {}, - - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model) => {}, - - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, results) => {}, - - // Fired before a `fetch` operation. - // beforeFetch: async (model) => {}, - - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, result) => {}, - - // Before creating a value. - // Fired before `insert` query. - // beforeCreate: async (model) => {}, - - // After creating a value. - // Fired after `insert` query. - // afterCreate: async (model, result) => {}, - - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model) => {}, - - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, result) => {}, - - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model) => {}, - - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, result) => {} -}; +module.exports = {}; diff --git a/packages/strapi-plugin-users-permissions/models/Role.js b/packages/strapi-plugin-users-permissions/models/Role.js index cb36c037bb..7a1fd601fc 100644 --- a/packages/strapi-plugin-users-permissions/models/Role.js +++ b/packages/strapi-plugin-users-permissions/models/Role.js @@ -4,51 +4,4 @@ * Lifecycle callbacks for the `Role` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model) => {}, - - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, result) => {}, - - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model) => {}, - - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, results) => {}, - - // Fired before a `fetch` operation. - // beforeFetch: async (model) => {}, - - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, result) => {}, - - // Before creating a value. - // Fired before `insert` query. - // beforeCreate: async (model) => {}, - - // After creating a value. - // Fired after `insert` query. - // afterCreate: async (model, result) => {}, - - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model) => {}, - - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, result) => {}, - - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model) => {}, - - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, result) => {} -}; +module.exports = {}; diff --git a/packages/strapi-plugin-users-permissions/models/User.js b/packages/strapi-plugin-users-permissions/models/User.js index 478b6808e1..2786146240 100644 --- a/packages/strapi-plugin-users-permissions/models/User.js +++ b/packages/strapi-plugin-users-permissions/models/User.js @@ -4,51 +4,4 @@ * Lifecycle callbacks for the `User` model. */ -module.exports = { - // Before saving a value. - // Fired before an `insert` or `update` query. - // beforeSave: async (model) => {}, - - // After saving a value. - // Fired after an `insert` or `update` query. - // afterSave: async (model, result) => {}, - - // Before fetching all values. - // Fired before a `fetchAll` operation. - // beforeFetchAll: async (model) => {}, - - // After fetching all values. - // Fired after a `fetchAll` operation. - // afterFetchAll: async (model, results) => {}, - - // Fired before a `fetch` operation. - // beforeFetch: async (model) => {}, - - // After fetching a value. - // Fired after a `fetch` operation. - // afterFetch: async (model, result) => {}, - - // Before creating a value. - // Fired before `insert` query. - // beforeCreate: async (model) => {}, - - // After creating a value. - // Fired after `insert` query. - // afterCreate: async (model, result) => {}, - - // Before updating a value. - // Fired before an `update` query. - // beforeUpdate: async (model) => {}, - - // After updating a value. - // Fired after an `update` query. - // afterUpdate: async (model, result) => {}, - - // Before destroying a value. - // Fired before a `delete` query. - // beforeDestroy: async (model) => {}, - - // After destroying a value. - // Fired after a `delete` query. - // afterDestroy: async (model, result) => {} -}; +module.exports = {}; From a78c8e4636e069aab88157e233e8fa0137f478a9 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Tue, 28 Apr 2020 14:05:54 +0200 Subject: [PATCH 07/12] Add tests for lifecycles calls Signed-off-by: Alexandre Bodin --- .../queries/__tests__/create-query.test.js | 65 +++++++++++++++++++ .../lib/queries/create-query.js | 5 ++ 2 files changed, 70 insertions(+) create mode 100644 packages/strapi-database/lib/queries/__tests__/create-query.test.js diff --git a/packages/strapi-database/lib/queries/__tests__/create-query.test.js b/packages/strapi-database/lib/queries/__tests__/create-query.test.js new file mode 100644 index 0000000000..48b7b20972 --- /dev/null +++ b/packages/strapi-database/lib/queries/__tests__/create-query.test.js @@ -0,0 +1,65 @@ +'use strict'; + +const _ = require('lodash'); +const createQuery = require('../create-query'); + +describe('Database queries', () => { + describe('Subsitute id with primaryKey in paramters', () => { + test.each(['create', 'update', 'delete', 'find', 'findOne', 'search', 'count', 'countSearch'])( + 'Calling "%s" replaces id by the primaryKey in the params of the model before calling the underlying connector', + async method => { + const model = { + primaryKey: 'testId', + }; + const params = { + id: 'someValue', + }; + + const connectorQuery = { + [method]: jest.fn(() => Promise.resolve({})), + }; + + const query = createQuery({ model, connectorQuery }); + + await query[method](params); + + expect(connectorQuery[method]).toHaveBeenCalledWith({ + testId: 'someValue', + }); + } + ); + }); + + describe('Lifecycles', () => { + test.each(['create', 'update', 'delete', 'find', 'findOne', 'search', 'count', 'countSearch'])( + 'Calling "%s" calls the before adn after lifecycle hooks with the correct arguments', + async method => { + const arg1 = {}; + const arg2 = {}; + const output = {}; + const beforeLifecycleMethod = jest.fn(); + const afterLifecycleMethod = jest.fn(); + const queryMethod = jest.fn(() => Promise.resolve(output)); + + const model = { + lifecycles: { + [`before${_.upperFirst(method)}`]: beforeLifecycleMethod, + [`after${_.upperFirst(method)}`]: afterLifecycleMethod, + }, + }; + + const connectorQuery = { + [method]: queryMethod, + }; + + const query = createQuery({ model, connectorQuery }); + + await query[method](arg1, arg2); + + expect(queryMethod).toHaveBeenCalledWith(arg1, arg2); + expect(beforeLifecycleMethod).toHaveBeenCalledWith(arg1, arg2); + expect(afterLifecycleMethod).toHaveBeenCalledWith(output, arg1, arg2); + } + ); + }); +}); diff --git a/packages/strapi-database/lib/queries/create-query.js b/packages/strapi-database/lib/queries/create-query.js index 57b4227020..87261cade4 100644 --- a/packages/strapi-database/lib/queries/create-query.js +++ b/packages/strapi-database/lib/queries/create-query.js @@ -3,6 +3,11 @@ const { replaceIdByPrimaryKey } = require('../utils/primary-key'); const { executeBeforeLifecycleHook, executeAfterLifecycleHook } = require('../utils/lifecycles'); +/** + * @param {Object} opts options + * @param {Object} opts.model The ORM model + * @param {Object} opts.connectorQuery The ORM queries implementation + */ module.exports = function createQuery(opts) { const { model, connectorQuery } = opts; From 079ad897812b0ca205bb5f5c083d4fdcd9464ab9 Mon Sep 17 00:00:00 2001 From: Convly Date: Thu, 30 Apr 2020 16:37:18 +0200 Subject: [PATCH 08/12] Prune obsolete files relations on startup Signed-off-by: Convly --- .../config/functions/bootstrap.js | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/strapi-plugin-upload/config/functions/bootstrap.js b/packages/strapi-plugin-upload/config/functions/bootstrap.js index 577975266c..562c9fa82c 100644 --- a/packages/strapi-plugin-upload/config/functions/bootstrap.js +++ b/packages/strapi-plugin-upload/config/functions/bootstrap.js @@ -1,15 +1,18 @@ 'use strict'; /** - * Upload plugin bootstrapi. + * Upload plugin bootstrap. * * It initializes the provider and sets the default settings in db. */ module.exports = async () => { + const type = 'plugin'; + const name = 'upload'; + // set plugin store const configurator = strapi.store({ - type: 'plugin', - name: 'upload', + type, + name, key: 'settings', }); @@ -26,6 +29,8 @@ module.exports = async () => { }, }); } + + await pruneObsoleteRelations(name); }; const createProvider = ({ provider, providerOptions }) => { @@ -52,3 +57,23 @@ const baseProvider = { throw new Error('Provider delete method is not implemented'); }, }; + +const pruneObsoleteRelations = async name => { + const { orm } = strapi.plugins[name].models.file; + + if (orm !== 'mongoose') { + return; + } + + await strapi.query('file', 'upload').custom(pruneObsoleteRelationsQuery)(); +}; + +const pruneObsoleteRelationsQuery = ({ model }) => { + const models = Array.from(strapi.db.models.values()); + const modelsId = models.map(model => model.globalId); + + return model.updateMany( + { related: { $elemMatch: { kind: { $nin: modelsId } } } }, + { $pull: { related: { kind: { $nin: modelsId } } } } + ); +}; From 4046e4227c7bb243fce172f52fa83dc6bf74339b Mon Sep 17 00:00:00 2001 From: Convly Date: Thu, 30 Apr 2020 16:48:49 +0200 Subject: [PATCH 09/12] Assign type value directly in configurator properties Signed-off-by: Convly --- packages/strapi-plugin-upload/config/functions/bootstrap.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/strapi-plugin-upload/config/functions/bootstrap.js b/packages/strapi-plugin-upload/config/functions/bootstrap.js index 562c9fa82c..77c6f8582f 100644 --- a/packages/strapi-plugin-upload/config/functions/bootstrap.js +++ b/packages/strapi-plugin-upload/config/functions/bootstrap.js @@ -6,12 +6,11 @@ */ module.exports = async () => { - const type = 'plugin'; const name = 'upload'; // set plugin store const configurator = strapi.store({ - type, + type: 'plugin', name, key: 'settings', }); From 8a2050add05d12bdb9ed68e380bde1ec9c24ca75 Mon Sep 17 00:00:00 2001 From: Convly Date: Mon, 4 May 2020 11:09:59 +0200 Subject: [PATCH 10/12] Apply PR feedback and fix tests Signed-off-by: Convly --- .../config/functions/bootstrap.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/strapi-plugin-upload/config/functions/bootstrap.js b/packages/strapi-plugin-upload/config/functions/bootstrap.js index 77c6f8582f..35f2b9d9a9 100644 --- a/packages/strapi-plugin-upload/config/functions/bootstrap.js +++ b/packages/strapi-plugin-upload/config/functions/bootstrap.js @@ -6,12 +6,10 @@ */ module.exports = async () => { - const name = 'upload'; - // set plugin store const configurator = strapi.store({ type: 'plugin', - name, + name: 'upload', key: 'settings', }); @@ -29,7 +27,7 @@ module.exports = async () => { }); } - await pruneObsoleteRelations(name); + await pruneObsoleteRelations(); }; const createProvider = ({ provider, providerOptions }) => { @@ -57,10 +55,11 @@ const baseProvider = { }, }; -const pruneObsoleteRelations = async name => { - const { orm } = strapi.plugins[name].models.file; +const pruneObsoleteRelations = async () => { + const { upload: plugin } = strapi.plugins; + const modelIsNotDefined = !plugin || !plugin.models || !plugin.models.file; - if (orm !== 'mongoose') { + if (modelIsNotDefined || plugin.models.file.orm !== 'mongoose') { return; } From a615f16bb5800bc83b0d815d70e8d7a8402a2510 Mon Sep 17 00:00:00 2001 From: Convly Date: Mon, 4 May 2020 11:27:52 +0200 Subject: [PATCH 11/12] Move orm condition directly into the pruneObsoleteRelations query Signed-off-by: Convly --- .../strapi-plugin-upload/config/functions/bootstrap.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/strapi-plugin-upload/config/functions/bootstrap.js b/packages/strapi-plugin-upload/config/functions/bootstrap.js index 35f2b9d9a9..68f8652b4f 100644 --- a/packages/strapi-plugin-upload/config/functions/bootstrap.js +++ b/packages/strapi-plugin-upload/config/functions/bootstrap.js @@ -59,14 +59,18 @@ const pruneObsoleteRelations = async () => { const { upload: plugin } = strapi.plugins; const modelIsNotDefined = !plugin || !plugin.models || !plugin.models.file; - if (modelIsNotDefined || plugin.models.file.orm !== 'mongoose') { - return; + if (modelIsNotDefined) { + return Promise.resolve(); } await strapi.query('file', 'upload').custom(pruneObsoleteRelationsQuery)(); }; const pruneObsoleteRelationsQuery = ({ model }) => { + if (model.orm !== 'mongoose') { + return Promise.resolve(); + } + const models = Array.from(strapi.db.models.values()); const modelsId = models.map(model => model.globalId); From b928013e43c95f1ce8e4dee9bf2440a7a44163b2 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Mon, 4 May 2020 11:16:19 +0200 Subject: [PATCH 12/12] Cleanup code and doc Signed-off-by: Alexandre Bodin --- docs/3.0.0-beta.x/concepts/models.md | 25 ++++++++++++++++++ .../lib/queries/create-query.js | 26 +++++++++---------- .../strapi-database/lib/utils/lifecycles.js | 14 +++++----- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/docs/3.0.0-beta.x/concepts/models.md b/docs/3.0.0-beta.x/concepts/models.md index 555df42e1c..f6ad00f224 100644 --- a/docs/3.0.0-beta.x/concepts/models.md +++ b/docs/3.0.0-beta.x/concepts/models.md @@ -1142,3 +1142,28 @@ module.exports = { ``` ::: + +### Custom use + +When you are building custom ORM specific queries the lifecycles will not be triggered. You can however call a lifecycle function directly if you wish. + +**Bookshelf example** + +**Path -** `./api/{apiName}/services/{serviceName}.js` + +```js +module.exports = { + async createCustomEntry() { + const ORMModel = strapi.query(modelName).model; + + const newCustomEntry = await ORMModel.forge().save(); + + // trigger manually + ORMModel.lifecycles.afterCreate(newCustomEntry.toJSON()); + }, +}; +``` + +::: tip +When calling a lifecycle function directly, you will need to make sur you call it with the expected parameters. +::: diff --git a/packages/strapi-database/lib/queries/create-query.js b/packages/strapi-database/lib/queries/create-query.js index 87261cade4..dfac5c423c 100644 --- a/packages/strapi-database/lib/queries/create-query.js +++ b/packages/strapi-database/lib/queries/create-query.js @@ -1,7 +1,7 @@ 'use strict'; const { replaceIdByPrimaryKey } = require('../utils/primary-key'); -const { executeBeforeLifecycleHook, executeAfterLifecycleHook } = require('../utils/lifecycles'); +const { executeBeforeLifecycle, executeAfterLifecycle } = require('../utils/lifecycles'); /** * @param {Object} opts options @@ -47,33 +47,33 @@ module.exports = function createQuery(opts) { return mapping[this.model.orm].call(this, { model: this.model }); }, - create: createQueryWithHooks({ query: 'create', model, connectorQuery }), - update: createQueryWithHooks({ query: 'update', model, connectorQuery }), - delete: createQueryWithHooks({ query: 'delete', model, connectorQuery }), - find: createQueryWithHooks({ query: 'find', model, connectorQuery }), - findOne: createQueryWithHooks({ query: 'findOne', model, connectorQuery }), - count: createQueryWithHooks({ query: 'count', model, connectorQuery }), - search: createQueryWithHooks({ query: 'search', model, connectorQuery }), - countSearch: createQueryWithHooks({ query: 'countSearch', model, connectorQuery }), + create: createQueryWithLifecycles({ query: 'create', model, connectorQuery }), + update: createQueryWithLifecycles({ query: 'update', model, connectorQuery }), + delete: createQueryWithLifecycles({ query: 'delete', model, connectorQuery }), + find: createQueryWithLifecycles({ query: 'find', model, connectorQuery }), + findOne: createQueryWithLifecycles({ query: 'findOne', model, connectorQuery }), + count: createQueryWithLifecycles({ query: 'count', model, connectorQuery }), + search: createQueryWithLifecycles({ query: 'search', model, connectorQuery }), + countSearch: createQueryWithLifecycles({ query: 'countSearch', model, connectorQuery }), }; }; // wraps a connectorQuery call with: // - param substitution // - lifecycle hooks -const createQueryWithHooks = ({ query, model, connectorQuery }) => async (params, ...rest) => { - // substite id for primaryKey value in params +const createQueryWithLifecycles = ({ query, model, connectorQuery }) => async (params, ...rest) => { + // substitute id for primaryKey value in params const newParams = replaceIdByPrimaryKey(params, model); const queryArguments = [newParams, ...rest]; // execute before hook - await executeBeforeLifecycleHook(query, model, ...queryArguments); + await executeBeforeLifecycle(query, model, ...queryArguments); // execute query const result = await connectorQuery[query](...queryArguments); // execute after hook with result and arguments - await executeAfterLifecycleHook(query, model, result, ...queryArguments); + await executeAfterLifecycle(query, model, result, ...queryArguments); // return result return result; diff --git a/packages/strapi-database/lib/utils/lifecycles.js b/packages/strapi-database/lib/utils/lifecycles.js index f81795e5fb..5875fd89f8 100644 --- a/packages/strapi-database/lib/utils/lifecycles.js +++ b/packages/strapi-database/lib/utils/lifecycles.js @@ -2,19 +2,19 @@ const _ = require('lodash'); -const executeLifecycleHook = async (lifecycle, model, ...args) => { +const executeLifecycle = async (lifecycle, model, ...args) => { if (_.has(model, `lifecycles.${lifecycle}`)) { await model.lifecycles[lifecycle](...args); } }; -const executeBeforeLifecycleHook = (lifecycle, model, ...args) => - executeLifecycleHook(`before${_.upperFirst(lifecycle)}`, model, ...args); +const executeBeforeLifecycle = (lifecycle, model, ...args) => + executeLifecycle(`before${_.upperFirst(lifecycle)}`, model, ...args); -const executeAfterLifecycleHook = (lifecycle, model, ...args) => - executeLifecycleHook(`after${_.upperFirst(lifecycle)}`, model, ...args); +const executeAfterLifecycle = (lifecycle, model, ...args) => + executeLifecycle(`after${_.upperFirst(lifecycle)}`, model, ...args); module.exports = { - executeBeforeLifecycleHook, - executeAfterLifecycleHook, + executeBeforeLifecycle, + executeAfterLifecycle, };