Skip to content

Commit

Permalink
Introduce EloquentAttribute which manage known and dynamic attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
nhat-phan committed Mar 19, 2018
1 parent 94e9834 commit 0169787
Show file tree
Hide file tree
Showing 11 changed files with 529 additions and 541 deletions.
19 changes: 19 additions & 0 deletions dist/lib/model/EloquentAttribute.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Eloquent } from './Eloquent';
export declare class EloquentAttribute {
protected known: string[];
protected dynamic: {
[key: string]: {
name: string;
getter: boolean;
setter: boolean;
accessor?: string;
mutator?: string;
};
};
constructor(model: Eloquent, prototype: any);
protected createDynamicAttributeIfNeeded(property: string): void;
isKnownAttribute(name: string | Symbol): boolean;
buildKnownAttributes(model: Eloquent, prototype: any): void;
findGettersAndSetters(prototype: any): void;
findAccessorsAndMutators(prototype: any): void;
}
67 changes: 67 additions & 0 deletions dist/lib/model/EloquentAttribute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Eloquent_1 = require("./Eloquent");
const lodash_1 = require("lodash");
const EloquentProxy_1 = require("./EloquentProxy");
class EloquentAttribute {
constructor(model, prototype) {
this.dynamic = {};
this.known = [];
this.findGettersAndSetters(prototype);
this.findAccessorsAndMutators(prototype);
this.buildKnownAttributes(model, prototype);
}
createDynamicAttributeIfNeeded(property) {
if (!this.dynamic[property]) {
this.dynamic[property] = {
name: property,
getter: false,
setter: false
};
}
}
isKnownAttribute(name) {
if (typeof name === 'symbol') {
return true;
}
return this.known.indexOf(name) !== -1;
}
buildKnownAttributes(model, prototype) {
this.known = Array.from(new Set(model['getReservedProperties']().concat(Object.getOwnPropertyNames(model), EloquentProxy_1.GET_FORWARD_TO_DRIVER_FUNCTIONS, EloquentProxy_1.GET_QUERY_FUNCTIONS, Object.getOwnPropertyNames(Eloquent_1.Eloquent.prototype), Object.getOwnPropertyNames(prototype))));
}
findGettersAndSetters(prototype) {
const descriptors = Object.getOwnPropertyDescriptors(prototype);
for (const property in descriptors) {
const getter = lodash_1.isFunction(descriptors[property].get);
const setter = lodash_1.isFunction(descriptors[property].set);
if (!getter && !setter) {
continue;
}
this.createDynamicAttributeIfNeeded(property);
this.dynamic[property].getter = getter;
this.dynamic[property].setter = setter;
}
}
findAccessorsAndMutators(prototype) {
const names = Object.getOwnPropertyNames(prototype);
const regex = new RegExp('^(get|set)([a-zA-z0-9_\\-]{1,})Attribute$', 'g');
names.forEach(name => {
let match;
while ((match = regex.exec(name)) != undefined) {
// javascript RegExp has a bug when the match has length 0
// if (match.index === regex.lastIndex) {
// ++regex.lastIndex
// }
const property = lodash_1.snakeCase(match[2]);
this.createDynamicAttributeIfNeeded(property);
if (match[1] === 'get') {
this.dynamic[property].accessor = match[0];
}
else {
this.dynamic[property].mutator = match[0];
}
}
});
}
}
exports.EloquentAttribute = EloquentAttribute;
24 changes: 2 additions & 22 deletions dist/lib/model/EloquentMetadata.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Eloquent } from './Eloquent';
import { EloquentAttribute } from './EloquentAttribute';
export declare type EloquentTimestamps = {
createdAt: string;
updatedAt: string;
Expand All @@ -7,20 +8,6 @@ export declare type EloquentSoftDelete = {
deletedAt: string;
overrideMethods: boolean | 'all' | string[];
};
export declare type EloquentAccessors = {
[key: string]: {
name: string;
type: 'getter' | 'function' | string;
ref?: string;
};
};
export declare type EloquentMutators = {
[key: string]: {
name: string;
type: 'setter' | 'function' | string;
ref?: string;
};
};
/**
* This class contains all metadata parsing functions, such as:
* - fillable
Expand All @@ -36,15 +23,8 @@ export declare class EloquentMetadata {
protected prototype: any;
protected definition: typeof Eloquent;
protected knownAttributes: string[];
protected accessors: EloquentAccessors;
protected mutators: EloquentMutators;
protected attribute: EloquentAttribute;
private constructor();
protected buildKnownAttributes(): void;
/**
* Find accessors and mutators defined in getter/setter, only available for node >= 8.7
*/
protected findGettersAndSetters(): void;
protected findAccessorsAndMutators(): void;
getSettingProperty<T extends any>(property: string, defaultValue: T): T;
hasSetting(property: string): boolean;
getSettingWithDefaultForTrueValue(property: string, defaultValue: any): any;
Expand Down
63 changes: 3 additions & 60 deletions dist/lib/model/EloquentMetadata.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const najs_binding_1 = require("najs-binding");
const Eloquent_1 = require("./Eloquent");
const EloquentProxy_1 = require("./EloquentProxy");
const lodash_1 = require("lodash");
const EloquentAttribute_1 = require("./EloquentAttribute");
const DEFAULT_TIMESTAMPS = {
createdAt: 'created_at',
updatedAt: 'updated_at'
Expand All @@ -27,59 +25,7 @@ class EloquentMetadata {
this.model = model;
this.prototype = Object.getPrototypeOf(this.model);
this.definition = Object.getPrototypeOf(model).constructor;
this.accessors = {};
this.mutators = {};
this.buildKnownAttributes();
this.findGettersAndSetters();
this.findAccessorsAndMutators();
}
buildKnownAttributes() {
this.knownAttributes = Array.from(new Set(this.model['getReservedProperties']().concat(Object.getOwnPropertyNames(this.model), EloquentProxy_1.GET_FORWARD_TO_DRIVER_FUNCTIONS, EloquentProxy_1.GET_QUERY_FUNCTIONS, Object.getOwnPropertyNames(Eloquent_1.Eloquent.prototype), Object.getOwnPropertyNames(this.prototype))));
}
/**
* Find accessors and mutators defined in getter/setter, only available for node >= 8.7
*/
findGettersAndSetters() {
const descriptors = Object.getOwnPropertyDescriptors(this.prototype);
for (const name in descriptors) {
if (lodash_1.isFunction(descriptors[name].get)) {
this.accessors[name] = {
name: name,
type: 'getter'
};
}
if (lodash_1.isFunction(descriptors[name].set)) {
this.mutators[name] = {
name: name,
type: 'setter'
};
}
}
}
findAccessorsAndMutators() {
const names = Object.getOwnPropertyNames(this.prototype);
const regex = new RegExp('^(get|set)([a-zA-z0-9_\\-]{1,})Attribute$', 'g');
names.forEach(name => {
let match;
while ((match = regex.exec(name)) != undefined) {
// javascript RegExp has a bug when the match has length 0
// if (match.index === regex.lastIndex) {
// ++regex.lastIndex
// }
const property = lodash_1.snakeCase(match[2]);
const data = {
name: property,
type: 'function',
ref: match[0]
};
if (match[1] === 'get' && typeof this.accessors[property] === 'undefined') {
this.accessors[property] = data;
}
if (match[1] === 'set' && typeof this.mutators[property] === 'undefined') {
this.mutators[property] = data;
}
}
});
this.attribute = new EloquentAttribute_1.EloquentAttribute(model, this.prototype);
}
getSettingProperty(property, defaultValue) {
if (this.definition[property]) {
Expand Down Expand Up @@ -116,10 +62,7 @@ class EloquentMetadata {
return this.getSettingWithDefaultForTrueValue('softDeletes', defaultValue);
}
hasAttribute(name) {
if (typeof name === 'symbol') {
return true;
}
return this.knownAttributes.indexOf(name) !== -1;
return this.attribute.isKnownAttribute(name);
}
static get(model, cache = true) {
const className = model.getClassName();
Expand Down
1 change: 1 addition & 0 deletions dist/test/model/EloquentAttribute.test.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'jest';
148 changes: 148 additions & 0 deletions dist/test/model/EloquentAttribute.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
require("jest");
const najs_binding_1 = require("najs-binding");
const Eloquent_1 = require("../../lib/model/Eloquent");
const EloquentAttribute_1 = require("../../lib/model/EloquentAttribute");
const EloquentDriverProvider_1 = require("../../lib/drivers/EloquentDriverProvider");
const DummyDriver_1 = require("../../lib/drivers/DummyDriver");
const EloquentProxy_1 = require("../../lib/model/EloquentProxy");
EloquentDriverProvider_1.EloquentDriverProvider.register(DummyDriver_1.DummyDriver, 'dummy');
class Model extends Eloquent_1.Eloquent {
get accessor() {
return '';
}
set mutator(value) { }
getClassName() {
return 'Model';
}
modelMethod() { }
}
najs_binding_1.register(Model);
class ChildModel extends Model {
get child_accessor() {
return '';
}
set child_mutator(value) { }
getClassName() {
return 'ChildModel';
}
childModelMethod() { }
}
najs_binding_1.register(ChildModel);
const fakeModel = {
getReservedProperties() {
return [];
}
};
describe('EloquentAttribute', function () {
describe('.findGettersAndSetters()', function () {
it('finds all defined getters and put to accessors with type = getter', function () {
class ClassEmpty {
}
const attribute = new EloquentAttribute_1.EloquentAttribute(fakeModel, {});
attribute.findGettersAndSetters(Object.getPrototypeOf(new ClassEmpty()));
expect(attribute['dynamic']).toEqual({});
class Class {
get a() {
return '';
}
set a(value) { }
get b() {
return '';
}
}
attribute.findGettersAndSetters(Object.getPrototypeOf(new Class()));
expect(attribute['dynamic']).toEqual({
a: { name: 'a', getter: true, setter: true },
b: { name: 'b', getter: true, setter: false }
});
});
});
describe('.findAccessorsAndMutators()', function () {
it('finds all defined getters and put to accessors with type = getter', function () {
class ClassEmpty {
}
const attribute = new EloquentAttribute_1.EloquentAttribute(fakeModel, {});
attribute.findAccessorsAndMutators(Object.getPrototypeOf(new ClassEmpty()));
expect(attribute['dynamic']).toEqual({});
class Class {
get a() {
return '';
}
getAAttribute() { }
getFirstNameAttribute() { }
getWrongFormat() { }
set b(value) { }
setBAttribute() { }
setWrongFormat() { }
setDoublegetDoubleAttribute() { }
get c() {
return '';
}
set c(value) { }
getCAttribute() { }
setCAttribute() { }
}
attribute.findGettersAndSetters(Object.getPrototypeOf(new Class()));
attribute.findAccessorsAndMutators(Object.getPrototypeOf(new Class()));
expect(attribute['dynamic']).toEqual({
a: { name: 'a', getter: true, setter: false, accessor: 'getAAttribute' },
b: { name: 'b', getter: false, setter: true, mutator: 'setBAttribute' },
c: { name: 'c', getter: true, setter: true, accessor: 'getCAttribute', mutator: 'setCAttribute' },
first_name: { name: 'first_name', getter: false, setter: false, accessor: 'getFirstNameAttribute' },
doubleget_double: {
name: 'doubleget_double',
getter: false,
setter: false,
mutator: 'setDoublegetDoubleAttribute'
}
});
});
});
describe('protected .buildKnownAttributes()', function () {
const attribute = new EloquentAttribute_1.EloquentAttribute(fakeModel, {});
attribute.buildKnownAttributes(new Model(), Model.prototype);
it('merges reserved properties defined in .getReservedProperties() of model and driver', function () {
const props = new Model()['getReservedProperties']();
for (const name of props) {
expect(attribute['known'].indexOf(name) !== -1).toBe(true);
}
});
it('merges properties defined Eloquent.prototype', function () {
const props = Object.getOwnPropertyNames(Model.prototype);
for (const name of props) {
expect(attribute['known'].indexOf(name) !== -1).toBe(true);
}
});
it('merges properties defined in model', function () {
const props = ['accessor', 'mutator', 'modelMethod'];
for (const name of props) {
expect(attribute['known'].indexOf(name) !== -1).toBe(true);
}
// warning: props defined in model is not included in list
expect(attribute['known'].indexOf('props') === -1).toBe(true);
});
it('merges properties defined in child model', function () {
const childAttribute = new EloquentAttribute_1.EloquentAttribute(new ChildModel(), ChildModel.prototype);
const props = ['child_accessor', 'child_mutator', 'childModelMethod'];
for (const name of props) {
expect(childAttribute['known'].indexOf(name) !== -1).toBe(true);
}
// warning: props defined in model is not included in list
expect(childAttribute['known'].indexOf('child_props') === -1).toBe(true);
});
it('merges properties defined GET_FORWARD_TO_DRIVER_FUNCTIONS', function () {
const props = EloquentProxy_1.GET_FORWARD_TO_DRIVER_FUNCTIONS;
for (const name of props) {
expect(attribute['known'].indexOf(name) !== -1).toBe(true);
}
});
it('merges properties defined GET_QUERY_FUNCTIONS', function () {
const props = EloquentProxy_1.GET_QUERY_FUNCTIONS;
for (const name of props) {
expect(attribute['known'].indexOf(name) !== -1).toBe(true);
}
});
});
});
Loading

0 comments on commit 0169787

Please sign in to comment.