Merge branch 'develop' of github.com:strapi/strapi into front/media-lib-loader

This commit is contained in:
Ky 2020-05-05 00:54:56 +02:00
commit 18ac81057b
23 changed files with 473 additions and 927 deletions

View File

@ -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,68 @@ 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',
};
},
},
};
```
:::
:::::
### 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.
:::

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -5,40 +5,54 @@
*/
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) => {}
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);
},
},
};

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -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();
});
});
};

View File

@ -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 => {

View File

@ -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);
}
);
});
});

View File

@ -1,85 +1,80 @@
'use strict';
const { replaceIdByPrimaryKey } = require('../utils/primary-key');
const { executeBeforeLifecycle, executeAfterLifecycle } = 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) {
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: 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 }),
};
};
class Query {
constructor({ model, connectorQuery }) {
this.connectorQuery = connectorQuery;
this.model = model;
}
// wraps a connectorQuery call with:
// - param substitution
// - lifecycle hooks
const createQueryWithLifecycles = ({ query, model, connectorQuery }) => async (params, ...rest) => {
// substitute id for primaryKey value in params
const newParams = replaceIdByPrimaryKey(params, model);
const queryArguments = [newParams, ...rest];
get orm() {
return this.model.orm;
}
// execute before hook
await executeBeforeLifecycle(query, model, ...queryArguments);
get primaryKey() {
return this.model.primaryKey;
}
// execute query
const result = await connectorQuery[query](...queryArguments);
get associations() {
return this.model.associations;
}
// execute after hook with result and arguments
await executeAfterLifecycle(query, model, result, ...queryArguments);
/**
* 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 });
}
find(params = {}, ...args) {
const newParams = replaceIdByPrimaryKey(params, this.model);
return this.connectorQuery.find(newParams, ...args);
}
findOne(params = {}, ...args) {
const newParams = replaceIdByPrimaryKey(params, this.model);
return this.connectorQuery.findOne(newParams, ...args);
}
create(params = {}, ...args) {
const newParams = replaceIdByPrimaryKey(params, this.model);
return this.connectorQuery.create(newParams, ...args);
}
update(params = {}, ...args) {
const newParams = replaceIdByPrimaryKey(params, this.model);
return this.connectorQuery.update(newParams, ...args);
}
delete(params = {}, ...args) {
const newParams = replaceIdByPrimaryKey(params, this.model);
return this.connectorQuery.delete(newParams, ...args);
}
count(params = {}, ...args) {
const newParams = replaceIdByPrimaryKey(params, this.model);
return this.connectorQuery.count(newParams, ...args);
}
search(params = {}, ...args) {
const newParams = replaceIdByPrimaryKey(params, this.model);
return this.connectorQuery.search(newParams, ...args);
}
countSearch(params = {}, ...args) {
const newParams = replaceIdByPrimaryKey(params, this.model);
return this.connectorQuery.countSearch(newParams, ...args);
}
}
// return result
return result;
};

View File

@ -0,0 +1,20 @@
'use strict';
const _ = require('lodash');
const executeLifecycle = async (lifecycle, model, ...args) => {
if (_.has(model, `lifecycles.${lifecycle}`)) {
await model.lifecycles[lifecycle](...args);
}
};
const executeBeforeLifecycle = (lifecycle, model, ...args) =>
executeLifecycle(`before${_.upperFirst(lifecycle)}`, model, ...args);
const executeAfterLifecycle = (lifecycle, model, ...args) =>
executeLifecycle(`after${_.upperFirst(lifecycle)}`, model, ...args);
module.exports = {
executeBeforeLifecycle,
executeAfterLifecycle,
};

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -1,6 +1,6 @@
'use strict';
/**
* Upload plugin bootstrapi.
* Upload plugin bootstrap.
*
* It initializes the provider and sets the default settings in db.
*/
@ -26,6 +26,8 @@ module.exports = async () => {
},
});
}
await pruneObsoleteRelations();
};
const createProvider = ({ provider, providerOptions }) => {
@ -52,3 +54,28 @@ const baseProvider = {
throw new Error('Provider delete method is not implemented');
},
};
const pruneObsoleteRelations = async () => {
const { upload: plugin } = strapi.plugins;
const modelIsNotDefined = !plugin || !plugin.models || !plugin.models.file;
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);
return model.updateMany(
{ related: { $elemMatch: { kind: { $nin: modelsId } } } },
{ $pull: { related: { kind: { $nin: modelsId } } } }
);
};

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -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 = {};

View File

@ -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 = {};