Skip to content

Commit

Permalink
Reorganize materialization process
Browse files Browse the repository at this point in the history
Add an additional state (materializing) that a
record goes into when its underlying data is
materialized.

Also add an AttributeChange object in prep for
future consistency with RelationshipChange.

This change replaces the setProperty event with a
pair of willSetProperty and didSetProperty events.
  • Loading branch information
wycats committed Dec 21, 2012
1 parent 1f7dd27 commit e493b10
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 41 deletions.
5 changes: 5 additions & 0 deletions packages/ember-data/lib/system/associations/ext.js
Expand Up @@ -39,6 +39,11 @@ DS.Model.reopen({
Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange'); Ember.addBeforeObserver(proto, key, null, 'belongsToWillChange');
} }


if (meta.isAttribute) {
Ember.addObserver(proto, key, null, 'attributeDidChange');
Ember.addBeforeObserver(proto, key, null, 'attributeWillChange');
}

meta.parentType = proto.constructor; meta.parentType = proto.constructor;
} }
} }
Expand Down
42 changes: 37 additions & 5 deletions packages/ember-data/lib/system/model/attributes.js
Expand Up @@ -19,12 +19,47 @@ DS.Model.reopenClass({
}) })
}); });


var AttributeChange = DS.AttributeChange = function(options) {
this.reference = options.reference;
this.store = options.store;
this.name = options.name;
this.oldValue = options.oldValue;
};

AttributeChange.createChange = function(options) {
return new AttributeChange(options);
};

AttributeChange.prototype = {
sync: function() {
this.store.recordAttributeDidChange(this.reference, this.name, this.value, this.oldValue);

// TODO: Use this object in the commit process
this.destroy();
},

destroy: function() {
delete this.store.recordForReference(this.reference)._changesToSync[this.name];
}
};

DS.Model.reopen({ DS.Model.reopen({
eachAttribute: function(callback, binding) { eachAttribute: function(callback, binding) {
get(this.constructor, 'attributes').forEach(function(name, meta) { get(this.constructor, 'attributes').forEach(function(name, meta) {
callback.call(binding, name, meta); callback.call(binding, name, meta);
}, binding); }, binding);
} },

attributeWillChange: Ember.beforeObserver(function(record, key) {
var reference = get(record, 'reference'),
store = get(record, 'store');

record.send('willSetProperty', { reference: reference, store: store, name: key });
}),

attributeDidChange: Ember.observer(function(record, key) {
record.send('didSetProperty', { name: key });
})
}); });


function getAttr(record, options, key) { function getAttr(record, options, key) {
Expand All @@ -51,10 +86,7 @@ DS.attr = function(type, options) {
var data; var data;


if (arguments.length > 1) { if (arguments.length > 1) {
// TODO: If there is a cached oldValue, use it [tomhuda] Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.constructor.toString(), key !== 'id');
oldValue = get(this, 'data.attributes')[key];
Ember.assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.toString(), key !== 'id');
this.setProperty(key, value, oldValue);
} else { } else {
value = getAttr(this, options, key); value = getAttr(this, options, key);
} }
Expand Down
6 changes: 4 additions & 2 deletions packages/ember-data/lib/system/model/model.js
Expand Up @@ -54,6 +54,8 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
}).property(), }).property(),


materializeData: function() { materializeData: function() {
this.send('materializingData');

get(this, 'store').materializeData(this); get(this, 'store').materializeData(this);


this.suspendAssociationObservers(function() { this.suspendAssociationObservers(function() {
Expand Down Expand Up @@ -160,6 +162,8 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
this.hasManyDidChange(association.key); this.hasManyDidChange(association.key);
} }
}, this); }, this);

this.send('finishedMaterializing');
}, 'data'), }, 'data'),


hasManyDidChange: function(key) { hasManyDidChange: function(key) {
Expand Down Expand Up @@ -189,8 +193,6 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
hasMany: {}, hasMany: {},
id: null id: null
}; };

this.notifyPropertyChange('data');
}, },


