class-o-mat
Version:
ALPHA VERSION ONLY: class-o-mats will help to create getter-setter functionality with validations that can be attached to a container class for easier coding. Some basic validations are included. class-o-mats also has the ability to store params as bloc
653 lines (535 loc) • 18.5 kB
JavaScript
import Bunyan from 'bunyan';
const log = Bunyan.createLogger({name: "ClassOMat"});
import mixin from 'mixin-decorator';
import clonedeep from 'lodash.clonedeep';
import uuid from 'uuid/v4';
import _eventMethods from './_event-methods-o-mat';
import _apiMethods from './_api-methods-o-mat';
import { getterSetter as gs, toggler as tg } from './class-o-mat-field-functions';
import { getterSetter } from '../methods/field-functions';
import { NONE, CLASS_O_MAT, TO_SET } from '../constants/types';
import ClassOMatGroup from './class-o-mat-group';
import FieldOMat from './field-o-mat';
import ApiFieldOMat from './api-field-o-mat';
import ApiMethodOMat from './api-method-o-mat';
import ChildOMatAdder from './child-o-mat-adder';
import RadioOMat from './radio-o-mat';
import buildClassMethods from '../methods/build-class';
class ClassOMat {
static TYPE = CLASS_O_MAT;
TYPE = CLASS_O_MAT;
static VERSION = '0.6.1';
VERSION = '0.6.1';
constructor(key='classOMat that needs a key', parent=NONE) {
const group = ( parent === NONE)
? new ClassOMatGroup()
: parent.group();
class ClassOMatDefaultBaseClass {};
this._params = {
key,
id : NONE,
uid : uuid(),
pluginKeys : [],
cacheOMats : false,
accessorGetter : getterSetter,
FieldOMat,
fieldOMats : [],
fieldOMatsIndex : {},
apiFields : [],
paramObjectKey : '_params',
paramKeyPrefix : '___',
createParamsAccessor : true,
createKeysAccessor : true,
group,
childOMats : {},
childOMatInfos : {},
copyToChildFields : [],
parentKey : '___parent',
needsParentAccessor : false,
parentId : parent === NONE ? NONE : parent.id(),
BaseClass : ClassOMatDefaultBaseClass,
BuiltClass : NONE,
defaults : {___pending:[]},
mixins : {
___isClassOMat: ()=>true,
},
buildMethods : buildClassMethods,
ON_GET : NONE,
ON_SET : NONE,
ON_DELETE : NONE,
}
const [isNew, id] = group.add(this);
this._params = {
...this._params,
id,
group
}
}
// API TO BUILD THE CLASS TO BE GENERATED
/**
* builds the class for this param and all the
* child params .. returns all as an object.
* @return {[type]} [description]
*/
$() {
const classGroup = this._params.group.$();
const Class = classGroup.get(this.id());
const test = new Class();
return classGroup.get(this.id());
}
/**
* Build the class with the FieldSets in the ClassOMat
* This should be used by Group but it available in case.
* @param {Class} BaseClass The base class to have the methods added.
* @return {Class} The base class with the methods added.
*/
buildClass(classGroup=NONE, classOMatGroup=NONE) {
const { _params } = this;
let { buildMethods, defaults, mixins } = _params;
let workingArguments = {classOMat: this, mixins, defaults, params: _params}
let WorkingClass;
[WorkingClass, buildMethods]
= buildMethods.build(workingArguments, buildMethods);
const BuiltClass = WorkingClass;
this.BuiltClass(BuiltClass);
return BuiltClass;
}
// API FOR DEFINING THIS CLASS_O_MAT
/**
* getter setter for id
* @param {number} id
* @return {number|ClassOMat}
key,
*/
id(...p) { return gs(this, 'id', ...p) };
uid(...p) { return gs(this, 'uid', ...p) };
/**
* If true, this class will have a reference
* to the ClassOMatGroup that this ClassOMat belongs to.
* This is needed to modify
* @param {[type]} cacheChildOMats [description]
* @return {[type]} [description]
*/
cacheOMats(cacheOMats=NONE) {
if (cacheOMats === NONE) return this._params.cacheOMats;
this._params.cacheOMats = cacheOMats === true ? true : false;
return this;
}
// API TO SPECIFY THE CLASS TO BE GENERATED
/**
* Set the BaseClass for this ClassOMat
* @param {Class} BaseClass
*/
BaseClass(BaseClass=NONE) {
return gs(this, 'BaseClass', BaseClass);
}
/**
* Set the BuiltClass for this ClassOMat
* @param {Class} BuiltClass
*/
BuiltClass(BuiltClass=NONE) { return gs(this, 'BuiltClass', BuiltClass); }
defaultAccessor(accessorGetter) {
return gs(this, 'accessor', accessorGetter)
}
/**
* toggler for createParamsAccessor
* This will create a parameters accessor in the BuiltClass
* default = true
* under the function name __params
* @return {object|ClassOMat}
*/
createParamsAccessor() { return tg(this, 'createParamsAccessor') };
/**
* toggler for createKeyAccesor
* This will create a parameter keys accessor in the BuiltClass
* defalut = true
* @return {object|ClassOMat}
*/
createKeyAccesor() { return tg(this, 'createKeyAccesor', ); };
/**
* Lets you attach a field to a Minion instance
* AFTER it is created. It otherwise works
* just like classOMat.field
* @param {[type]} key [description]
* @return {[type]} [description]
*/
apiField(key) {
const apiFieldOMat = new ApiFieldOMat(this, key)
this._params.fieldOMats.push(apiFieldOMat);
return apiFieldOMat;
}
/**
* lets you attach a method to the classOMat
* that will add that method to the minion class api.
* @return {ApiMethod} [description]
*/
apiMethod(method) {
const apiMethodOMat = new ApiMethodOMat(this, method);
this._params.fields.push(apiMethodOMat);
return apiMethodOMat;
}
deleteField(key) {
const { fieldsIndex, fields } = this._params;
if (key in fieldsIndex === false) {
throw new Error(
`Tried to delete non-existent field ${key} from classOMat ${this._params.key}.`);
}
const fieldIndex = fieldsIndex[key];
fields.splice(fieldIndex, 1);
delete fieldIndex[key];
this._params.fieldsIndex = fieldsIndex;
this._params.fields = fields;
return this;
}
/**
* Get a FieldOMat.
* @param {string} key the key for this field.
* This will be the function name of the class instance.
* @return {FieldOMat} The field param builder
*/
field(key, params = NONE) {
if (this._params.fieldOMatsIndex[key]) {
return this._params.fieldOMats[this._params.fieldOMatsIndex[key]]
}
const field = new this._params.FieldOMat().init(this, key);
if (params !== NONE) {
const { parent, ...transferParams } = params;
field.params(transferParams, true);
}
this._params.fieldOMats.push(field);
this._params.fieldOMatsIndex[key] = this._params.fieldOMats.length -1;
return field;
}
/**
* get or set all the fields to be defined in this classOMat
* @param {Array} fields
* @return {Array} fields
*/
fields(fieldOMats = NONE) {
if (fieldOMats === NONE) return this._params.fieldOMats;
this._params.fieldOMats = fieldOMats;
return this;
}
fieldNames() {
return this._params.fieldOMats.map((f)=>f.key())
}
/**
* getter setter for group
* @param {Group} group
* @return {Group|ClassOMat}
*/
group(...p) { return gs(this, 'group', ...p); };
parentKey(...p) { return gs(this, 'parentKey', ...p)};
pluginKeys() {
return this._params.pluginKeys;
}
/**
* getter setter for paramObjectKey
* @param {object} p
* @return {object|ClassOMat}
*/
paramObjectKey(paramObjectKey=NONE) {
if (paramObjectKey === false)
return gs(this, 'paramObjectKey', paramObjectKey);
if (paramObjectKey !== NONE) {
if (typeof paramObjectKey !== 'string') {
throw new Error('ClassOMat: Invalid paramObjectKey');
}
if (paramObjectKey.slice(0, 1) !== '_') {
paramObjectKey = '_' + paramObjectKey;
}
}
return gs(this, 'paramObjectKey', paramObjectKey);
};
radio(key) {
return new RadioOMat(this, key);
}
// FOR NEW CLASS-O-MATS.
/**
* create a copy of this paramset but with the new key
* @param {string} key
* @return {ClassOMat} the cloned set.
*/
clone(key = this.key()) {
const clone = new ClassOMat(key, this);
const cloneParams = clonedeep(this.params());
clone.params(cloneParams);
clone.key(key);
clone.uid(uuid());
return clone;
}
subOMat(key) {
const subOMat = new ClassOMat(key);
const subOMatParams = clonedeep(this._params);
delete subOMatParams.group;
delete subOMatParams.key;
delete subOMatParams.uid;
delete subOMatParams.childOMats;
delete subOMatParams.childInfoOMats;
subOMat.params(subOMatParams, true);
const { childOMats, childOMatInfos } = this._params;
const childOMatInfoKeys = Object.keys(childOMatInfos);
childOMatInfoKeys.forEach((childInfoKey)=>{
const childOMatInfo = childOMatInfos[childInfoKey];
const { childOMatKey, ...childOMatInfoToAdder } = childOMatInfo;
const childOMat = childOMats[childOMatInfo.childOMatUid];
subOMat._addChildFromAdder({ childOMat, ...childOMatInfoToAdder});
});
return subOMat;
}
// API FOR NESTING SETS and NAVIGATING SAME
/**
* add a set as a child of this one.
* @param {ClassOMat} set default is NONE which means "self"
* @return {ClassOMat} this;
*/
addChild(childOMat = NONE) {
return new ChildOMatAdder().init(this, childOMat);
}
/**
* getterSetter for all the childOMats of this classOMat
* @param {Object|NONE} p
* @return {Object|ClassOMat} the Object of childOMats or this
*/
childOMats(...p) { return gs(this, 'childOMats', ...p)};
/**
* get a classOMat nested within this one.
* @param {string} key
* @return {ClassOMat} the child set
*/
getChild(key) {
const { childOMats, childOMatInfos } = this._params;
if (key in childOMatInfos === false) {
throw new Error(`Invalid Set Name: ${key}`);
}
return childOMats[childOMatInfos[key].childOMatUid];
}
/**
* If this ClassOMat is nested within a parent, get that parent
* or set it.. this is a getter/setter function
* get the parent.
*
* adding a parent forces adding the parent accessor to the child class
* if one is needed.
* @param {ClassOMat|NONE} parent
* @return {ClassOMat}
*/
parent(parent=NONE) {
if (parent===NONE) return this._params.parent;
this._params.parent = parent;
return this;
}
needsParentAccessor() {
this._params.needsParentAccessor = true;
return this;
}
reGroupChildren() {
const { childOMats, childOMatInfos, group } = this._params;
const childKeys = Object.keys(childOMatInfos);
if (childKeys.length === 0) return;
childKeys.forEach((childKey)=>{
const childOMatInfo = childOMatInfos[childKey];
const { childOMatUid } = childOMatInfo;
const childOMat = childOMats[childOMatUid];
const childOMatGroup = childOMat.group();
if (childOMatGroup.uid()===group.uid()) return;
const [isNew, id] = group.add(childOMat);
if (isNew === false) return;
childOMat.id(id);
childOMat.reGroupChildren();
});
}
addChildAccessors(classGroup) {
const { childOMats, childOMatInfos, BuiltClass } = this._params;
const childKeys = Object.keys(childOMatInfos);
if (childKeys.length === 0) return BuiltClass;
const childAccessorsMixins = {};
childKeys.forEach((childKey)=>{
const childOMatInfo = childOMatInfos[childKey];
const childOMat = childOMats[childOMatInfo.childOMatUid];
const childClassId = childOMat.id();
const ChildClass = classGroup.get(childClassId);
childAccessorsMixins[childKey] =
this._childAccessorFunction(
{
childKey,
classGroup,
childClassId,
...childOMatInfo
});
const {accessChildAs, namedChildren, singleton} = childOMatInfo;
const _defaultChild = (singleton === true)
? NONE
: (namedChildren === true)
? {}
: [];
if (accessChildAs !== NONE) {
childAccessorsMixins[accessChildAs] = function() {
return this.___child(childKey)||_defaultChild;
}
childAccessorsMixins[`delete_${accessChildAs}`] = function() {
return this.___deleteChild(childKey);
}
}
});
class WorkingClass extends BuiltClass {};
this.BuiltClass(WorkingClass);
return WorkingClass;
}
_childAccessorFunction({
accessChildAs,
cacheChild,
classGroup,
childClassId,
childUid,
copyToChildFields,
initializer,
internalKey,
childKey,
namedChildren,
parentKey,
singleton
}) {
const {fields, paramObjectKey } = this._params;
const getStoredChildObject
= (namedChildren === false)
? (___children, childKey, childInstance)=> {
const storedChild = ___children[childKey];
if (storedChild === undefined) return childInstance;
if (Array.isArray(storedChild) === true)
return storedChild.concat([childInstance]);
return [storedChild, childInstance];
}
: (___children, childKey, childInstance, instanceKey)=> {
const storedChild = ___children[childKey];
if (storedChild === undefined) return { [instanceKey]: childInstance }
return { ...storedChild, [instanceKey]: childInstance }
}
const nonInitializerWrapper =(childInstance, self, ...p)=>{
return childInstance[parentKey](self, ...p);
}
const initializerWrapper =(childInstance, self, ...p)=>{
const minion = childInstance[parentKey](self, ...p);
initializer(minion, self, ...p);
return minion;
}
const copyFieldsWrapper =(childInstance, self)=>{
const parentParams = self.params();
const childParams = childInstance.params();
copyToChildFields.forEach(({key, as})=>{
childParams[as] = parentParams[key];
});
return childInstance;
}
const copyToChildFieldsCount = copyToChildFields.length;
let initWrapper;
if (initializer === NONE) {
if (copyToChildFieldsCount === 0) {
initWrapper = nonInitializerWrapper;
} else {
initWrapper = copyFieldsWrapper;
}
} else {
if (copyToChildFieldsCount === 0) {
initWrapper = initializerWrapper;
} else {
initWrapper =(childInstance, self, ...p)=>{
childInstance = copyFieldsWrapper(childInstance, self);
return initializerWrapper(childInstance, self, ...p);
}
}
}
if (cacheChild === false) {
return function(...p) {
return initWrapper(
new (classGroup.get(childClassId))()[parentKey](this),
this,
...p
)
}
}
const getChildren = paramObjectKey === false
? (self)=>self.___children
: (self)=>self[paramObjectKey].___children;
if (singleton === true) {
const workingInitWrapper = initWrapper;
initWrapper = (childInstance, self, ...p)=>{
const result = workingInitWrapper(childInstance, self, ...p);
self[childKey] = function() { return childInstance };
return childInstance;
}
}
if (namedChildren === false) {
return function(...p) {
const childInstance = new (classGroup.get(childClassId))()[parentKey](this);
const ___children = getChildren(this);
___children[childKey]
= getStoredChildObject(___children, childKey, childInstance);
return initWrapper(childInstance, this, ...p);
}
}
return function(...p) {
const [instanceKey, ...params ] = p;
const childInstance = new (classGroup.get(childClassId))()[parentKey](this);
const ___children = getChildren(this);
___children[childKey]
= getStoredChildObject(___children, childKey, childInstance, instanceKey);
return initWrapper(childInstance, this, ...params);
}
}
// INTERNAL COMMANDS FOR ADDING CHILDREN AND THEIR FEATURES
_addChildFromAdder(adderParams) {
const { childOMats, group } = this._params;
const {
accessChildAs,
cacheChild,
childOMat,
childIsSelf,
copyToChildFields,
internalKey,
initializer,
childKey,
namedChildren,
parentKey,
singleton
} = adderParams;
const newChildOMat = ( childIsSelf === true )
? this
: childOMat;
newChildOMat.needsParentAccessor();
newChildOMat.parent(this);
newChildOMat.parentKey(parentKey);
const childOMatUid = newChildOMat.uid();
this._params.childOMats[childOMatUid] = newChildOMat;
this._params.childOMatInfos[childKey] = {
accessChildAs,
cacheChild,
childOMatUid,
childIsSelf,
copyToChildFields,
internalKey,
initializer,
childKey,
namedChildren,
parentKey,
singleton
}
if (childIsSelf===true) {
newChildOMat.childOMats(this.childOMats());
this.field(parentKey);
}
this.reGroupChildren();
newChildOMat.needsParentAccessor();
return this;
}
_addFieldCopier(copy) {
const { key, as } = copy._params;
this._params.copyToChildFields.push([key, as]);
}
_acceptFieldOMat(fieldOMat) {
this._params.fieldOMats.push(fieldOMat);
}
}
export default ClassOMat;