Skip to content

Commit db4fcbd

Browse files
committed
refactor: reduce amount of work on initialization
- Simplify approach to wrapping most properties - Reduce the amount of work done when initializing objects
1 parent 81ae6c1 commit db4fcbd

15 files changed

Lines changed: 206 additions & 247 deletions

addon/-private/extensions/with-validation.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { wrapField } from '../wrap-field';
1+
import Ember from 'ember';
2+
3+
import wrapComputedProperty from '../wrap-computed-property';
24
import { getValidationsFor } from '../validations-for';
35

46
const HAS_VALIDATION = new WeakSet();
@@ -21,10 +23,17 @@ export function withExtension(klass) {
2123
init(...args) {
2224
super.init(...args);
2325

24-
const validations = getValidationsFor(this.constructor);
26+
const { constructor } = this;
27+
28+
const validations = getValidationsFor(constructor);
29+
const meta = Ember.meta(this);
2530

2631
for (let key in validations) {
27-
wrapField(this.constructor, this, validations, key);
32+
const validation = validations[key];
33+
34+
wrapComputedProperty(constructor, this, meta, validation, key);
35+
36+
validation.run(constructor, key, this[key], 'init');
2837
}
2938
}
3039
};

addon/-private/utils/computed.js

Lines changed: 0 additions & 25 deletions
This file was deleted.

addon/-private/utils/object.js

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
1-
/**
2-
* Walk up the prototype chain and find the property descriptor for the
3-
* given property
4-
*
5-
* @param {object} target
6-
* @param {string} property
7-
* @return {Descriptor|undefined}
8-
*/
9-
export function getPropertyDescriptor(target, property) {
10-
if (target === undefined) return;
11-
12-
return (
13-
Object.getOwnPropertyDescriptor(target, property) ||
14-
getPropertyDescriptor(Object.getPrototypeOf(target), property)
15-
);
16-
}
17-
181
export function isExtensionOf(childClass, parentClass) {
192
return childClass.prototype instanceof parentClass;
203
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
function guardBind(fn, ...args) {
2+
if (typeof fn === 'function') {
3+
return fn.bind(...args);
4+
}
5+
}
6+
7+
class ComputedValidatedProperty {
8+
constructor(desc, klass, originalValue, typeValidators) {
9+
this.isDescriptor = true;
10+
11+
this.desc = desc;
12+
this.klass = klass;
13+
this.originalValue = originalValue;
14+
this.typeValidators = typeValidators;
15+
16+
this.setup = guardBind(desc.setup, desc);
17+
this.teardown = guardBind(desc.teardown, desc);
18+
this.willChange = guardBind(desc.willChange, desc);
19+
this.didChange = guardBind(desc.didChange, desc);
20+
this.willWatch = guardBind(desc.willWatch, desc);
21+
this.didUnwatch = guardBind(desc.didUnwatch, desc);
22+
}
23+
24+
get(obj, keyName) {
25+
let { klass, typeValidators } = this;
26+
let newValue = this.desc.get(obj, keyName);
27+
28+
if (typeValidators) {
29+
typeValidators.run(klass, keyName, newValue, 'get');
30+
}
31+
32+
return newValue;
33+
}
34+
35+
set(obj, keyName, value) {
36+
let { klass, typeValidators } = this;
37+
let newValue = this.desc.set(obj, keyName, value);
38+
39+
if (typeValidators) {
40+
typeValidators.run(klass, keyName, newValue, 'set');
41+
}
42+
43+
return newValue;
44+
}
45+
}
46+
47+
export default function wrapComputedProperty(
48+
klass,
49+
instance,
50+
meta,
51+
typeValidators,
52+
key
53+
) {
54+
const possibleDesc = meta.peekDescriptors(key);
55+
56+
// Handle the argument being overwritten by a computed property
57+
if (possibleDesc && possibleDesc.isDescriptor) {
58+
let originalValue = possibleDesc.get(instance, key);
59+
60+
let validatedProperty = new ComputedValidatedProperty(
61+
possibleDesc,
62+
klass,
63+
originalValue,
64+
typeValidators
65+
);
66+
67+
Object.defineProperty(instance, key, {
68+
configurable: true,
69+
enumerable: true,
70+
get() {
71+
return validatedProperty.get(instance, key);
72+
}
73+
});
74+
75+
meta.writeDescriptors(key, validatedProperty);
76+
}
77+
}

addon/-private/wrap-descriptor.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Transforms a `field` descriptor into a property `getter/setter` that
3+
* ensures that the value matches the validators.
4+
*
5+
* @param {Object} element
6+
* @param {Function} validator
7+
*/
8+
function wrapFieldDescriptor(
9+
{ descriptor, initializer, key, placement },
10+
validator
11+
) {
12+
const { ...descriptorToApply } = descriptor;
13+
delete descriptorToApply.writable;
14+
15+
let initialized = false;
16+
let cachedValue;
17+
18+
const newElement = {
19+
key,
20+
placement,
21+
kind: 'method',
22+
descriptor: {
23+
...descriptorToApply,
24+
get() {
25+
if (!initialized) {
26+
initialized = true;
27+
cachedValue = initializer ? initializer.call(this) : undefined;
28+
}
29+
30+
return cachedValue;
31+
},
32+
set(newValue) {
33+
initialized = true;
34+
35+
validator.run(this.constructor, key, newValue, 'set');
36+
37+
cachedValue = newValue;
38+
}
39+
}
40+
};
41+
42+
return newElement;
43+
}
44+
45+
/**
46+
* Wraps a property `getter/setter` such that the value must match the
47+
* validator.
48+
*
49+
* @param {Object} element
50+
* @param {Function} validator
51+
*/
52+
function wrapMethodDescriptor({ descriptor, key, ...rest }, validator) {
53+
const { get, set, ...restOfDescriptor } = descriptor;
54+
55+
const newElement = {
56+
...rest,
57+
key,
58+
descriptor: {
59+
...restOfDescriptor,
60+
get() {
61+
const value = get.call(this);
62+
63+
validator.run(this.constructor, key, value, 'get');
64+
65+
return value;
66+
},
67+
set(newValue) {
68+
const setterReturnedValue = set.call(this, newValue);
69+
70+
validator.run(this.constructor, key, this[key], 'set');
71+
72+
return setterReturnedValue;
73+
}
74+
}
75+
};
76+
77+
return newElement;
78+
}
79+
80+
/**
81+
* Transforms a `field` descriptor, wrapping it with a setter that performs
82+
* validator.
83+
*
84+
* @param {Object} element
85+
* @param {Function} validator validator function for new values
86+
*/
87+
export default function wrapDescriptor(element, validator) {
88+
switch (element.kind) {
89+
case 'field':
90+
return wrapFieldDescriptor(element, validator);
91+
case 'method':
92+
return wrapMethodDescriptor(element, validator);
93+
default:
94+
throw new Error(
95+
'`@argument` must be applied to a `field` or property accessor'
96+
);
97+
}
98+
}

0 commit comments

Comments
 (0)