Introduced model._changed

refs #9248

- Bookshelf gives access to ".changed" before the update
  - Discussion: https://github.com/bookshelf/bookshelf/issues/1943
- We also need to know what has changed after the update to be able to decide if we should trigger events
- Furthermore: Bookshelf cannot handle relation updates, it always marks relations as changed even though they did not change
- Bumped bookshelf-relations to be able to
  - know if relations were updated
  - ensure we unset relations on bookshelf's ".changed"
This commit is contained in:
kirrg001 2019-02-02 10:24:53 +01:00
parent d44d93a19d
commit 3289dc7619
5 changed files with 52 additions and 41 deletions

View File

@ -53,6 +53,7 @@ ghostBookshelf.plugin(plugins.hasPosts);
ghostBookshelf.plugin('bookshelf-relations', {
allowedOptions: ['context', 'importing', 'migrating'],
unsetRelations: true,
extendChanged: '_changed',
hooks: {
belongsToMany: {
after: function (existing, targets, options) {
@ -191,7 +192,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
self.on(eventName, self[functionName]);
});
// NOTE: Please keep here. If we don't initialize the parent, bookshelf-relations won't work.
// @NOTE: Please keep here. If we don't initialize the parent, bookshelf-relations won't work.
proto.initialize.call(this);
},
@ -279,23 +280,27 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
*
* @deprecated: x_by fields (https://github.com/TryGhost/Ghost/issues/10286)
*/
onUpdating: function onUpdating(newObj, attr, options) {
onUpdating: function onUpdating(model, attr, options) {
if (this.relationships) {
model.changed = _.omit(model.changed, this.relationships);
}
if (schema.tables[this.tableName].hasOwnProperty('updated_by')) {
if (!options.importing && !options.migrating) {
this.set('updated_by', this.contextUser(options));
}
}
if (options && options.context && !options.internal && !options.importing) {
if (options && options.context && !options.context.internal && !options.importing) {
if (schema.tables[this.tableName].hasOwnProperty('created_at')) {
if (newObj.hasDateChanged('created_at', {beforeWrite: true})) {
newObj.set('created_at', this.previous('created_at'));
if (model.hasDateChanged('created_at', {beforeWrite: true})) {
model.set('created_at', this.previous('created_at'));
}
}
if (schema.tables[this.tableName].hasOwnProperty('created_by')) {
if (newObj.hasChanged('created_by')) {
newObj.set('created_by', this.previous('created_by'));
if (model.hasChanged('created_by')) {
model.set('created_by', this.previous('created_by'));
}
}
}
@ -303,13 +308,16 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
// CASE: do not allow setting only the `updated_at` field, exception: importing
if (schema.tables[this.tableName].hasOwnProperty('updated_at') && !options.importing) {
if (options.migrating) {
newObj.set('updated_at', newObj.previous('updated_at'));
} else if (newObj.hasChanged() && Object.keys(newObj.changed).length === 1 && newObj.changed.updated_at) {
newObj.set('updated_at', newObj.previous('updated_at'));
model.set('updated_at', model.previous('updated_at'));
} else if (Object.keys(model.changed).length === 1 && model.changed.updated_at) {
model.set('updated_at', model.previous('updated_at'));
delete model.changed.updated_at;
}
}
return Promise.resolve(this.onValidate(newObj, attr, options));
model._changed = _.cloneDeep(model.changed);
return Promise.resolve(this.onValidate(model, attr, options));
},
/**

View File

@ -46,30 +46,34 @@ module.exports = function (Bookshelf) {
* HTML is always auto generated, ignore.
*/
parentSync.update = function update() {
var changed = _.omit(self.changed, [
'created_at', 'updated_at', 'author_id', 'id',
'published_by', 'updated_by', 'html', 'plaintext'
]),
clientUpdatedAt = moment(self.clientData.updated_at || self.serverData.updated_at || new Date()),
serverUpdatedAt = moment(self.serverData.updated_at || clientUpdatedAt);
return originalUpdateSync.apply(this, arguments)
.then((response) => {
const changed = _.omit(self._changed, [
'created_at', 'updated_at', 'author_id', 'id',
'published_by', 'updated_by', 'html', 'plaintext'
]);
if (Object.keys(changed).length) {
if (clientUpdatedAt.diff(serverUpdatedAt) !== 0) {
// @TODO: Remove later. We want to figure out how many people experience the error in which context.
return Promise.reject(new common.errors.UpdateCollisionError({
message: 'Saving failed! Someone else is editing this post.',
code: 'UPDATE_COLLISION',
level: 'critical',
context: JSON.stringify({
clientUpdatedAt: self.clientData.updated_at,
serverUpdatedAt: self.serverData.updated_at,
changed: changed
})
}));
}
}
const clientUpdatedAt = moment(self.clientData.updated_at || self.serverData.updated_at || new Date());
const serverUpdatedAt = moment(self.serverData.updated_at || clientUpdatedAt);
return originalUpdateSync.apply(this, arguments);
if (Object.keys(changed).length) {
if (clientUpdatedAt.diff(serverUpdatedAt) !== 0) {
// @NOTE: This will rollback the update. We cannot know if relations were updated before doing the update.
return Promise.reject(new common.errors.UpdateCollisionError({
message: 'Saving failed! Someone else is editing this post.',
code: 'UPDATE_COLLISION',
level: 'critical',
context: JSON.stringify({
clientUpdatedAt: self.clientData.updated_at,
serverUpdatedAt: self.serverData.updated_at,
changed: changed
})
}));
}
}
return response;
});
};
return parentSync;

View File

@ -157,7 +157,6 @@ module.exports.extendModel = function extendModel(Post, Posts, ghostBookshelf) {
serialize: function serialize(options) {
const authors = this.related('authors');
let attrs = proto.serialize.call(this, options);
// CASE: e.g. you stub model response in the test

View File

@ -41,7 +41,7 @@
"bluebird": "3.5.3",
"body-parser": "1.18.3",
"bookshelf": "0.14.2",
"bookshelf-relations": "0.2.1",
"bookshelf-relations": "1.0.0",
"brute-knex": "3.0.1",
"bson-objectid": "1.2.4",
"chalk": "2.4.2",

View File

@ -532,13 +532,13 @@ body@^5.1.0:
raw-body "~1.1.0"
safe-json-parse "~1.0.1"
bookshelf-relations@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/bookshelf-relations/-/bookshelf-relations-0.2.1.tgz#6974c0ec92409807d2cf6e73e111a38ebc03e7cd"
bookshelf-relations@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/bookshelf-relations/-/bookshelf-relations-1.0.0.tgz#709b49a8b51bbe30873a453b6b18c74858e52839"
dependencies:
bluebird "^3.4.1"
ghost-ignition "2.9.2"
lodash "4.17.10"
bluebird "3.5.3"
ghost-ignition "3.0.0"
lodash "4.17.11"
bookshelf@0.14.2:
version "0.14.2"