Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support making computed properties cacheable by default #706

Merged
merged 6 commits into from Apr 20, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -1565,7 +1565,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