Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Setup data without triggering materialization. #827

Merged
merged 2 commits into from

7 participants

@mmpestorich

A model's data is lazily initialized but in doing so currently triggers the materialization process. This is a problem if something tries to access the model's data while it is in a loading state - for example, binding to a property on an record that is not yet loaded currently has this affect:

var record = SomeModel.find(1);

Will put the record into a loading state:

record.stateManager.currentState.path === 'rootState.loading' //true

In the meantime, before data has been returned from the server:

var MyObject = Ember.Object.extend({
    init: function() {
        set(this, 'valueBinding', Ember.bind(this, 'value', 'record.' + get(this, 'property')));
    }
});
MyObject.create({ record: record, property: 'name'})

The binding will eventually attempt to get the model's data, the model's data is lazily initialized (eventually in the store's materializeData) and a materializingData event is sent to the model causing the model's state to transition:

record.stateManager.currentState.path === 'rootState.loaded.materializing.firstTime' //true

...and then a finishedMaterializing event is sent causing a transition to:

record.stateManager.currentState.path === 'rootState.loaded.saved' //true

The problem is that the model's data hasn't actually come back from the server yet and as such should not be in a loaded state in which isLoaded equals true and didLoad has been triggered resolving the load promise.

I believe that materialization should only be triggered by transitioning out of the loading state and not by simply accessing a model's data. This commit allows data to be lazily init'd without triggering materialization.

@thomasboyt

:+1: Lost an entire morning to this.

@tomdale
Owner

Sounds good; happy to merge if we can get a unit test. :)

@igorT igorT was assigned
@mmpestorich

I'll get a test up this week.

@tchak
Collaborator

@mmpestorich would you have a time soon to add a test for this patch? This is a critical regression, we need to get it in as soon as possible. If you do not have time I am happy to provide a test.

@mmpestorich

Sorry. I forgot about this. I should have the time to push up a test on this later tonight.

Mike M Pesto... added some commits
@mmpestorich

I believe that should do it

@inkredabull

So glad to see this being addressed. We had an acceptance test failing and I took me a while to get to the bottom of why.

In the browser (manually) and under Selenium, functionality was fine. The spec failed under Poltergeist; kept on getting:

Attempted to handle event loadedData on [model] while in state rootState.loaded.updated.uncommitted.

A little more context: we were blocking interactivity based on the isLoaded of the model and then, via an input, allowing the user to change a property bound to same model and save. It was perplexing to figure out:

  • we're not allowing UI controls to be enabled until the model is loaded (ok)
  • then the model is loaded (ok)
  • then the user makes changes and saves (ok)
  • then there's a race condition because the model hasn't been loaded yet (what?!?)

For the step of the spec that was failing under Poltergeist, was seeing only this for the state transitioning of the model (via console.log):
saved

In a browser (manually) and under Selenium, I saw:
saved
materializing
saved

Until the problem is fixed, my work-around is to implement a savedCount prop on the controller to count how many times 'saved' for content.stateManager.currentState.name has been seen. Two (or more) seems to be the magic number.

@tomdale tomdale merged commit af72965 into emberjs:master

1 check passed

Details default The Travis build passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 6, 2013
  1. Setup data without triggering materialization.

    Mike M Pestorich authored
  2. add unit test

    Mike M Pestorich authored
This page is out of date. Refresh to see the latest.
View
4 packages/ember-data/lib/system/model/model.js
@@ -125,7 +125,7 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
data: Ember.computed(function() {
if (!this._data) {
- this.materializeData();
+ this.setupData();
}
return this._data;
@@ -284,7 +284,7 @@ DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
Ember.run.once(this, this.updateRecordArrays);
},
- setupData: function(prematerialized) {
+ setupData: function() {
this._data = {
attributes: {},
belongsTo: {},
View
26 packages/ember-data/tests/unit/model_test.js
@@ -353,3 +353,29 @@ test("don't allow setting", function(){
record.set('isLoaded', true);
}, "raised error when trying to set an unsettable record");
});
+
+test("ensure model exits loading state, materializes data and fulfills promise only after data is available", function () {
+ expect(7);
+
+ var store = DS.Store.create({
+ adapter: DS.Adapter.create({
+ find: Ember.K
+ })
+ });
+
+ var person = store.find(Person, 1);
+
+ equal(get(person, 'stateManager.currentState.path'), 'rootState.loading', 'model is in loading state');
+ equal(get(person, 'isLoaded'), false, 'model is not loaded');
+ equal(get(person, '_deferred.promise.isFulfilled'), undefined, 'model is not fulfilled');
+
+ get(person, 'name'); //trigger data setup
+
+ equal(get(person, 'stateManager.currentState.path'), 'rootState.loading', 'model is still in loading state');
+
+ store.load(Person, { id: 1, name: "John", isDrugAddict: false });
+
+ equal(get(person, 'stateManager.currentState.path'), 'rootState.loaded.saved', 'model is in loaded state');
+ equal(get(person, 'isLoaded'), true, 'model is loaded');
+ equal(get(person, '_deferred.promise.isFulfilled'), true, 'model is fulfilled');
+});
Something went wrong with that request. Please try again.