Skip to content
Browse files

Reorganize materialization process

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...
1 parent 1f7dd27 commit e493b10011a05da6fce748eb39580e48f562627b @wycats wycats committed Dec 20, 2012
View
5 packages/ember-data/lib/system/associations/ext.js
@@ -39,6 +39,11 @@ DS.Model.reopen({
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;
}
}
View
42 packages/ember-data/lib/system/model/attributes.js
@@ -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({
eachAttribute: function(callback, binding) {
get(this.constructor, 'attributes').forEach(function(name, meta) {
callback.call(binding, name, meta);
}, 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) {
@@ -51,10 +86,7 @@ DS.attr = function(type, options) {
var data;
if (arguments.length > 1) {
- // TODO: If there is a cached oldValue, use it [tomhuda]
- 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);
+ 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');
} else {
value = getAttr(this, options, key);
}
View
6 packages/ember-data/lib/system/model/model.js
@@ -54,6 +54,8 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
}).property(),
materializeData: function() {
+ this.send('materializingData');
+
get(this, 'store').materializeData(this);
this.suspendAssociationObservers(function() {
@@ -160,6 +162,8 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
this.hasManyDidChange(association.key);
}
}, this);
+
+ this.send('finishedMaterializing');
}, 'data'),
hasManyDidChange: function(key) {
@@ -189,8 +193,6 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
hasMany: {},
id: null
};
-
- this.notifyPropertyChange('data');
},
materializeId: function(id) {
View
85 packages/ember-data/lib/system/model/states.js
@@ -180,14 +180,17 @@ var didChangeData = function(manager) {
record.materializeData();
};
-var setProperty = function(manager, context) {
- var record = get(manager, 'record'),
- store = get(record, 'store'),
- key = context.key,
- oldValue = context.oldValue,
- newValue = context.value;
-
- store.recordAttributeDidChange(record, key, newValue, oldValue);
+var willSetProperty = function(manager, context) {
+ context.oldValue = get(get(manager, 'record'), context.name);
+
+ var change = DS.AttributeChange.createChange(context);
+ get(manager, 'record')._changesToSync[context.attributeName] = change;
+};
+
+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
@@ -278,7 +281,8 @@ var DirtyState = DS.State.extend({
},
// EVENTS
- setProperty: setProperty,
+ willSetProperty: willSetProperty,
+ didSetProperty: didSetProperty,
becomeDirty: Ember.K,
@@ -294,7 +298,7 @@ var DirtyState = DS.State.extend({
t.recordBecameClean(dirtyType, record);
});
- manager.transitionTo('loaded.saved');
+ manager.transitionTo('loaded.materializing');
},
becameInvalid: function(manager) {
@@ -381,18 +385,20 @@ var DirtyState = DS.State.extend({
get(manager, 'record').clearRelationships();
},
- setProperty: function(manager, context) {
+ willSetProperty: willSetProperty,
+
+ didSetProperty: function(manager, context) {
var record = get(manager, 'record'),
errors = get(record, 'errors'),
- key = context.key;
+ key = context.name;
set(errors, key, null);
if (!hasDefinedProperties(errors)) {
manager.send('becameValid');
}
- setProperty(manager, context);
+ didSetProperty(manager, context);
},
becomeDirty: Ember.K,
@@ -497,19 +503,11 @@ var states = {
// Usually, this process is asynchronous, using an
// XHR to retrieve the data.
loading: DS.State.create({
- // TRANSITIONS
- exit: function(manager) {
- var record = get(manager, 'record');
-
- Ember.run.once(function() {
- record.trigger('didLoad');
- });
- },
-
// EVENTS
- loadedData: function(manager) {
- didChangeData(manager);
- manager.transitionTo('loaded');
+ loadedData: didChangeData,
+
+ materializingData: function(manager) {
+ manager.transitionTo('loaded.materializing.firstTime');
}
}),
@@ -524,15 +522,46 @@ var states = {
// 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
// in the `saved` state.
saved: DS.State.create({
-
// EVENTS
- setProperty: setProperty,
+ willSetProperty: willSetProperty,
+ didSetProperty: didSetProperty,
+
didChangeData: didChangeData,
loadedData: didChangeData,
+ materializingData: function(manager) {
+ manager.transitionTo('loaded.materializing');
+ },
+
becomeDirty: function(manager) {
manager.transitionTo('updated');
},
@@ -647,7 +676,7 @@ var states = {
t.recordBecameClean('deleted', record);
});
- manager.transitionTo('loaded.saved');
+ manager.transitionTo('loaded.materializing');
}
}),
View
9 packages/ember-data/lib/system/store.js
@@ -353,6 +353,10 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
// the `loaded` state.
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
// this clientId.
this.recordCache[clientId] = record;
@@ -1759,8 +1763,9 @@ DS.Store = Ember.Object.extend(DS._Mappable, {
// . RECORD CHANGE NOTIFICATION .
// ..............................
- recordAttributeDidChange: function(record, attributeName, newValue, oldValue) {
- var dirtySet = new Ember.OrderedSet(),
+ recordAttributeDidChange: function(reference, attributeName, newValue, oldValue) {
+ var record = this.recordForReference(reference),
+ dirtySet = new Ember.OrderedSet(),
adapter = this.adapterForType(record.constructor);
if (adapter.dirtyRecordsForAttributeChange) {
View
4 packages/ember-data/tests/integration/record_persistence_test.js
@@ -328,9 +328,9 @@ test("An error is raised when attempting to set a property while a record is bei
try {
tom.set('name', "Tommy Bahama");
} 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 += "{key: name , value: Tommy Bahama , oldValue: null}";
+ expectedMessage += "{reference: [object Object] , store: <DS.Store:" + Ember.guidFor(store) + "> , name: name}";
equal(e.message, expectedMessage);
}
finishSaving();
View
4 packages/ember-data/tests/unit/fixture_adapter_test.js
@@ -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");
- ebryn.addObserver('isLoaded', function() {
+ ebryn.on('didLoad', function() {
clearTimeout(timer);
start();
@@ -56,7 +56,7 @@ test("should load data for a type asynchronously when it is requested", function
stop();
var wycats = store.find(Person, 'wycats');
- wycats.addObserver('isLoaded', function() {
+ wycats.on('didLoad', function() {
clearTimeout(timer);
start();

0 comments on commit e493b10

Please sign in to comment.
Something went wrong with that request. Please try again.