Skip to content

Instantly share code, notes, and snippets.

@mfenniak
Last active December 5, 2015 16:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mfenniak/bb66e5cd7b83517217c5 to your computer and use it in GitHub Desktop.
Save mfenniak/bb66e5cd7b83517217c5 to your computer and use it in GitHub Desktop.
import Ember from 'ember';
// computedDependent is a wrapper around Ember.computed that promotes a property access pattern that
// ensures that a computed property declares its dependencies accurately.
//
// You create a computed property with computedDependent, passing in the getter function and the names
// of the dependent properties as additional arguments. The getter function will be called with a
// single object argument which has ES5 properties defined upon it matching the names of the dependent
// properties; you can then use this 'props' argument to access the dependent properties.
//
// Example:
// fullName: computedDependent((props) => props.firstName + ' ' + props.lastName, 'firstName', 'lastName')
//
// This example is analogous to this normal Ember pattern:
// Ember.computed('firstName', 'lastName', () => this.get('firstName') + ' ' + this.get('lastName'))
//
// The advantage of computedDependent is that with the normal Ember pattern, it is possible for you to
// write 100% functioning code that passes common unit-tests by defining the getter function correctly,
// but missing or neglecting a dependent property in the Ember.computed() function call. The code will
// work correctly in a variety of testing scenarios, but then one day surprise you by failing to update
// properly because the property dependency isn't defined correctly.
//
// With computedDependent, if you missed a dependent property in the computedDependent() call, accessing
// it on the props object would always return undefined. This is far more likely cause an early testing
// failure as it would never work correctly. If it works when the dependent properties are static, it will
// work just as well when they're dynamic.
//
// computedDependent maintains all the advantages that a normal Ember pattern has, such as only accessing
// properties when they're actually used by the getter function. It also caches property accesses within
// each call to the getter (I'm not so sure how valuable that is...). It also works just fine for nested
// properties; eg.
// fullName: computedDependent((props) => props['name.first'] + ' ' + props['name.last'], 'name.first', 'name.last')
//
function computedDependent(getterFunction, ...dependentProperties) {
const PropertyAccessor = function(targetObject) {
this.__targetObject = targetObject;
this.__cache = new Map();
};
dependentProperties.forEach((dependantProperty) => {
Object.defineProperty(
PropertyAccessor.prototype,
dependantProperty,
{
get() {
if (this.__cache.has(dependantProperty)) {
return this.__cache.get(dependantProperty);
}
const value = this.__targetObject.get(dependantProperty);
this.__cache.set(dependantProperty, value);
return value;
}
});
});
return Ember.computed(...dependentProperties, {
get() {
return getterFunction.apply(this, [new PropertyAccessor(this)]);
}
});
}
// Example usage:
export default Ember.Component.extend({
cardNumberValid: computedDependent(function(props) {
return !Ember.isBlank(props.cardNumber) && Stripe.card.validateCardNumber(props.cardNumber);
}, "cardNumber")
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment