Skip to content

Commit

Permalink
Make Ember Data objects promises
Browse files Browse the repository at this point in the history
  • Loading branch information
tomhuda committed Dec 30, 2012
1 parent 7da1b29 commit ca9968a
Show file tree
Hide file tree
Showing 10 changed files with 10,310 additions and 9,844 deletions.
14 changes: 14 additions & 0 deletions packages/ember-data/lib/system/mixins/load_promise.js
@@ -0,0 +1,14 @@
var DeferredMixin = Ember.DeferredMixin, // ember-runtime/mixins/deferred
Evented = Ember.Evented, // ember-runtime/mixins/evented
run = Ember.run; // ember-metal/run-loop

var LoadPromise = Ember.Mixin.create(Evented, DeferredMixin, {
init: function() {
this._super.apply(this, arguments);
this.one('didLoad', function() {
run(this, 'resolve', this);

This comment has been minimized.

Copy link
@krisselden

krisselden Jun 14, 2013

Contributor

Ember.run should be in the adapter in the ajax event handler, not here, this causes a lot of nested run loops. cc @stefanpenner

This comment has been minimized.

Copy link
@stefanpenner

stefanpenner Jun 14, 2013

Member

Good catch. The current rest adapter does catch this case.

I think we need to raise an exception (during testing) when nested run loops occur.

I will make this fix shortly.

});
}
});

DS.LoadPromise = LoadPromise;
7 changes: 6 additions & 1 deletion packages/ember-data/lib/system/model/model.js
@@ -1,12 +1,15 @@
require("ember-data/system/model/states"); require("ember-data/system/model/states");
require("ember-data/system/mixins/load_promise");

var LoadPromise = DS.LoadPromise; // system/mixins/load_promise


var get = Ember.get, set = Ember.set, none = Ember.isNone, map = Ember.EnumerableUtils.map; var get = Ember.get, set = Ember.set, none = Ember.isNone, map = Ember.EnumerableUtils.map;


var retrieveFromCurrentState = Ember.computed(function(key) { var retrieveFromCurrentState = Ember.computed(function(key) {
return get(get(this, 'stateManager.currentState'), key); return get(get(this, 'stateManager.currentState'), key);
}).property('stateManager.currentState'); }).property('stateManager.currentState');


