Skip to content

Commit

Permalink
feat: add support for serializing models directly from paginator
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed May 6, 2021
1 parent db4e198 commit 6b67cad
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 11 deletions.
24 changes: 22 additions & 2 deletions adonis-typings/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,15 @@ declare module '@ioc:Adonis/Lucid/Model' {
(callback: (preloader: PreloaderContract<Model>) => void): Promise<void>
}

/**
* An extension of the simple paginator with support for serializing models
*/
export interface ModelPaginatorContract<Result extends LucidRow>
extends Omit<SimplePaginatorContract<Result>, 'toJSON'> {
serialize(cherryPick?: CherryPick): { meta: any; data: ModelObject[] }
toJSON(): { meta: any; data: ModelObject[] }
}

/**
* ------------------------------------------------------
* Model Query Builder
Expand Down Expand Up @@ -382,10 +391,21 @@ declare module '@ioc:Adonis/Lucid/Model' {
del(): ModelQueryBuilderContract<Model, any>
delete(): ModelQueryBuilderContract<Model, any>

/**
* A shorthand to define limit and offset based upon the
* current page
*/
forPage(page: number, perPage?: number): this

/**
* Execute query with pagination
*/
paginate(page: number, perPage?: number): Promise<SimplePaginatorContract<Result>>
paginate(
page: number,
perPage?: number
): Promise<
Result extends LucidRow ? ModelPaginatorContract<Result> : SimplePaginatorContract<Result>
>

