Skip to content

Commit

Permalink
Merge pull request #706 from emberjs/cacheable-default
Browse files Browse the repository at this point in the history
Support making computed properties cacheable by default
  • Loading branch information
wagenet committed Apr 20, 2012
2 parents 741ca32 + acafb13 commit f1032b3
Show file tree
Hide file tree
Showing 20 changed files with 81 additions and 39 deletions.
1 change: 1 addition & 0 deletions Rakefile
Expand Up @@ -367,6 +367,7 @@ task :test, [:suite] => :dist do |t, args|
"package=all&extendprototypes=true&nojshint=true",
"package=all&extendprototypes=true&jquery=1.6.4&nojshint=true",
"package=all&extendprototypes=true&jquery=git&nojshint=true",
"package=all&cpdefaultcacheable=true&nojshint=true",
"package=all&dist=build&nojshint=true"]
}

Expand Down
6 changes: 2 additions & 4 deletions packages/ember-debug/lib/main.js
@@ -1,7 +1,5 @@
/*global __fail__*/

require("ember-metal");

/**
Define an assertion that will throw an exception if the condition is not
met. Ember build tools will remove any calls to ember_assert() when
Expand Down Expand Up @@ -73,13 +71,13 @@ window.ember_warn = function(message, test) {
will be displayed.
*/
window.ember_deprecate = function(message, test) {
if (Ember.TESTING_DEPRECATION) { return; }
if (Ember && Ember.TESTING_DEPRECATION) { return; }

if (arguments.length === 1) { test = false; }
if ('function' === typeof test) { test = test()!==false; }
if (test) { return; }

if (Ember.ENV.RAISE_ON_DEPRECATION) { throw new Error(message); }
if (Ember && Ember.ENV.RAISE_ON_DEPRECATION) { throw new Error(message); }

var error, stackStr = '';

Expand Down
6 changes: 3 additions & 3 deletions packages/ember-handlebars/lib/controls/checkbox.js
Expand Up @@ -52,11 +52,11 @@ Ember.Checkbox = Ember.View.extend({

tagName: Ember.computed(function(){
return get(this, 'title') ? undefined : 'input';
}).property(),
}).property().volatile(),

attributeBindings: Ember.computed(function(){
return get(this, 'title') ? [] : ['type', 'checked', 'disabled'];
}).property(),
}).property().volatile(),

type: "checkbox",
checked: false,
Expand All @@ -82,7 +82,7 @@ Ember.Checkbox = Ember.View.extend({
} else {
return get(this, 'checked');
}
}).property('checked'),
}).property('checked').volatile(),

change: function() {
Ember.run.once(this, this._updateElementValue);
Expand Down
2 changes: 1 addition & 1 deletion packages/ember-handlebars/lib/controls/select.js
Expand Up @@ -127,7 +127,7 @@ Ember.SelectOption = Ember.View.extend({
// `new Number(4) !== 4`, we use `==` below
return content == selection;
}
}).property('content', 'parentView.selection'),
}).property('content', 'parentView.selection').volatile(),

labelPathDidChange: Ember.observer(function() {
var labelPath = getPath(this, 'parentView.optionLabelPath');
Expand Down
4 changes: 2 additions & 2 deletions packages/ember-handlebars/lib/controls/tabs/tab_pane_view.js
Expand Up @@ -3,9 +3,9 @@ var get = Ember.get, getPath = Ember.getPath;
Ember.TabPaneView = Ember.View.extend({
tabsContainer: Ember.computed(function() {
return this.nearestInstanceOf(Ember.TabContainerView);
}).property(),
}).property().volatile(),

isVisible: Ember.computed(function() {
return get(this, 'viewName') === getPath(this, 'tabsContainer.currentView');
}).property('tabsContainer.currentView')
}).property('tabsContainer.currentView').volatile()
});
2 changes: 1 addition & 1 deletion packages/ember-handlebars/lib/controls/tabs/tab_view.js
Expand Up @@ -3,7 +3,7 @@ var get = Ember.get, setPath = Ember.setPath;
Ember.TabView = Ember.View.extend({
tabsContainer: Ember.computed(function() {
return this.nearestInstanceOf(Ember.TabContainerView);
}).property(),
}).property().volatile(),

