Home Identifier Source Repository

src/models/$Fields.js

/**
 * @module $Fields.js
 * @author Joe Groseclose <@benderTheCrime>
 * @date 8/23/2015
 */

// System Modules
import util from                'util';
import {magenta, cyan} from     'chalk';
import $LogProvider from        'angie-log';

const p = process;

class BaseField {
    constructor(
        args = 0,
        maxValue = undefined,
        minLength = 0,
        maxLength = undefined,
        nullable = false,
        unique = false,
        $default = undefined
    ) {
        this.type = 'BaseField';
        if (typeof args === 'object') {
            util._extend(this, arguments[0]);
        } else if (!isNaN(args)) {
            if (args === 1) {
                return;
            }
            [
                this.minValue,
                this.maxValue,
                this.minLength,
                this.maxLength,
                this.nullable,
                this.unique,
                this.default
            ] = [
                args,
                maxValue,
                minLength,
                maxLength,
                nullable,
                unique,
                $default
            ];
        }
    }
    create() {

        // TODO this method is not responsible for migrating a model, only
        // creating a field in a record when the model is instantiated
        if (this.default) {
            if (this.validate(this.default)) {
                this.value = typeof this.default === 'function' ? this.default() :
                    this.default;
            } else {
                throw new $$InvalidFieldConfigError(
                    this.type,
                    'Invalid default value'
                );
            }
        }
    }
    validate(value) {

        // TODO is this necessary?
        // value = value || this.value;
        if (!value && !this.nullable) {
            return false;
        }
        if (
            typeof value === 'string' &&
            (
                (
                    this.minLength &&
                    value.length < this.minLength
                ) ||
                (
                    this.maxLength &&
                    value.length > this.maxLength
                )
            )
        ) {
            return false;
        } else if (
            (
                this.minValue &&
                value < this.minValue
            ) ||
            (
                this.maxValue &&
                value > this.maxValue
            )
        ) {
            return false;
        }
        return true;
    }
}

class CharField extends BaseField {
    constructor() {
        super(...arguments);
        this.type = 'CharField';
    }
    create() {
        super.create();
        if (!this.value) {
            this.value = '';
        }
    }
    validate(value) {
        value = this.value || value;
        if (typeof value !== 'string') {
            return false;
        }
        return super.validate(value);
    }
}

class IntegerField extends BaseField {
    constructor() {
        super(...arguments);
        this.type = 'IntegerField';
    }
}

class KeyField extends IntegerField {
    constructor() {
        super(1);
        this.type = 'KeyField';
        this.unique = false;
        this.minValue = 1;
        this.maxLength = 11;
        this.nullable = false;
    }
}

class ForeignKeyField extends KeyField {
    constructor(rel, args) {
        super(args);

        this.type = 'ForeignKeyField';

        if (!rel || !global.app.Models[ rel ]) {
            throw new $$InvalidFieldConfigError(
                this.type,
                `Invalid relative model ${rel ? `${cyan(rel)} ` : ''}` +
                'in constrained field declaration'
            );
        }

        this.nesting = this.deepNesting = false;
        if (args && typeof args === 'object') {

            if (args.hasOwnProperty('deepNesting')) {
                this.nesting = this.deepNesting = true;
            } else {
                this.nesting = args.hasOwnProperty('nesting');
            }
        }
        this.rel = rel;
        this.type = 'ForeignKeyField';
    }
}

class ManyToManyField extends ForeignKeyField {
    constructor(rel, args = {}) {
        super(rel, args);

        this.type = 'ManyToManyField';
        this.unique = false;

        this.name = args.alias || args.name;
        if (!this.name) {
            throw new $$InvalidFieldConfigError(
                this.type,
                `${cyan(`${this.type}s`)} require name to be included in ` +
                'configuration and to be valid model'
            );
        }

        // Setup a reference to the relationship model
        this.crossReferenceTableId = args.tableName || `${this.name}_${this.rel}_id`;
        if (!args.crossReferenceTable) {
            global.app.Model(this.crossReferenceTableId, {
                [ `${this.name}_id` ]: new KeyField(),
                [ `${this.rel}_id` ]: new KeyField()
            });
            this.crossReferenceTable = global.app.Models[
                this.crossReferenceTableId
            ];
        } else {
            this.crossReferenceTableId = args.crossReferenceTableId;
            this.crossReferenceTable = args.crossReferenceTable;
        }
    }
}

class $$InvalidFieldConfigError extends TypeError {
    constructor(type, error = '') {
        $LogProvider.error(
            `Invalid Field configuration for ${magenta(type)}` +
            `${error ? `: ${error}` : ''}`
        );
        super();
        p.exit(1);
    }
}

export {
    CharField,
    IntegerField,
    ForeignKeyField,
    ManyToManyField
};