@absolunet/ioc2.1.0

View on GitHub

database/Model/Model.js

//--------------------------------------------------------
//-- Node IoC - Database - Model
//--------------------------------------------------------

import __            from '@absolunet/private-registry';
import ModelProxy    from './ModelProxy';
import getsMethods   from '../../support/mixins/getsMethods';


/**
 * Database model that implements ORM features to transact with Knex with an Active Record Pattern (ARP) approach.
 *
 * @memberof database
 * @augments {support.mixins.GetsMethod}
 * @abstract
 */
class Model extends getsMethods() {

	/**
	 * Class dependencies: <code>['app', 'engine']</code>.
	 *
	 * @type {Array<string>}
	 */
	static get dependencies() {
		return ['app', 'engine'];
	}

	/**
	 * Default values.
	 *
	 * @type {object}
	 */
	get defaults() {
		return {};
	}

	/**
	 * Flag that indicates if the model has timestamp columns.
	 *
	 * @type {boolean}
	 */
	get timestamps() {
		return true;
	}

	/**
	 * Primary key column name.
	 *
	 * @type {string}
	 */
	get key() {
		return 'id';
	}

	/**
	 * Primary key column type.
	 *
	 * @type {string}
	 */
	get keyType() {
		return 'uuid';
	}

	/**
	 * Table name.
	 *
	 * @type {string}
	 */
	get table() {
		const stringHelper = this.app.make('helper.string');

		return stringHelper.snake(stringHelper.plural(this.constructor.name));
	}

	/**
	 * Model constructor.
	 *
	 * @param {foundation.Application} app - The current application instance.
	 * @param {*} engine - The engine that exposes the base model.
	 * @returns {Function} An engine model factory, wrapped by a proxy.
	 */
	constructor(app, engine) {
		super();
		const { Model: model } = engine;
		const self             = this;
		const factory          = function() { return self; };
		Object.defineProperty(factory, 'name', {
			get: () => { return this.constructor.name; }
		});

		__(this).set('app',   app);
		__(this).set('super', model.prototype);
		__(this).set('model', model.extend(this.definition));

		return new Proxy(factory, new ModelProxy());
	}

	/**
	 * Boot the model on initialization.
	 */
	boot() {
		//
	}

	/**
	 * Get default values.
	 *
	 * @returns {object} The default values.
	 */
	getDefaults() {
		return this.defaults;
	}

	/**
	 * Get if the model has timestamp columns.
	 *
	 * @returns {boolean} Indicates that the model uses timestamps.
	 */
	getHasTimestamps() {
		return this.timestamps;
	}

	/**
	 * Get primary key column name.
	 *
	 * @returns {string} The primary column name.
	 */
	getIdAttribute() {
		return this.key;
	}

	/**
	 * Get primary key column type.
	 *
	 * @returns {string} The primary column type.
	 */
	getIdType() {
		return this.keyType;
	}

	/**
	 * Get a list of all processors.
	 *
	 * @returns {object<string, Function>} The processors list for each columns.
	 */
	getProcessors() {
		const date = this.app.make('helper.date');

		return {
			...Object.fromEntries(['created_at', 'updated_at']
				.map((timestamp) => {
					return [
						timestamp,
						(value) => {
							return date(value).format('YYYY-MM-DD HH:mm:ss');
						}
					];
				})),
			...this.processors
		};
	}

	/**
	 * Get table name.
	 *
	 * @returns {string} The table name.
	 */
	getTableName() {
		return this.table;
	}

	/**
	 * Get ORM model for forward calls.
	 *
	 * @returns {*} The ORM model instance.
	 */
	getForward() {
		return __(this).get('model');
	}

	/**
	 * Get relation builders that maps relation names with their query builder.
	 *
	 * @returns {object<string, Function>} The mapped relations with their builder.
	 */
	getRelationBuilders() {
		const self = this;
		const relationEntries = this.getMethods(this)
			.filter((method) => {
				return method.endsWith('Relation');
			})
			.map((method) => {
				return [
					method.replace(/Relation$/u, ''),
					function(...parameters) { return self[method](this, ...parameters); }
				];
			});

		return Object.fromEntries(relationEntries);
	}

	/**
	 * Create a new record in the database.
	 *
	 * @param {object} attributes - The attributes to create the model with.
	 * @returns {Promise<database.Model>} The newly created model instance.
	 */
	async create(attributes) {
		await this.getForward().forge(attributes).save();

		return this;
	}

	/**
	 * Get model definition for the underlying model system.
	 *
	 * @type {object}
	 */
	get definition() {
		const self = this;

		return {
			defaults:      this.getDefaults(),
			hasTimestamps: this.getHasTimestamps(),
			idAttribute:   this.getIdAttribute(),
			processors:    this.getProcessors(),
			tableName:     this.getTableName(),
			uuid:          this.getIdType() === 'uuid',
			initialize() {
				__(self).get('super').initialize.call(this);

				return self.boot(this);
			},
			...this.getRelationBuilders()
		};
	}

	/**
	 * Application accessor.
	 *
	 * @type {foundation.Application}
	 */
	get app() {
		return __(this).get('app');
	}

}


export default Model;