materializeId: function(id) { materializeId: function(id) {
Expand Down
85 changes: 57 additions & 28 deletions packages/ember-data/lib/system/model/states.js
Expand Up @@ -180,14 +180,17 @@ var didChangeData = function(manager) {
record.materializeData(); record.materializeData();
}; };


var setProperty = function(manager, context) { var willSetProperty = function(manager, context) {
var record = get(manager, 'record'), context.oldValue = get(get(manager, 'record'), context.name);
store = get(record, 'store'),
key = context.key, var change = DS.AttributeChange.createChange(context);
oldValue = context.oldValue, get(manager, 'record')._changesToSync[context.attributeName] = change;
newValue = context.value; };


store.recordAttributeDidChange(record, key, newValue, oldValue); var didSetProperty = function(manager, context) {
var change = get(manager, 'record')._changesToSync[context.attributeName];
change.value = get(get(manager, 'record'), context.name);
change.sync();
}; };


// Whenever a property is set, recompute all dependent filters // Whenever a property is set, recompute all dependent filters
Expand Down Expand Up @@ -278,7 +281,8 @@ var DirtyState = DS.State.extend({
}, },


// EVENTS // EVENTS
setProperty: setProperty, willSetProperty: willSetProperty,
didSetProperty: didSetProperty,


becomeDirty: Ember.K, becomeDirty: Ember.K,


Expand All @@ -294,7 +298,7 @@ var DirtyState = DS.State.extend({
t.recordBecameClean(dirtyType, record); t.recordBecameClean(dirtyType, record);
}); });


manager.transitionTo('loaded.saved'); manager.transitionTo('loaded.materializing');
}, },


becameInvalid: function(manager) { becameInvalid: function(manager) {
Expand Down Expand Up @@ -381,18 +385,20 @@ var DirtyState = DS.State.extend({
get(manager, 'record').clearRelationships(); get(manager, 'record').clearRelationships();
}, },


setProperty: function(manager, context) { willSetProperty: willSetProperty,

didSetProperty: function(manager, context) {
var record = get(manager, 'record'), var record = get(manager, 'record'),
errors = get(record, 'errors'), errors = get(record, 'errors'),
key = context.key; key = context.name;


set(errors, key, null); set(errors, key, null);


if (!hasDefinedProperties(errors)) { if (!hasDefinedProperties(errors)) {
manager.send('becameValid'); manager.send('becameValid');
} }


setProperty(manager, context); didSetProperty(manager, context);
}, },


becomeDirty: Ember.K, becomeDirty: Ember.K,
Expand Down Expand Up @@ -497,19 +503,11 @@ var states = {
// Usually, this process is asynchronous, using an // Usually, this process is asynchronous, using an
// XHR to retrieve the data. // XHR to retrieve the data.
loading: DS.State.create({ loading: DS.State.create({
// TRANSITIONS
exit: function(manager) {
var record = get(manager, 'record');

Ember.run.once(function() {
record.trigger('didLoad');
});
},

// EVENTS // EVENTS
loadedData: function(manager) { loadedData: didChangeData,
didChangeData(manager);
manager.transitionTo('loaded'); materializingData: function(manager) {
manager.transitionTo('loaded.materializing.firstTime');
} }
}), }),


Expand All @@ -524,15 +522,46 @@ var states = {


// SUBSTATES // SUBSTATES


materializing: DS.State.create({
// FLAGS
isLoaded: false,

// EVENTS
willSetProperty: Ember.K,
didSetProperty: Ember.K,

didChangeData: didChangeData,

finishedMaterializing: function(manager) {
manager.transitionTo('loaded.saved');
},

// SUBSTATES
firstTime: DS.State.create({
exit: function(manager) {
var record = get(manager, 'record');

Ember.run.once(function() {
record.trigger('didLoad');
});
}
})
}),

// If there are no local changes to a record, it remains // If there are no local changes to a record, it remains
// in the `saved` state. // in the `saved` state.
saved: DS.State.create({ saved: DS.State.create({

// EVENTS // EVENTS
setProperty: setProperty, willSetProperty: willSetProperty,
didSetProperty: didSetProperty,

didChangeData: didChangeData, didChangeData: didChangeData,
loadedData: didChangeData, loadedData: didChangeData,


materializingData: function(manager) {
manager.transitionTo('loaded.materializing');
},

becomeDirty: function(manager) { becomeDirty: function(manager) {
manager.transitionTo('updated'); manager.transitionTo('updated');
}, },
Expand Down Expand Up @@ -647,7 +676,7 @@ var states = {
t.recordBecameClean('deleted', record); t.recordBecameClean('deleted', record);
}); });


manager.transitionTo('loaded.saved'); manager.transitionTo('loaded.materializing');
} }
}), }),