mouseUp: function() {
setPath(this, 'tabsContainer.currentView', get(this, 'value'));
Expand Down
2 changes: 1 addition & 1 deletion packages/ember-handlebars/lib/views/bindable_span.js
Expand Up @@ -96,7 +96,7 @@ Ember._BindableSpanView = Ember.View.extend(Ember.Metamorph,
}

return valueNormalizer ? valueNormalizer(result) : result;
}).property('property', 'previousContext', 'valueNormalizerFunc'),
}).property('property', 'previousContext', 'valueNormalizerFunc').volatile(),

rerenderIfNeeded: function() {
if (!get(this, 'isDestroyed') && get(this, 'normalizedValue') !== this._lastNormalizedValue) {
Expand Down
2 changes: 1 addition & 1 deletion packages/ember-handlebars/tests/handlebars_test.js
Expand Up @@ -1593,7 +1593,7 @@ test("should be able to update when bound property updates", function(){
valueBinding: 'MyApp.controller',
computed: Ember.computed(function(){
return this.getPath('value.name') + ' - computed';
}).property('value')
}).property('value').volatile()
});

view = View.create();
Expand Down
28 changes: 24 additions & 4 deletions packages/ember-metal/lib/computed.js
Expand Up @@ -10,6 +10,10 @@ require('ember-metal/platform');
require('ember-metal/utils');
require('ember-metal/properties');


ember_warn("Computed properties will soon be cacheable by default. To enable this in your app, set `ENV.CP_DEFAULT_CACHEABLE = true`.", Ember.CP_DEFAULT_CACHEABLE);


var meta = Ember.meta;
var guidFor = Ember.guidFor;
var USE_ACCESSORS = Ember.USE_ACCESSORS;
Expand Down Expand Up @@ -80,7 +84,7 @@ function addDependentKeys(desc, obj, keyName) {
/** @private */
function ComputedProperty(func, opts) {
this.func = func;
this._cacheable = opts && opts.cacheable;
this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : Ember.CP_DEFAULT_CACHEABLE;
this._dependentKeys = opts && opts.dependentKeys;
}

Expand Down Expand Up @@ -165,18 +169,34 @@ var Cp = ComputedProperty.prototype;
}.property('firstName', 'lastName').cacheable()
});
It is common to use `cacheable()` on nearly every computed property
you define.
Properties are cacheable by default.
@name Ember.ComputedProperty.cacheable
@param {Boolean} aFlag optional set to false to disable cacheing
@param {Boolean} aFlag optional set to false to disable caching
@returns {Ember.ComputedProperty} receiver
*/
Cp.cacheable = function(aFlag) {
this._cacheable = aFlag!==false;
return this;
};

/**
Call on a computed property to set it into non-cached mode. When in this
mode the computed property will not automatically cache the return value.
MyApp.outsideService = Ember.Object.create({
value: function() {
return OutsideService.getValue();
}.property().volatile()
});
@name Ember.ComputedProperty.volatile
@returns {Ember.ComputedProperty} receiver
*/
Cp.volatile = function() {
return this.cacheable(false);
};

/**
Sets the dependent keys on this computed property. Pass any number of
arguments containing key paths that this computed property depends on.
Expand Down
15 changes: 15 additions & 0 deletions packages/ember-metal/lib/core.js
Expand Up @@ -91,6 +91,21 @@ Ember.EXTEND_PROTOTYPES = (Ember.ENV.EXTEND_PROTOTYPES !== false);
Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES;


/**
@static
@type Boolean
@default false
@constant
Determines whether computed properties are cacheable by default.
In future releases this will default to `true`. For the 1.0 release,
the option to turn off caching by default will be removed entirely.
When caching is enabled by default, you can use `volatile()` to disable
caching on individual computed properties.
*/
Ember.CP_DEFAULT_CACHEABLE = !!Ember.ENV.CP_DEFAULT_CACHEABLE;


