Skip to content

Commit

Permalink
feat: implement basic functionality for the base model
Browse files Browse the repository at this point in the history
The model is capable of issuing direct requests to the database using
the adapter.
  • Loading branch information
thetutlage committed Sep 23, 2019
1 parent cd4206f commit aca8ad5
Show file tree
Hide file tree
Showing 10 changed files with 556 additions and 113 deletions.
173 changes: 113 additions & 60 deletions adonis-typings/orm.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,113 @@
// /*
// * @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.
// */

// declare module '@ioc:Adonis/Lucid/Orm' {
// import {
// InsertQueryBuilderContract,
// DatabaseQueryBuilderContract,
// } from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

// import {
// ModelConstructorContract as BaseModelConstructorContract,
// ModelContract as BaseModelContract,
// AdapterContract as BaseAdapterContract,
// } from '@poppinss/data-models'

// export interface OrmQueryBuilder<
// Model extends ModelConstructorContract,
// Result extends any,
// > extends DatabaseQueryBuilderContract<Model['refs']>, ExcutableQueryBuilderContract<Result> {
// }

// export interface ModelConstructorContract extends BaseModelConstructorContract {
// $connection?: string,
// $increments: boolean,
// $table: string,

// refs: any,

// $getSaveQuery (
// client: QueryClientContract,
// action: 'insert',
// ): InsertQueryBuilderContract,

// $getSaveQuery (
// client: QueryClientContract,
// action: 'update',
// ): OrmQueryBuilder<any, any>,

// $getSaveQuery (
// client: QueryClientContract,
// action: 'insert' | 'update',
// ): InsertQueryBuilderContract | OrmQueryBuilder<any, any>,

// query (): OrmQueryBuilder<this, new () => this>,
// }

// export interface ModelContract extends BaseModelContract {
// $getConstructor (): ModelConstructorContract,
// }

// export interface AdapterContract extends BaseAdapterContract {
// insert (instance: ModelContract, attributes: any): Promise<void>
// }
// }
/*
* @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.
*/

declare module '@ioc:Adonis/Lucid/Orm' {
import knex from 'knex'
import { QueryClientContract, ExcutableQueryBuilderContract } from '@ioc:Adonis/Lucid/Database'

import {
InsertQueryBuilderContract,
DatabaseQueryBuilderContract,
} from '@ioc:Adonis/Lucid/DatabaseQueryBuilder'

import {
BaseModel as DataModelBaseModel,
AdapterContract as DataModelAdapterContract,
ModelConstructorContract as DataModelConstructorContract,
ModelContract as DataModelContract,
column as baseColumn,
} from '@poppinss/data-models'

/**
* Orm query builder will have extras methods on top of Database query builder
*/
export interface OrmQueryBuilder<
Record extends any,
Result extends any,
> extends DatabaseQueryBuilderContract<Record, Result>, ExcutableQueryBuilderContract<Result> {
}

/**
* The shape of query adapter
*/
export interface AdapterContract extends DataModelAdapterContract {
insert (instance: ModelContract, attributes: any): Promise<void>
update (instance: ModelContract, dirty: any): Promise<void>
delete (instance: ModelContract): Promise<void>
find (model: ModelConstructorContract, key: string, value: any): Promise<ModelContract | null>
findAll (model: ModelConstructorContract): Promise<ModelContract[]>
}

/**
* Shape of base model
*/
export interface ModelContract extends DataModelContract {
/**
* Gives an option to the end user to define constraints for update, insert
* and delete queries. Since the query builder for these queries aren't
* exposed to the end user, this method opens up the API to build
* custom queries.
*/
$getQueryFor (
action: 'insert',
client: QueryClientContract,
): ReturnType<QueryClientContract['insertQuery']>
$getQueryFor (
action: 'update' | 'delete',
client: QueryClientContract,
): ReturnType<QueryClientContract['query']>
$getQueryFor (
action: 'insert' | 'delete' | 'update',
client: QueryClientContract,
): ReturnType<QueryClientContract['query']> | ReturnType<QueryClientContract['insertQuery']>
}

/**
* Shape of base model static properties
*/
export interface ModelConstructorContract extends DataModelConstructorContract {
/**
* The database connection to use
*/
$connection?: string

/**
* Whether primary key is auto incrementing or not. If not, then
* end user must provide the value for the primary key
*/
$increments: boolean

/**
* Database table to use
*/
$table: string

/**
* Refs are named value pair of model
*/
refs: any

/**
* Returns the query for fetching a model instance
*/
query<
Model extends ModelConstructorContract,
Instance extends ModelContract,
> (this: new () => Instance): OrmQueryBuilder<Model, Instance>

$createFromAdapterResult (result?: any, sideloadAttributes?: string[]): null | ModelContract
$createMultipleFromAdapterResult (results: any[], sideloadAttributes?: string[]): ModelContract[]
}

export const BaseModel: ModelConstructorContract & {
new (): ModelContract,
}

export const column: typeof baseColumn
}
10 changes: 10 additions & 0 deletions example/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { BaseModel } from '@ioc:Adonis/Lucid/Orm'