Expand Down
9 changes: 7 additions & 2 deletions packages/ember-data/lib/system/store.js
Expand Up @@ -353,6 +353,10 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
// the `loaded` state. // the `loaded` state.
record.loadedData(); record.loadedData();


// Make sure the data is set up so the record doesn't
// try to materialize its nonexistent data.
record.setupData();

// Store the record we just created in the record cache for // Store the record we just created in the record cache for
// this clientId. // this clientId.
this.recordCache[clientId] = record; this.recordCache[clientId] = record;
Expand Down Expand Up @@ -1759,8 +1763,9 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
// . RECORD CHANGE NOTIFICATION . // . RECORD CHANGE NOTIFICATION .
// .............................. // ..............................


recordAttributeDidChange: function(record, attributeName, newValue, oldValue) { recordAttributeDidChange: function(reference, attributeName, newValue, oldValue) {
var dirtySet = new Ember.OrderedSet(), var record = this.recordForReference(reference),
dirtySet = new Ember.OrderedSet(),
adapter = this.adapterForType(record.constructor); adapter = this.adapterForType(record.constructor);


if (adapter.dirtyRecordsForAttributeChange) { if (adapter.dirtyRecordsForAttributeChange) {
Expand Down
Expand Up @@ -328,9 +328,9 @@ test("An error is raised when attempting to set a property while a record is bei
try { try {
tom.set('name', "Tommy Bahama"); tom.set('name', "Tommy Bahama");
} catch(e) { } catch(e) {
var expectedMessage = "Attempted to handle event `setProperty` on <Person:" + Ember.guidFor(tom) + ":1> "; var expectedMessage = "Attempted to handle event `willSetProperty` on <Person:" + Ember.guidFor(tom) + ":1> ";
expectedMessage += "while in state rootState.loaded.updated.inFlight. Called with "; expectedMessage += "while in state rootState.loaded.updated.inFlight. Called with ";
expectedMessage += "{key: name , value: Tommy Bahama , oldValue: null}"; expectedMessage += "{reference: [object Object] , store: <DS.Store:" + Ember.guidFor(store) + "> , name: name}";
equal(e.message, expectedMessage); equal(e.message, expectedMessage);
} }
finishSaving(); finishSaving();
Expand Down
4 changes: 2 additions & 2 deletions packages/ember-data/tests/unit/fixture_adapter_test.js
Expand Up @@ -46,7 +46,7 @@ test("should load data for a type asynchronously when it is requested", function


equal(get(ebryn, 'isLoaded'), false, "record from fixtures is returned in the loading state"); equal(get(ebryn, 'isLoaded'), false, "record from fixtures is returned in the loading state");


ebryn.addObserver('isLoaded', function() { ebryn.on('didLoad', function() {
clearTimeout(timer); clearTimeout(timer);
start(); start();


Expand All @@ -56,7 +56,7 @@ test("should load data for a type asynchronously when it is requested", function
stop(); stop();


var wycats = store.find(Person, 'wycats'); var wycats = store.find(Person, 'wycats');
wycats.addObserver('isLoaded', function() { wycats.on('didLoad', function() {
clearTimeout(timer); clearTimeout(timer);
start(); start();


Expand Down

0 comments on commit e493b10

Please sign in to comment.