/**
* Mutations (update and increment can be one query aswell)
Expand Down Expand Up @@ -816,7 +836,7 @@ declare module '@ioc:Adonis/Lucid/Model' {
after<Model extends LucidModel>(
this: Model,
event: 'paginate',
handler: HooksHandler<SimplePaginatorContract<InstanceType<Model>>, 'paginate'>
handler: HooksHandler<ModelPaginatorContract<InstanceType<Model>>, 'paginate'>
): void
after<Model extends LucidModel, Event extends EventsList>(
this: Model,
Expand Down
21 changes: 20 additions & 1 deletion adonis-typings/orm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@
declare module '@ioc:Adonis/Lucid/Orm' {
import {
ScopeFn,
LucidRow,
LucidModel,
HooksDecorator,
ColumnDecorator,
ComputedDecorator,
DateColumnDecorator,
ModelPaginatorContract,
DateTimeColumnDecorator,
} from '@ioc:Adonis/Lucid/Model'

import { SimplePaginatorMetaKeys } from '@ioc:Adonis/Lucid/Database'

import {
HasOneDecorator,
HasManyDecorator,
Expand All @@ -33,7 +37,11 @@ declare module '@ioc:Adonis/Lucid/Orm' {
ManyToMany,
HasManyThrough,
} from '@ioc:Adonis/Lucid/Relations'
export { NamingStrategyContract, ModelQueryBuilderContract } from '@ioc:Adonis/Lucid/Model'
export {
NamingStrategyContract,
ModelQueryBuilderContract,
ModelPaginatorContract,
} from '@ioc:Adonis/Lucid/Model'

export const scope: ScopeFn
export const BaseModel: LucidModel
Expand Down Expand Up @@ -64,6 +72,17 @@ declare module '@ioc:Adonis/Lucid/Orm' {
export const afterFetch: HooksDecorator
export const beforePaginate: HooksDecorator
export const afterPaginate: HooksDecorator
export const ModelPaginator: {
namingStrategy: {
paginationMetaKeys(): SimplePaginatorMetaKeys
}
new <Row extends LucidRow>(
rows: Row[],
total: number,
perPage: number,
currentPage: number
): ModelPaginatorContract<Row>
}

/**
* Columns and computed
Expand Down
1 change: 0 additions & 1 deletion example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export class User extends BaseModel {
}

User.query().apply((scopes) => scopes.active().country('India'))

User.create({ id: '1', username: 'a' })
User.fetchOrCreateMany('id', [{ id: '1', username: 'virk' }])
User.create({ id: '1', username: 'virk' })
Expand Down
2 changes: 2 additions & 0 deletions providers/DatabaseProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default class DatabaseServiceProvider {
const { scope } = require('../src/Helpers/scope')
const decorators = require('../src/Orm/Decorators')
const { BaseModel } = require('../src/Orm/BaseModel')
const { ModelPaginator } = require('../src/Orm/Paginator')

/**
* Attaching adapter to the base model. Each model is allowed to define
Expand All @@ -50,6 +51,7 @@ export default class DatabaseServiceProvider {

return {
BaseModel,
ModelPaginator,
scope,
...decorators,
}
Expand Down
29 changes: 29 additions & 0 deletions src/Orm/Paginator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* @adonisjs/lucid
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/// <reference path="../../../adonis-typings/index.ts" />

import { ModelPaginatorContract, CherryPick } from '@ioc:Adonis/Lucid/Model'
import { SimplePaginator } from '../../Database/Paginator/SimplePaginator'

/**
* Model paginator extends the simple paginator and adds support for
* serializing models as well
*/
export class ModelPaginator extends SimplePaginator implements ModelPaginatorContract<any> {
/**
* Serialize models
*/
public serialize(cherryPick?: CherryPick) {
return {
meta: this.getMeta(),
data: this.all().map((row) => row.serialize(cherryPick)),
}
}
}
26 changes: 20 additions & 6 deletions src/Orm/QueryBuilder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {

import { isObject } from '../../utils'
import { Preloader } from '../Preloader'
import { ModelPaginator } from '../Paginator'
import { QueryRunner } from '../../QueryRunner'
import { Chainable } from '../../Database/QueryBuilder/Chainable'
import { SimplePaginator } from '../../Database/Paginator/SimplePaginator'
Expand Down Expand Up @@ -714,7 +715,9 @@ export class ModelQueryBuilder extends Chainable implements ModelQueryBuilderCon
/**
* Paginate through rows inside a given table
*/
public async paginate(page: number, perPage: number = 20) {
public async paginate(page: number, perPage: number = 20): Promise<any> {
const isFetchCall = this.wrapResultsToModelInstances && this.knexQuery['_method'] === 'select'

/**
* Cast to number
*/
Expand All @@ -733,18 +736,29 @@ export class ModelQueryBuilder extends Chainable implements ModelQueryBuilderCon
* We pass both the counts query and the main query to the
* paginate hook
*/
await this.model.$hooks.exec('before', 'paginate', [countQuery, this])
await this.model.$hooks.exec('before', 'fetch', this)
if (isFetchCall) {
await this.model.$hooks.exec('before', 'paginate', [countQuery, this])
await this.model.$hooks.exec('before', 'fetch', this)
}

const aggregateResult = await countQuery.exec()
const total = this.hasGroupBy ? aggregateResult.length : aggregateResult[0].total

const results = total > 0 ? await this.forPage(page, perPage).execQuery() : []
const paginator = new SimplePaginator(results, total, perPage, page)

/**
* Choose paginator
*/
const paginator = this.wrapResultsToModelInstances
? new ModelPaginator(results, total, perPage, page)
: new SimplePaginator(results, total, perPage, page)

paginator.namingStrategy = this.model.namingStrategy

await this.model.$hooks.exec('after', 'paginate', paginator)
await this.model.$hooks.exec('after', 'fetch', results)
if (isFetchCall) {
await this.model.$hooks.exec('after', 'paginate', paginator)
await this.model.$hooks.exec('after', 'fetch', results)
}

return paginator
}
Expand Down
8 changes: 7 additions & 1 deletion test/database-provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { scope } from '../src/Helpers/scope'
import { BaseSeeder } from '../src/BaseSeeder'
import { FactoryManager } from '../src/Factory'
import { BaseModel } from '../src/Orm/BaseModel'
import { ModelPaginator } from '../src/Orm/Paginator'
import * as decorators from '../src/Orm/Decorators'

import { setupApplication, fs } from '../test-helpers'
Expand All @@ -35,7 +36,12 @@ test.group('Database Provider', (group) => {
)

assert.instanceOf(app.container.use('Adonis/Lucid/Database'), Database)
assert.deepEqual(app.container.use('Adonis/Lucid/Orm'), { BaseModel, scope, ...decorators })
assert.deepEqual(app.container.use('Adonis/Lucid/Orm'), {
BaseModel,
ModelPaginator,
scope,
...decorators,
})
assert.isTrue(app.container.hasBinding('Adonis/Lucid/Schema'))
assert.instanceOf(app.container.use('Adonis/Lucid/Factory'), FactoryManager)
assert.deepEqual(app.container.use('Adonis/Lucid/Seeder'), BaseSeeder)
Expand Down
108 changes: 108 additions & 0 deletions test/orm/base-model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import {
getBaseModel,
setupApplication,
} from '../../test-helpers'
import { ModelPaginator } from '../../src/Orm/Paginator'
import { SimplePaginator } from '../../src/Database/Paginator/SimplePaginator'
import { SnakeCaseNamingStrategy } from '../../src/Orm/NamingStrategies/SnakeCase'

Expand Down Expand Up @@ -4336,6 +4337,32 @@ test.group('Base Model | hooks', (group) => {
await db.insertQuery().table('users').insert({ username: 'virk' })
await User.query().paginate(1)
})

test('do not invoke before and after paginate hooks when using pojo', async () => {
class User extends BaseModel {
@column({ isPrimary: true })
public id: number

@column()
public username: string

@column()
public email: string

@beforePaginate()
public static beforePaginateHook() {
throw new Error('Never expected to reached here')
}

@afterPaginate()
public static afterPaginateHook() {
throw new Error('Never expected to reached here')
}
}

await db.insertQuery().table('users').insert({ username: 'virk' })
await User.query().pojo().paginate(1)
})
})

test.group('Base model | extend', (group) => {
Expand Down Expand Up @@ -5312,6 +5339,8 @@ test.group('Base Model | paginate', (group) => {
const users = await User.query().paginate(1, 5)
users.baseUrl('/users')

assert.instanceOf(users, ModelPaginator)

assert.lengthOf(users.all(), 5)
assert.instanceOf(users.all()[0], User)
assert.equal(users.perPage, 5)
Expand All @@ -5335,6 +5364,85 @@ test.group('Base Model | paginate', (group) => {
})
})

test('serialize from model paginator', async (assert) => {
class User extends BaseModel {
@column({ isPrimary: true })
public id: number

@column()
public username: string

@column()
public email: string
}

await db.insertQuery().table('users').multiInsert(getUsers(18))
const users = await User.query().paginate(1, 5)
users.baseUrl('/users')

assert.instanceOf(users, ModelPaginator)
const { meta, data } = users.serialize({
fields: ['username'],
})

data.forEach((row) => {
assert.notProperty(row, 'email')
assert.notProperty(row, 'id')
})
assert.deepEqual(meta, {
total: 18,
per_page: 5,
current_page: 1,
last_page: 4,
first_page: 1,
first_page_url: '/users?page=1',
last_page_url: '/users?page=4',
next_page_url: '/users?page=2',
previous_page_url: null,
})
})

test('return simple paginator instance when using pojo', async (assert) => {
class User extends BaseModel {
@column({ isPrimary: true })
public id: number

@column()
public username: string

@column()
public email: string
}

await db.insertQuery().table('users').multiInsert(getUsers(18))
const users = await User.query().pojo().paginate(1, 5)
users.baseUrl('/users')

assert.instanceOf(users, SimplePaginator)

assert.lengthOf(users.all(), 5)
assert.notInstanceOf(users.all()[0], User)
assert.equal(users.perPage, 5)
assert.equal(users.currentPage, 1)
assert.equal(users.lastPage, 4)
assert.isTrue(users.hasPages)
assert.isTrue(users.hasMorePages)
assert.isFalse(users.isEmpty)
assert.equal(users.total, 18)
assert.isTrue(users.hasTotal)
assert.deepEqual(users.getMeta(), {
total: 18,
per_page: 5,
current_page: 1,
last_page: 4,
first_page: 1,
first_page_url: '/users?page=1',
last_page_url: '/users?page=4',
next_page_url: '/users?page=2',
previous_page_url: null,
})
})

test('use model naming strategy for pagination properties', async (assert) => {
class User extends BaseModel {
@column({ isPrimary: true })
Expand Down

0 comments on commit 6b67cad

Please sign in to comment.