class User extends BaseModel {
public username: string
}

const user = User.query().then((a) => {
a.username.toLocaleLowerCase()
})
console.log(user)
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,29 @@
},
"homepage": "https://github.com/adonisjs/adonis-lucid#readme",
"dependencies": {
"@poppinss/data-models": "^1.0.2",
"@poppinss/traits": "^1.0.0",
"@poppinss/utils": "^1.0.5",
"knex": "^0.19.3",
"knex-dynamic-connection": "^1.0.0",
"pluralize": "^8.0.0",
"snake-case": "^2.1.0",
"ts-essentials": "^3.0.2"
},
"peerDependencies": {
"@adonisjs/core": "2.x.x"
},
"devDependencies": {
"@adonisjs/fold": "^6.1.5",
"@adonisjs/fold": "^6.2.0",
"@adonisjs/mrm-preset": "^2.1.0",
"@adonisjs/sink": "^2.1.5",
"@poppinss/application": "^1.0.10",
"@poppinss/dev-utils": "^1.0.1",
"@poppinss/logger": "^1.1.3",
"@poppinss/profiler": "^1.1.1",
"@types/dotenv": "^6.1.1",
"@types/node": "^12.7.3",
"@types/node": "^12.7.4",
"@types/pluralize": "0.0.29",
"clone": "^2.1.2",
"commitizen": "^4.0.3",
"copyfiles": "^2.1.1",
Expand All @@ -70,6 +74,7 @@
"mysql": "^2.17.1",
"np": "^5.0.3",
"pg": "^7.12.1",
"reflect-metadata": "^0.1.13",
"sqlite3": "^4.1.0",
"ts-node": "^8.3.0",
"tslint": "^5.19.0",
Expand Down
12 changes: 11 additions & 1 deletion providers/DatabaseProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
* file that was distributed with this source code.
*/

import { IocContract } from '@adonisjs/fold'
import { Database } from '../src/Database'
import { BaseModel } from '../src/Orm/BaseModel'
import { column } from '../src/Orm/Decorators'

export default class DatabaseServiceProvider {
constructor (protected $container: any) {
constructor (protected $container: IocContract) {
}

/**
Expand All @@ -23,5 +26,12 @@ export default class DatabaseServiceProvider {
const Profiler = this.$container.use('Adonis/Core/Profiler')
return new Database(config, Logger, Profiler)
})

this.$container.singleton('Adonis/Lucid/Orm', () => {
return {
BaseModel,
column,
}
})
}
}
114 changes: 65 additions & 49 deletions src/Orm/Adapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,63 +10,79 @@
/// <reference path="../../../adonis-typings/orm.ts" />
/// <reference path="../../../adonis-typings/database.ts" />

// import { DatabaseContract } from '@ioc:Adonis/Lucid/Database'
// import { AdapterContract, ModelContract } from '@ioc:Adonis/Lucid/Orm'
import { DatabaseContract } from '@ioc:Adonis/Lucid/Database'
import { AdapterContract, ModelConstructorContract, ModelContract } from '@ioc:Adonis/Lucid/Orm'

// /**
// * Adapter to execute queries for a given model. Please note that adapters
// * are stateless and only one instance of adapter is used across the
// * app, so make sure not to store any state.
// */
// export class Adapter implements AdapterContract {
// constructor (private _db: DatabaseContract) {}
/**
* Adapter exposes the API to make database queries and constructor
* model instances from it.
*/
export class Adapter implements AdapterContract {
constructor (private _db: DatabaseContract) {
}

// public async insert (model: ModelContract, attributes: any): Promise<void> {
// const modelConstructor = model.$getConstructor()
/**
* Find a given row and construct model instance from it
*/
public async find (
modelConstructor: ModelConstructorContract,
key: string,
value: any,
): Promise<ModelContract | null> {
const client = this._db.connection(modelConstructor.$connection)

// /**
// * Pulling the query client for a given connection.
// */
// const client = this._db.connection(modelConstructor.$connection)
const result = await client
.query()
.select('*')
.from(modelConstructor.$table)
.where(key, value)
.limit(1)

// *
// * Letting model give us the insert query. This enables the end user
// * to add some constraints to the query builder before returning
// * it back to us
return modelConstructor.$createFromAdapterResult(result[0])
}

// const query = modelConstructor.$getSaveQuery(client, 'insert')
/**
* Returns an array of models by making a select query
*/
public async findAll (modelConstructor: ModelConstructorContract): Promise<ModelContract[]> {
const client = this._db.connection(modelConstructor.$connection)

// /**
// * Execute the query
// */
// const result = await query.insert(attributes)
const results = await client.query().select('*').from(modelConstructor.$table)
return modelConstructor.$createMultipleFromAdapterResult(results)
}

// /**
// * Set id when increments is true
// */
// if (modelConstructor.$increments) {
// model.$consumeAdapterResult({ [modelConstructor.$primaryKey]: result[0] })
// }
// }
/**
* Perform insert query on a given model instance
*/
public async insert (instance: ModelContract, attributes: any) {
const modelConstructor = instance.constructor as unknown as ModelConstructorContract
const client = this._db.connection(modelConstructor.$connection)
const query = instance.$getQueryFor('insert', client)

// public async update (model: ModelContract, dirty: any): Promise<void> {
// const modelConstructor = model.$getConstructor()
const result = await query.insert(attributes)
if (modelConstructor.$increments) {
instance.$consumeAdapterResult({ [modelConstructor.$primaryKey]: result[0] })
}
}

// /**
// * Pulling the query client for a given connection.
// */
// const client = this._db.connection(modelConstructor.$connection)
/**
* Perform update query on a given model instance
*/
public async update (instance: ModelContract, dirty: any) {
const modelConstructor = instance.constructor as unknown as ModelConstructorContract
const client = this._db.connection(modelConstructor.$connection)
const query = instance.$getQueryFor('update', client)

// /**
// * Letting model give us the insert query. This enables the end user
// * to add some constraints to the query builder before returning
// * it back to us
// */
// const query = modelConstructor.$getSaveQuery(client, 'update')
await query.update(dirty)
}

// /**
// * Execute the query
// */
// await query.update(dirty)
// }
// }
/**
* Perform delete query on a given model instance
*/
public async delete (instance: ModelContract) {
const modelConstructor = instance.constructor as unknown as ModelConstructorContract
const client = this._db.connection(modelConstructor.$connection)
const query = instance.$getQueryFor('delete', client)
await query.del()
}
}

0 comments on commit aca8ad5

Please sign in to comment.