/**
Empty function. Useful for some operations.
Expand Down
2 changes: 1 addition & 1 deletion packages/ember-metal/tests/binding/sync_test.js
Expand Up @@ -15,7 +15,7 @@ testBoth("bindings should not sync twice in a single run loop", function(get, se
getCalled++;
return setValue;
}
}));
}).volatile());

b = {
a: a
Expand Down
4 changes: 2 additions & 2 deletions packages/ember-runtime/lib/mixins/enumerable.js
Expand Up @@ -146,7 +146,7 @@ Ember.Enumerable = Ember.Mixin.create( /** @lends Ember.Enumerable */ {
ret = this.nextObject(0, null, context);
pushCtx(context);
return ret ;
}).property(),
}).property().volatile(),

/**
Helper method returns the last object from a collection. If your enumerable
Expand Down Expand Up @@ -175,7 +175,7 @@ Ember.Enumerable = Ember.Mixin.create( /** @lends Ember.Enumerable */ {
pushCtx(context);
return last;
}
}).property(),
}).property().volatile(),

/**
Returns true if the passed object can be found in the receiver. The
Expand Down
Expand Up @@ -53,7 +53,7 @@ module("object.get()", {
numberVal: 24,
toggleVal: true,

computed: Ember.computed(function() { return 'value'; }).property(),
computed: Ember.computed(function() { return 'value'; }).property().volatile(),

method: function() { return "value"; },

Expand Down Expand Up @@ -103,7 +103,7 @@ module("Ember.get()", {
numberVal: 24,
toggleVal: true,

computed: Ember.computed(function() { return 'value'; }).property(),
computed: Ember.computed(function() { return 'value'; }).property().volatile(),

method: function() { return "value"; },

Expand Down Expand Up @@ -178,7 +178,7 @@ module("Ember.getPath()");
test("should return a property at a given path relative to the window", function() {
window.Foo = ObservableObject.create({
Bar: ObservableObject.create({
Baz: Ember.computed(function() { return "blargh"; }).property()
Baz: Ember.computed(function() { return "blargh"; }).property().volatile()
})
});

Expand All @@ -192,7 +192,7 @@ test("should return a property at a given path relative to the window", function
test("should return a property at a given path relative to the passed object", function() {
var foo = ObservableObject.create({
bar: ObservableObject.create({
baz: Ember.computed(function() { return "blargh"; }).property()
baz: Ember.computed(function() { return "blargh"; }).property().volatile()
})
});

Expand Down Expand Up @@ -242,7 +242,7 @@ module("object.set()", {
this._computed = value ;
}
return this._computed ;
}).property(),
}).property().volatile(),

// method, but not a property
_method: "method",
Expand Down Expand Up @@ -328,7 +328,7 @@ module("Computed properties", {
computed: Ember.computed(function(key, value) {
this.computedCalls.push(value);
return 'computed';
}).property(),
}).property().volatile(),

computedCachedCalls: [],
computedCached: Ember.computed(function(key, value) {
Expand All @@ -345,13 +345,13 @@ module("Computed properties", {
dependent: Ember.computed(function(key, value) {
this.dependentCalls.push(value);
return 'dependent';
}).property('changer'),
}).property('changer').volatile(),

dependentFrontCalls: [],
dependentFront: Ember.computed('changer', function(key, value) {
this.dependentFrontCalls.push(value);
return 'dependentFront';
}),
}).volatile(),

dependentCachedCalls: [],
dependentCached: Ember.computed(function(key, value) {
Expand All @@ -376,12 +376,12 @@ module("Computed properties", {
isOn: Ember.computed(function(key, value) {
if (value !== undefined) this.set('state', 'on');
return this.get('state') === 'on';
}).property('state'),
}).property('state').volatile(),

isOff: Ember.computed(function(key, value) {
if (value !== undefined) this.set('state', 'off');
return this.get('state') === 'off';
}).property('state')
}).property('state').volatile()

}) ;
}
Expand Down Expand Up @@ -558,7 +558,7 @@ test("nested dependent keys should propagate after they update", function() {

price: Ember.computed(function() {
return this.getPath('restaurant.menu.price');
}).property('restaurant.menu.price')
}).property('restaurant.menu.price').volatile()
});

var bindObj = ObservableObject.create({
Expand Down
Expand Up @@ -130,7 +130,7 @@ test("should invalidate function property cache when notifyPropertyChange is cal
return this;
}
return this._b;
})
}).volatile()
});

a.set('b', 'foo');
Expand Down
Expand Up @@ -143,7 +143,7 @@ test("can retrieve metadata for a computed property", function() {

var ClassWithNoMetadata = Ember.Object.extend({
computedProperty: Ember.computed(function() {
}).property(),
}).property().volatile(),

staticProperty: 12
});
Expand Down
Expand Up @@ -41,7 +41,7 @@ test('chains should copy forward to subclasses when prototype created', function
hiBinding: 'obj.hi', // add chain
hello: Ember.computed(function() {
return this.getPath('obj.hi') + ' world';
}).property('hi'), // observe chain
}).property('hi').volatile(), // observe chain
greetingBinding: 'hello'
});
SubSub = SubWithChains.extend();
Expand Down
4 changes: 2 additions & 2 deletions packages/ember-views/lib/views/view.js
Expand Up @@ -226,13 +226,13 @@ Ember.View = Ember.Object.extend(Ember.Evented,
} else {
return parent;
}
}).property('_parentView'),
}).property('_parentView').volatile(),

// return the current view, not including virtual views
concreteView: Ember.computed(function() {
if (!this.isVirtual) { return this; }
else { return get(this, 'parentView'); }
}).property('_parentView'),
}).property('_parentView').volatile(),

/**
If false, the view will appear hidden in DOM.
Expand Down
4 changes: 2 additions & 2 deletions packages/ember-views/tests/views/view/init_test.js
Expand Up @@ -24,7 +24,7 @@ test("should warn if a non-array is used for classNames", function() {
Ember.View.create({
classNames: Ember.computed(function() {
return ['className'];
}).property()
}).property().volatile()
});
}, /Only arrays are allowed/i, 'should warn that an array was not used');
});
Expand All @@ -34,7 +34,7 @@ test("should warn if a non-array is used for classNamesBindings", function() {
Ember.View.create({
classNameBindings: Ember.computed(function() {
return ['className'];
}).property()
}).property().volatile()
});
}, /Only arrays are allowed/i, 'should warn that an array was not used');
});
9 changes: 8 additions & 1 deletion tests/index.html
Expand Up @@ -32,12 +32,19 @@
}


window.ENV = window.ENV || {};

// Handle extending prototypes
QUnit.config.urlConfig.push('extendprototypes');

window.ENV = window.ENV || {};
var extendPrototypes = QUnit.urlParams.extendprototypes;
ENV['EXTEND_PROTOTYPES'] = !!extendPrototypes;

// Handle CP cacheable by default
QUnit.config.urlConfig.push('cpdefaultcacheable');

var cpDefaultCacheable = QUnit.urlParams.cpdefaultcacheable;
ENV['CP_DEFAULT_CACHEABLE'] = !!cpDefaultCacheable;
</script>
</head>
<body>
Expand Down
1 change: 1 addition & 0 deletions tests/qunit/run-qunit.js
Expand Up @@ -11,6 +11,7 @@ if (args.length < 1 || args.length > 2) {
var page = require('webpage').create();

page.onConsoleMessage = function(msg) {
if (msg.slice(0,8) === 'WARNING:') { return; }
console.log(msg);
};

Expand Down

0 comments on commit f1032b3

Please sign in to comment.