DS.Model = Ember.Object.extend(Ember.Evented, { DS.Model = Ember.Object.extend(Ember.Evented, LoadPromise, {
isLoaded: retrieveFromCurrentState, isLoaded: retrieveFromCurrentState,
isDirty: retrieveFromCurrentState, isDirty: retrieveFromCurrentState,
isSaving: retrieveFromCurrentState, isSaving: retrieveFromCurrentState,
Expand Down Expand Up @@ -66,6 +69,8 @@ DS.Model = Ember.Object.extend(Ember.Evented, {
_data: null, _data: null,


init: function() { init: function() {
this._super();

var stateManager = DS.StateManager.create({ record: this }); var stateManager = DS.StateManager.create({ record: this });
set(this, 'stateManager', stateManager); set(this, 'stateManager', stateManager);


Expand Down
6 changes: 5 additions & 1 deletion packages/ember-data/lib/system/record_arrays/record_array.js
@@ -1,5 +1,9 @@
require("ember-data/system/mixins/load_promise");

var get = Ember.get, set = Ember.set; var get = Ember.get, set = Ember.set;


var LoadPromise = DS.LoadPromise; // system/mixins/load_promise

/** /**
A record array is an array that contains records of a certain type. The record A record array is an array that contains records of a certain type. The record
array materializes records as needed when they are retrieved for the first array materializes records as needed when they are retrieved for the first
Expand All @@ -8,7 +12,7 @@ var get = Ember.get, set = Ember.set;
in response to queries. in response to queries.
*/ */


DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, { DS.RecordArray = Ember.ArrayProxy.extend(Ember.Evented, LoadPromise, {
/** /**
The model type contained by this record array. The model type contained by this record array.
Expand Down
6 changes: 5 additions & 1 deletion packages/ember-data/tests/integration/has_many_test.js
Expand Up @@ -35,7 +35,7 @@ module("Has-Many Relationships", {
}); });


test("A hasMany relationship has an isLoaded flag that indicates whether the ManyArray has finished loaded", function() { test("A hasMany relationship has an isLoaded flag that indicates whether the ManyArray has finished loaded", function() {
expect(8); expect(9);


var array, hasLoaded; var array, hasLoaded;


Expand All @@ -58,6 +58,10 @@ test("A hasMany relationship has an isLoaded flag that indicates whether the Man
ok(true, "didLoad was triggered"); ok(true, "didLoad was triggered");
}); });


array.then(function(resolvedValue) {
equal(resolvedValue, array, "the promise was resolved with itself");
});

equal(get(array, 'isLoaded'), false, "isLoaded should not be true when first created"); equal(get(array, 'isLoaded'), false, "isLoaded should not be true when first created");
}); });


Expand Down
10 changes: 7 additions & 3 deletions packages/ember-data/tests/integration/queries_test.js
Expand Up @@ -21,7 +21,7 @@ module("Queries", {
}); });


test("When a query is made, the adapter should receive a record array it can populate with the results of the query.", function() { test("When a query is made, the adapter should receive a record array it can populate with the results of the query.", function() {
expect(7); expect(8);


adapter.findQuery = function(store, type, query, recordArray) { adapter.findQuery = function(store, type, query, recordArray) {
equal(type, Person, "the find method is called with the correct type"); equal(type, Person, "the find method is called with the correct type");
Expand All @@ -40,12 +40,16 @@ test("When a query is made, the adapter should receive a record array it can pop
equal(get(queryResults, 'isLoaded'), false, "the record array's `isLoaded` property is false"); equal(get(queryResults, 'isLoaded'), false, "the record array's `isLoaded` property is false");


queryResults.one('didLoad', function() { queryResults.one('didLoad', function() {
start();

equal(get(queryResults, 'length'), 2, "the record array has a length of 2 after the results are loaded"); equal(get(queryResults, 'length'), 2, "the record array has a length of 2 after the results are loaded");
equal(get(queryResults, 'isLoaded'), true, "the record array's `isLoaded` property should be true"); equal(get(queryResults, 'isLoaded'), true, "the record array's `isLoaded` property should be true");


equal(queryResults.objectAt(0).get('name'), "Peter Wagenet", "the first record is 'Peter Wagenet'"); equal(queryResults.objectAt(0).get('name'), "Peter Wagenet", "the first record is 'Peter Wagenet'");
equal(queryResults.objectAt(1).get('name'), "Brohuda Katz", "the second record is 'Brohuda Katz'"); equal(queryResults.objectAt(1).get('name'), "Brohuda Katz", "the second record is 'Brohuda Katz'");
}); });

queryResults.then(function(resolvedValue) {
start();

equal(resolvedValue, queryResults, "The promise was resolved with the query results");
});
}); });
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.on('didLoad', function() { ebryn.then(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.on('didLoad', function() { wycats.then(function() {
clearTimeout(timer); clearTimeout(timer);
start(); start();


Expand Down
2 changes: 1 addition & 1 deletion packages/ember-data/tests/unit/rest_adapter_test.js
Expand Up @@ -892,7 +892,7 @@ test("sideloaded data is loaded prior to primary data (to ensure relationship co
expect(1); expect(1);


group = store.find(Group, 1); group = store.find(Group, 1);
group.on("didLoad", function() { group.then(function(group) {
equal(group.get('people.firstObject').get('name'), "Tom Dale", "sideloaded data are already loaded"); equal(group.get('people.firstObject').get('name'), "Tom Dale", "sideloaded data are already loaded");
}); });


Expand Down
37 changes: 29 additions & 8 deletions packages/ember-data/tests/unit/store_test.js
Expand Up @@ -266,7 +266,7 @@ test("DS.Store loads individual records without explicit IDs with a custom prima
*/ */


test("DS.Store passes only needed guids to findMany", function() { test("DS.Store passes only needed guids to findMany", function() {
expect(11); expect(13);


var adapter = TestAdapter.create({ var adapter = TestAdapter.create({
findMany: function(store, type, ids) { findMany: function(store, type, ids) {
Expand All @@ -286,6 +286,11 @@ test("DS.Store passes only needed guids to findMany", function() {
equal(get(objects, 'length'), 6, "the RecordArray returned from findMany has all the objects"); equal(get(objects, 'length'), 6, "the RecordArray returned from findMany has all the objects");
equal(get(objects, 'isLoaded'), false, "the RecordArrays' isLoaded flag is false"); equal(get(objects, 'isLoaded'), false, "the RecordArrays' isLoaded flag is false");


objects.then(function(resolvedObjects) {
strictEqual(resolvedObjects, objects, "The promise is resolved with the RecordArray");
equal(get(objects, 'isLoaded'), true, "The objects are loaded");
});

var i, object, hash; var i, object, hash;
for (i=0; i<3; i++) { for (i=0; i<3; i++) {
object = objects.objectAt(i); object = objects.objectAt(i);
Expand All @@ -306,7 +311,7 @@ test("DS.Store passes only needed guids to findMany", function() {
}); });


test("a findManys' isLoaded is true when all objects are loaded", function() { test("a findManys' isLoaded is true when all objects are loaded", function() {
expect(2); expect(4);


var adapter = TestAdapter.create({ var adapter = TestAdapter.create({
findMany: function(store, type, ids) { findMany: function(store, type, ids) {
Expand All @@ -323,6 +328,11 @@ test("a findManys' isLoaded is true when all objects are loaded", function() {


var objects = currentStore.findMany(currentType, [1,2,3]); var objects = currentStore.findMany(currentType, [1,2,3]);


objects.then(function(resolvedObjects) {
strictEqual(resolvedObjects, objects, "The resolved RecordArray is correct");
equal(get(objects, 'isLoaded'), true, "The RecordArray is loaded by the time the promise is resolved");
});

equal(get(objects, 'length'), 3, "the RecordArray returned from findMany has all the objects"); equal(get(objects, 'length'), 3, "the RecordArray returned from findMany has all the objects");
equal(get(objects, 'isLoaded'), true, "the RecordArrays' isLoaded flag is true"); equal(get(objects, 'isLoaded'), true, "the RecordArrays' isLoaded flag is true");
}); });
Expand Down Expand Up @@ -384,6 +394,11 @@ test("a new record of a particular type is created via store.createRecord(type)"


var person = store.createRecord(Person); var person = store.createRecord(Person);


person.then(function(resolvedPerson) {
strictEqual(resolvedPerson, person, "The promise is resolved with the record");
equal(get(person, 'isLoaded'), true, "The record is loaded");
});

equal(get(person, 'isLoaded'), true, "A newly created record is loaded"); equal(get(person, 'isLoaded'), true, "A newly created record is loaded");
equal(get(person, 'isNew'), true, "A newly created record is new"); equal(get(person, 'isNew'), true, "A newly created record is new");
equal(get(person, 'isDirty'), true, "A newly created record is dirty"); equal(get(person, 'isDirty'), true, "A newly created record is dirty");
Expand All @@ -401,6 +416,11 @@ test("an initial data hash can be provided via store.createRecord(type, hash)",


var person = store.createRecord(Person, { name: "Brohuda Katz" }); var person = store.createRecord(Person, { name: "Brohuda Katz" });


person.then(function(resolvedPerson) {
strictEqual(resolvedPerson, person, "The promise is resolved with the record");
equal(get(person, 'isLoaded'), true, "The record is loaded");
});

equal(get(person, 'isLoaded'), true, "A newly created record is loaded"); equal(get(person, 'isLoaded'), true, "A newly created record is loaded");
equal(get(person, 'isNew'), true, "A newly created record is new"); equal(get(person, 'isNew'), true, "A newly created record is new");
equal(get(person, 'isDirty'), true, "A newly created record is dirty"); equal(get(person, 'isDirty'), true, "A newly created record is dirty");
Expand Down Expand Up @@ -453,12 +473,10 @@ test("records inside a collection view should have their ids updated", function(


module("DS.State - Lifecycle Callbacks"); module("DS.State - Lifecycle Callbacks");


test("a record receives a didLoad callback when it has finished loading", function() { asyncTest("a record receives a didLoad callback when it has finished loading", function() {
var callCount = 0;

var Person = DS.Model.extend({ var Person = DS.Model.extend({
didLoad: function() { didLoad: function() {
callCount++; ok("The didLoad callback was called");
} }
}); });


Expand All @@ -471,9 +489,12 @@ test("a record receives a didLoad callback when it has finished loading", functi
var store = DS.Store.create({ var store = DS.Store.create({
adapter: adapter adapter: adapter
}); });
store.find(Person, 1); var person = store.find(Person, 1);


equal(callCount, 1, "didLoad callback was called once"); person.then(function(resolvedPerson) {
equal(resolvedPerson, person, "The resolved value is correct");
start();
});
}); });


test("a record receives a didUpdate callback when it has finished updating", function() { test("a record receives a didUpdate callback when it has finished updating", function() {
Expand Down

1 comment on commit ca9968a

@lukemelia
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DS.LoadPromise includes Ember.Evented so DS.Model and DS.RecordArray don't need to include it again.

Please sign in to comment.