Extracted ordering into separate bookshelf plugin

refs #11729

- Having a plugin allows to reason about ordering better and gives way
to further extraction away form the core.
- Just like with filtering, ordering falls into similar category of having effect on knex'es query builder object extension through additional statements - ORDER BY in this case.
- Because there were bugs associated with use of permittedAttributes inside of `parseOrderOption` method, introduced separate `orderAttributes` function which returns only those fields which are orderable (https://github.com/TryGhost/Ghost/issues/11729#issuecomment-685740836)
This commit is contained in:
Nazar Gargol 2020-09-08 16:15:47 +12:00
parent 5766b39be6
commit 84c8bcc457
3 changed files with 56 additions and 38 deletions

View File

@ -43,6 +43,9 @@ ghostBookshelf.plugin(plugins.customQuery);
// Load the Ghost filter plugin, which handles applying a 'filter' to findPage requests
ghostBookshelf.plugin(plugins.filter);
// Load the Ghost filter plugin, which handles applying a 'order' to findPage requests
ghostBookshelf.plugin(plugins.order);
// Load the Ghost search plugin, which handles applying a search query to findPage requests
ghostBookshelf.plugin(plugins.search);
@ -170,6 +173,11 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
return _.keys(schema.tables[this.tableName]);
},
// Ghost ordering handling, allows to order by permitted attributes by default and can be overriden on specific model level
orderAttributes: function orderAttributes() {
return this.permittedAttributes();
},
// When loading an instance, subclasses can specify default to fetch
defaultColumnsToFetch: function defaultColumnsToFetch() {
return [];
@ -912,7 +920,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
}
if (options.order) {
options.order = this.parseOrderOption(options.order, options.withRelated);
options.order = itemCollection.parseOrderOption(options.order, options.withRelated);
} else if (options.autoOrder) {
options.orderRaw = options.autoOrder;
} else if (this.orderDefaultRaw) {
@ -1165,43 +1173,6 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
return checkIfSlugExists(slug);
},
parseOrderOption: function (order, withRelated) {
let permittedAttributes;
let result;
let rules;
permittedAttributes = this.prototype.permittedAttributes();
if (withRelated && withRelated.indexOf('count.posts') > -1) {
permittedAttributes.push('count.posts');
}
result = {};
rules = order.split(',');
_.each(rules, function (rule) {
let match;
let field;
let direction;
match = /^([a-z0-9_.]+)\s+(asc|desc)$/i.exec(rule.trim());
// invalid order syntax
if (!match) {
return;
}
field = match[1].toLowerCase();
direction = match[2].toUpperCase();
if (permittedAttributes.indexOf(field) === -1) {
return;
}
result[field] = direction;
});
return result;
},
/**
* If you want to fetch all data fast, i recommend using this function.
* Bookshelf is just too slow, too much ORM overhead.

View File

@ -1,5 +1,6 @@
module.exports = {
filter: require('./filter'),
order: require('./order'),
customQuery: require('./custom-query'),
search: require('./search'),
includeCount: require('./include-count'),

View File

@ -0,0 +1,46 @@
const _ = require('lodash');
const order = function order(Bookshelf) {
Bookshelf.Model = Bookshelf.Model.extend({
orderAttributes() {},
parseOrderOption: function (orderQueryString, withRelated) {
let orderAttributes;
let result;
let rules;
orderAttributes = this.orderAttributes();
if (withRelated && withRelated.indexOf('count.posts') > -1) {
orderAttributes.push('count.posts');
}
result = {};
rules = orderQueryString.split(',');
_.each(rules, function (rule) {
let match;
let field;
let direction;
match = /^([a-z0-9_.]+)\s+(asc|desc)$/i.exec(rule.trim());
// invalid order syntax
if (!match) {
return;
}
field = match[1].toLowerCase();
direction = match[2].toUpperCase();
if (orderAttributes.indexOf(field) === -1) {
return;
}
result[field] = direction;
});
return result;
}
});
};
module.exports = order;