0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00

Merge pull request #5432 from ErisDS/api-pagination2

Refactor pagination count query
This commit is contained in:
Sebastian Gierlinger 2015-06-17 15:35:18 +02:00
commit f79a4f336b
3 changed files with 275 additions and 254 deletions

View file

@ -355,9 +355,35 @@ Post = ghostBookshelf.Model.extend({
findPage: function findPage(options) {
options = options || {};
var tagInstance = options.tag !== undefined ? ghostBookshelf.model('Tag').forge({slug: options.tag}) : false,
// -- Part 0 --
// Step 1: Setup filter models
var self = this,
tagInstance = options.tag !== undefined ? ghostBookshelf.model('Tag').forge({slug: options.tag}) : false,
authorInstance = options.author !== undefined ? ghostBookshelf.model('User').forge({slug: options.author}) : false;
// Step 2: Setup filter model promises
function fetchTagQuery() {
if (tagInstance) {
return tagInstance.fetch();
}
return false;
}
function fetchAuthorQuery() {
if (authorInstance) {
return authorInstance.fetch();
}
return false;
}
// Step 3: Prefetch filter models
return Promise.join(fetchTagQuery(), fetchAuthorQuery()).then(function setupCollectionPromises() {
// -- Part 1 --
var postCollection = Posts.forge(),
collectionPromise,
countPromise;
// Step 1: Setup pagination options
if (options.limit && options.limit !== 'all') {
options.limit = parseInt(options.limit, 10) || 15;
}
@ -366,9 +392,10 @@ Post = ghostBookshelf.Model.extend({
options.page = parseInt(options.page, 10) || 1;
}
options = this.filterOptions(options, 'findPage');
// Step 2: Filter options
options = self.filterOptions(options, 'findPage');
// Set default settings for options
// Step 3: Extend defaults
options = _.extend({
page: 1, // pagination page
limit: 15,
@ -377,6 +404,7 @@ Post = ghostBookshelf.Model.extend({
where: {}
}, options);
// Step 4: Setup filters (where clauses)
if (options.staticPages !== 'all') {
// convert string true/false to boolean
if (!_.isBoolean(options.staticPages)) {
@ -401,44 +429,12 @@ Post = ghostBookshelf.Model.extend({
options.where.status = options.status;
}
// Add related objects
options.withRelated = _.union(options.withRelated, options.include);
// If a query param for a tag is attached
// we need to fetch the tag model to find its id
function fetchTagQuery() {
if (tagInstance) {
return tagInstance.fetch();
}
return false;
}
function fetchAuthorQuery() {
if (authorInstance) {
return authorInstance.fetch();
}
return false;
}
return Promise.join(fetchTagQuery(), fetchAuthorQuery())
// Set the limit & offset for the query, fetching
// with the opts (to specify any eager relations, etc.)
// Omitting the `page`, `limit`, `where` just to be sure
// aren't used for other purposes.
.then(function then() {
var postCollection = Posts.forge(),
collectionPromise,
countPromise,
qb;
// If there are where conditionals specified, add those
// to the query.
// If there are where conditionals specified, add those to the query.
if (options.where) {
postCollection.query('where', options.where);
}
// If we have a tag instance we need to modify our query.
// We need to ensure we only select posts that contain
// the tag given in the query param.
// Step 5: Setup joins
if (tagInstance) {
postCollection
.query('join', 'posts_tags', 'posts_tags.post_id', '=', 'posts.id')
@ -450,42 +446,43 @@ Post = ghostBookshelf.Model.extend({
.query('where', 'author_id', '=', authorInstance.id);
}
// Step 6: Setup the counter to fetch the number of items in the set
// @TODO abstract this out
// tableName = _.result(postCollection, 'tableName'),
// idAttribute = _.result(postCollection, 'idAttribute');
countPromise = postCollection.query().clone().count('posts.id as aggregate');
// -- Part 2 --
// Add limit, offset and other query changes which aren't required when performing a count
// Step 1: Add related objects
options.withRelated = _.union(options.withRelated, options.include);
// Step 2: Add pagination options if needed
if (_.isNumber(options.limit)) {
postCollection
.query('limit', options.limit)
.query('offset', options.limit * (options.page - 1));
}
collectionPromise = postCollection
// Step 3: add order parameters
postCollection
.query('orderBy', 'status', 'ASC')
.query('orderBy', 'published_at', 'DESC')
.query('orderBy', 'updated_at', 'DESC')
.query('orderBy', 'id', 'DESC')
.fetch(_.omit(options, 'page', 'limit'));
.query('orderBy', 'id', 'DESC');
// Find the total number of posts
qb = ghostBookshelf.knex('posts');
if (options.where) {
qb.where(options.where);
}
if (tagInstance) {
qb.join('posts_tags', 'posts_tags.post_id', '=', 'posts.id');
qb.where('posts_tags.tag_id', '=', tagInstance.id);
}
if (authorInstance) {
qb.where('author_id', '=', authorInstance.id);
}
countPromise = qb.count('posts.id as aggregate');
// Step 4: Setup the promise
collectionPromise = postCollection.fetch(_.omit(options, 'page', 'limit'));
// -- Part 3 --
// Step 1: Fetch the data
return Promise.join(collectionPromise, countPromise);
}).then(function then(results) {
}).then(function formatResponse(results) {
var postCollection = results[0],
data = {};
// Step 2: Format the data
data.posts = postCollection.toJSON(options);
data.meta = {pagination: paginateResponse(results[1][0].aggregate, options)};
@ -504,8 +501,7 @@ Post = ghostBookshelf.Model.extend({
}
return data;
})
.catch(errors.logAndThrowError);
}).catch(errors.logAndThrowError);
},
/**

View file

@ -110,10 +110,17 @@ Tag = ghostBookshelf.Model.extend({
findPage: function findPage(options) {
options = options || {};
// -- Part 0 --
// Step 1: Setup filter models
// Step 2: Setup filter model promises
// Step 3: Prefetch filter models
// -- Part 1 --
var tagCollection = Tags.forge(),
collectionPromise,
qb;
countPromise;
// Step 1: Setup pagination options
if (options.limit && options.limit !== 'all') {
options.limit = parseInt(options.limit, 10) || 15;
}
@ -122,42 +129,60 @@ Tag = ghostBookshelf.Model.extend({
options.page = parseInt(options.page, 10) || 1;
}
// Step 2: Filter options
options = this.filterOptions(options, 'findPage');
// Set default settings for options
// Step 3: Extend defaults
options = _.extend({
page: 1, // pagination page
limit: 15,
where: {}
}, options);
// only include a limit-query if a numeric limit is provided
// Step 4: Setup filters (where clauses)
// If there are where conditionals specified, add those to the query.
if (options.where) {
tagCollection.query('where', options.where);
}
// Step 5: Setup joins
// Step 6: Setup the counter to fetch the number of items in the set
// @TODO abstract this out
// tableName = _.result(postCollection, 'tableName'),
// idAttribute = _.result(postCollection, 'idAttribute');
countPromise = tagCollection.query().clone().count('tags.id as aggregate');
// -- Part 2 --
// Add limit, offset and other query changes which aren't required when performing a count
// Step 1: Add related objects
addPostCount(options, tagCollection);
options.withRelated = _.union(options.withRelated, options.include);
// Step 2: Add pagination options if needed
if (_.isNumber(options.limit)) {
tagCollection
.query('limit', options.limit)
.query('offset', options.limit * (options.page - 1));
}
addPostCount(options, tagCollection);
// Step 3: add order parameters
// Step 4: Setup the promise
collectionPromise = tagCollection.fetch(_.omit(options, 'page', 'limit'));
// Find total number of tags
qb = ghostBookshelf.knex('tags');
if (options.where) {
qb.where(options.where);
}
return Promise.join(collectionPromise, qb.count('tags.id as aggregate')).then(function then(results) {
// -- Part 3 --
// Step 1: Fetch the data
return Promise.join(collectionPromise, countPromise).then(function formatResponse(results) {
var tagCollection = results[0],
data = {};
// Step 2: Format the data
data.tags = tagCollection.toJSON(options);
data.meta = {pagination: paginateResponse(results[1][0].aggregate, options)};
return data;
})
.catch(errors.logAndThrowError);
}).catch(errors.logAndThrowError);
},
destroy: function destroy(options) {
var id = options.id;

View file

@ -225,9 +225,27 @@ User = ghostBookshelf.Model.extend({
findPage: function findPage(options) {
options = options || {};
var userCollection = Users.forge(),
// -- Part 0 --
// Step 1: Setup filter models
var self = this,
roleInstance = options.role !== undefined ? ghostBookshelf.model('Role').forge({name: options.role}) : false;
// Step 2: Setup filter model promises
function fetchRoleQuery() {
if (roleInstance) {
return roleInstance.fetch();
}
return false;
}
// Step 3: Prefetch filter models
return Promise.resolve(fetchRoleQuery()).then(function setupCollectionPromises() {
// -- Part 1 --
var userCollection = Users.forge(),
collectionPromise,
countPromise;
// Step 1: Setup pagination options
if (options.limit && options.limit !== 'all') {
options.limit = parseInt(options.limit, 10) || 15;
}
@ -236,9 +254,10 @@ User = ghostBookshelf.Model.extend({
options.page = parseInt(options.page, 10) || 1;
}
options = this.filterOptions(options, 'findPage');
// Step 2: Filter options
options = self.filterOptions(options, 'findPage');
// Set default settings for options
// Step 3: Extend defaults
options = _.extend({
page: 1, // pagination page
limit: 15,
@ -247,6 +266,7 @@ User = ghostBookshelf.Model.extend({
whereIn: {}
}, options);
// Step 4: Setup filters (where clauses)
// TODO: there are multiple statuses that make a user "active" or "invited" - we a way to translate/map them:
// TODO (cont'd from above): * valid "active" statuses: active, warn-1, warn-2, warn-3, warn-4, locked
// TODO (cont'd from above): * valid "invited" statuses" invited, invited-pending
@ -268,72 +288,54 @@ User = ghostBookshelf.Model.extend({
options.where.status = options.status;
}
// If there are where conditionals specified, add those
// to the query.
// If there are where conditionals specified, add those to the query.
if (options.where) {
userCollection.query('where', options.where);
}
// Add related objects
options.withRelated = _.union(options.withRelated, options.include);
// only include a limit-query if a numeric limit is provided
if (_.isNumber(options.limit)) {
userCollection
.query('limit', options.limit)
.query('offset', options.limit * (options.page - 1));
}
function fetchRoleQuery() {
if (roleInstance) {
return roleInstance.fetch();
}
return false;
}
return Promise.resolve(fetchRoleQuery())
.then(function then() {
function fetchCollection() {
// Step 5: Setup joins
if (roleInstance) {
userCollection
.query('join', 'roles_users', 'roles_users.user_id', '=', 'users.id')
.query('where', 'roles_users.role_id', '=', roleInstance.id);
}
return userCollection
// Step 6: Setup the counter to fetch the number of items in the set
// @TODO abstract this out
// tableName = _.result(userCollection, 'tableName'),
// idAttribute = _.result(userCollection, 'idAttribute');
countPromise = userCollection.query().clone().count('users.id as aggregate');
// -- Part 2 --
// Add limit, offset and other query changes which aren't required when performing a count
// Step 1: Add related objects
options.withRelated = _.union(options.withRelated, options.include);
// Step 2: Add pagination options if needed
if (_.isNumber(options.limit)) {
userCollection
.query('limit', options.limit)
.query('offset', options.limit * (options.page - 1));
}
// Step 3: add order parameters
userCollection
.query('orderBy', 'last_login', 'DESC')
.query('orderBy', 'name', 'ASC')
.query('orderBy', 'created_at', 'DESC')
.fetch(_.omit(options, 'page', 'limit'));
}
.query('orderBy', 'created_at', 'DESC');
function fetchPaginationData() {
var qb,
tableName = _.result(userCollection, 'tableName'),
idAttribute = _.result(userCollection, 'idAttribute');
// Step 4: Setup the promise
collectionPromise = userCollection.fetch(_.omit(options, 'page', 'limit'));
// After we're done, we need to figure out what
// the limits are for the pagination values.
qb = ghostBookshelf.knex(tableName);
if (options.where) {
qb.where(options.where);
}
if (roleInstance) {
qb.join('roles_users', 'roles_users.user_id', '=', 'users.id');
qb.where('roles_users.role_id', '=', roleInstance.id);
}
return qb.count(tableName + '.' + idAttribute + ' as aggregate');
}
return Promise.join(fetchCollection(), fetchPaginationData());
})
// Format response of data
.then(function then(results) {
var data = {};
// -- Part 3 --
// Step 1: Fetch the data
return Promise.join(collectionPromise, countPromise);
}).then(function formatResponse(results) {
var userCollection = results[0],
data = {};
// Step 2: Format the data
data.users = userCollection.toJSON(options);
data.meta = {pagination: paginateResponse(results[1][0].aggregate, options)};
@ -345,8 +347,7 @@ User = ghostBookshelf.Model.extend({
}
return data;
})
.catch(errors.logAndThrowError);
}).catch(errors.logAndThrowError);
},
/**
@ -370,6 +371,7 @@ User = ghostBookshelf.Model.extend({
options = options || {};
options.withRelated = _.union(options.withRelated, options.include);
data = this.filterData(data);
// Support finding by role
if (lookupRole) {
@ -386,8 +388,6 @@ User = ghostBookshelf.Model.extend({
query = this.forge(data, {include: options.include});
}
data = this.filterData(data);
if (status === 'active') {
query.query('whereIn', 'status', activeStates);
} else if (status === 'invited') {