diff --git a/packages/ember-runtime/lib/system/core_object.js b/packages/ember-runtime/lib/system/core_object.js index 0d2058b634c..79484c7b106 100644 --- a/packages/ember-runtime/lib/system/core_object.js +++ b/packages/ember-runtime/lib/system/core_object.js @@ -50,6 +50,7 @@ var applyMixin = Mixin._apply; var finishPartial = Mixin.finishPartial; var reopen = Mixin.prototype.reopen; var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; +var hasCachedComputedProperties = false; var undefinedDescriptor = { configurable: true, @@ -746,6 +747,26 @@ var ClassMixin = Mixin.create({ return desc._meta || {}; }, + _computedProperties: Ember.computed(function() { + hasCachedComputedProperties = true; + var proto = this.proto(); + var descs = meta(proto).descs; + var property; + var properties = []; + + for (var name in descs) { + property = descs[name]; + + if (property instanceof ComputedProperty) { + properties.push({ + name: name, + meta: property._meta + }); + } + } + return properties; + }).readOnly(), + /** Iterate over each computed property for the class, passing its name and any associated metadata (see `metaForProperty`) to the callback. @@ -755,17 +776,15 @@ var ClassMixin = Mixin.create({ @param {Object} binding */ eachComputedProperty: function(callback, binding) { - var proto = this.proto(); - var descs = meta(proto).descs; + var property, name; var empty = {}; - var property; - for (var name in descs) { - property = descs[name]; + var properties = get(this, '_computedProperties'); - if (property instanceof ComputedProperty) { - callback.call(binding || this, name, property._meta || empty); - } + for (var i = 0, length = properties.length; i < length; i++) { + property = properties[i]; + name = property.name; + callback.call(binding || this, property.name, property.meta || empty); } } }); @@ -777,6 +796,23 @@ if (Ember.config.overrideClassMixin) { } CoreObject.ClassMixin = ClassMixin; + ClassMixin.apply(CoreObject); +CoreObject.reopen({ + didDefineProperty: function(proto, key, value) { + if (hasCachedComputedProperties === false) { return; } + if (value instanceof Ember.ComputedProperty) { + var cache = Ember.meta(this.constructor).cache; + + if (cache._computedProperties !== undefined) { + cache._computedProperties = undefined; + } + } + + this._super(); + } +}); + + export default CoreObject; diff --git a/packages/ember-runtime/tests/system/object/computed_test.js b/packages/ember-runtime/tests/system/object/computed_test.js index 44d13fb6fa4..fd5bbb1475a 100644 --- a/packages/ember-runtime/tests/system/object/computed_test.js +++ b/packages/ember-runtime/tests/system/object/computed_test.js @@ -155,7 +155,7 @@ test("can retrieve metadata for a computed property", function() { }, "metaForProperty() could not find a computed property with key 'staticProperty'."); }); -testBoth("can iterate over a list of computed properties for a class", function(get, set) { +test("can iterate over a list of computed properties for a class", function() { var MyClass = EmberObject.extend({ foo: computed(function() { @@ -204,3 +204,37 @@ testBoth("can iterate over a list of computed properties for a class", function( deepEqual(list.sort(), ['bar', 'bat', 'baz', 'foo'], "all inherited properties are included"); }); + +test("list of properties updates when an additional property is added (such cache busting)", function() { + var MyClass = EmberObject.extend({ + foo: computed(Ember.K), + + fooDidChange: observer('foo', function() { + + }), + + bar: computed(Ember.K) + }); + + var list = []; + + MyClass.eachComputedProperty(function(name) { + list.push(name); + }); + + deepEqual(list.sort(), ['bar', 'foo'].sort(), 'expected two computed properties'); + + MyClass.reopen({ + baz: computed(Ember.K) + }); + + MyClass.create(); // force apply mixins + + list = []; + + MyClass.eachComputedProperty(function(name) { + list.push(name); + }); + + deepEqual(list.sort(), ['bar', 'foo', 'baz'].sort(), 'expected three computed properties'); +});