App.Person = Ember.Object.extend({
  firstName: "Tom",
  surname: "Close",

  name: function() {
    return this.get('firstName') + " " + this.get('surname');
  }.property('firstName', 'surname'),
});

var tom = App.Person.create();
tom.firstName
// "Tom"
tom.firstName = "Thomas"
// "Thomas"
tom.get('firstName')
// "Thomas"
tom.firstName = "Alan"
// "Alan"
tom.get('name')
// "Alan Close"
tom.firstName = "Tom"
// Error: Assertion Failed: You must use Ember.set() to access this property (of <App.Person:ember381>)

If we look at the object we see that we now have a few new functions:

tom
Class {constructor: function, _super: function, firstName: "Tom", surname: "Close", title: "Mr"}
// snip
get firstName: function () {
set firstName: function (value) {
get surname: function () {
set surname: function (value) {

Ember has used defineProperty to provide setters and getters for when you call tom.firstName' andtom.firstName=. The setter will just raise the error that we hit above; the getter looks in themeta[cache]` for the stored.

The relevant file here is ember-metal/libs/property-get.js. I’m a little unsure why this isn’t triggered when you just do tom.get('firstName') though.

In ember-metal/libs/property-set.js we see that the setter calls propertyDidChange(obj, keyname) on setting (if the property is a vanilla non-descriptor sort). This notifies dependent keys, chains and observers (if anything is watching that key). We can call these manually.

For the dependent keys, it iterates over the graph (stored in meta[deps]), calling propertyDidChange on each key. On vanilla keys this does nothing, but on descriptors it calls .didChange(obj, keyname).

Calling didChange(obj, keyname) on a ComputedProperty sets the cached vaule to undefined and removes the dependentKeys. ~~~js ComputedPropertyPrototype.didChange = function(obj, keyName) { // suspended is set via a CP.set to ensure we don’t clear // the cached value set by the setter if (this.cacheable && this._suspended !== obj) { var meta = metaFor(obj); if (meta.cache[keyName] !== undefined) { meta.cache[keyName] = undefined; removeDependentKeys(this, obj, keyName, meta); } } }; ~~~

RemoveDependentKeys 1. Looks up on the descriptor to see what its dependent keys are (‘firstName’, 'surname’) 2. Reduces the count of the property ('name’) in the dependent keys. 3. Stops watching.

printMeta(tom)
== Cache ==
name: Tom Close
== Values ==
firstName: Tom
surname: Close
== Watching ==
firstName: 1
surname: 1
== Deps ==
firstName:
  name: 1
surname:
  name: 1
== Chain watchers ==
== Chains ==


> Em.propertyDidChange(tom, 'name')
undefined
> printMeta(tom)
== Cache ==
== Values ==
firstName: Tom
surname: Close
== Watching ==
firstName: 0
surname: 0
== Deps ==
firstName:
  name: 0
surname:
  name: 0
== Chain watchers ==
== Chains ==

So that’s ok for properties. What about chains?

Aside: defining properties: ~~~js // define a simple property Ember.defineProperty(contact, 'lastName’, undefined, 'Jolley’);

// define a computed property Ember.defineProperty(contact, 'fullName’, Ember.computed(function() { return this.firstName+’ ’+this.lastName; }).property('firstName’, 'lastName’)); ~~~

tom.get('locationName')
null
printMeta(tom)
== Cache ==
locationName: null
== Values ==
location: null
== Watching ==
location.name:
location: 1
== Deps ==
location.name:
  locationName: 1
== Chain watchers ==
location:
  0:
    {Key: location, Value: null, Object: <App.Person:ember252>, Watching: true, Count: 1}
    -> {Key: null, Value: <App.Person:ember252>, Object: undefined, Watching: false, Count: 0}
== Chains ==
root: {Key: null, Value: <App.Person:ember252>, Object: undefined, Watching: false, Count: 0}
  location: {Key: location, Value: null, Object: <App.Person:ember252>, Watching: true, Count: 1}
    name: {Key: name, Value: undefined, Object: null, Watching: true, Count: 1}
undefined

ChainNode.chainsWillChange ChainNode.willChange

seems to just walk the chain lattice, collecting the object/property pairs to fire the propertyWillChange events on. Which in turn essentially just fires didChange on the descriptors. Which just clears the cache and removes the dependent keys