Skip to content

Commit

Permalink
Merge pull request locks#36 from bjarkehs/master
Browse files Browse the repository at this point in the history
Handling relationships for finding several records at a time. Currently only handles when finding a single record
  • Loading branch information
kurko committed Feb 13, 2014
2 parents 84a24a7 + 1533d20 commit 64db967
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 4 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ Usage
Include `localstorage_adapter.js` in your app and then like all adapters:

```js
App.LSAdapter = DS.LSAdapter.extend({
namespace: 'app_namespace'
App.ApplicationSerializer = DS.LSSerializer.extend();
App.ApplicationAdapter = DS.LSAdapter.extend({
namespace: 'yournamespace'
});

App.ApplicationAdapter = DS.LSAdapter;
```

### Local Storage Namespace
Expand Down
115 changes: 115 additions & 0 deletions localstorage_adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,41 @@ DS.LSSerializer = DS.JSONSerializer.extend({
}
},

/**
* Extracts whatever was returned from the adapter.
*
* If the adapter returns relationships in an embedded way, such as follows:
*
* ```js
* {
* "id": 1,
* "title": "Rails Rambo",
*
* "_embedded": {
* "comment": [{
* "id": 1,
* "comment_title": "FIRST"
* }, {
* "id": 2,
* "comment_title": "Rails is unagi"
* }]
* }
* }
*
* this method will create separated JSON for each resource and then push
* them individually to the Store.
*
* In the end, only the main resource will remain, containing the ids of its
* relationships. Given the relations are already in the Store, we will
* return a JSON with the main resource alone. The Store will sort out the
* associations by itself.
*
* @method extractSingle
* @private
* @param {DS.Store} store the returned store
* @param {DS.Model} type the type/model
* @param {Object} payload returned JSON
*/
extractSingle: function(store, type, payload) {
if (payload && payload._embedded) {
for (var relation in payload._embedded) {
Expand All @@ -35,6 +70,24 @@ DS.LSSerializer = DS.JSONSerializer.extend({
}

return this.normalize(type, payload);
},

/**
* This is exactly the same as extractSingle, but used in an array.
*
* @method extractSingle
* @private
* @param {DS.Store} store the returned store
* @param {DS.Model} type the type/model
* @param {Array} payload returned JSONs
*/
extractArray: function(store, type, payload) {
var serializer = this;

return payload.map(function(record) {
var extracted = serializer.extractSingle(store, type, record);
return serializer.normalize(type, record);
});
}

});
Expand Down Expand Up @@ -96,6 +149,12 @@ DS.LSAdapter = DS.Adapter.extend(Ember.Evented, {
}

resolve(results);
}).then(function(records) {
if (records.get('length')) {
return adapter.loadRelationshipsForMany(type, records);
} else {
return records;
}
});
},

Expand All @@ -117,6 +176,10 @@ DS.LSAdapter = DS.Adapter.extend(Ember.Evented, {
var namespace = this._namespaceForType(type),
results = this.query(namespace.records, query);

if (results.get('length')) {
results = this.loadRelationshipsForMany(type, results);
}

return Ember.RSVP.resolve(results);
},

Expand Down Expand Up @@ -392,6 +455,58 @@ DS.LSAdapter = DS.Adapter.extend(Ember.Evented, {
return Object.prototype.toString.call(value) === '[object Array]';
},

/**
* Same as `loadRelationships`, but for an array of records.
*
* @method loadRelationshipsForMany
* @private
* @param {DS.Model} type
* @param {Object} recordsArray
*/
loadRelationshipsForMany: function(type, recordsArray) {
var adapter = this;

return new Ember.RSVP.Promise(function(resolve, reject) {
var recordsWithRelationships = [],
recordsToBeLoaded = [],
promises = [];

/**
* Some times Ember puts some stuff in arrays. We want to clean it so
* we know exactly what to iterate over.
*/
for (var i in recordsArray) {
if (recordsArray.hasOwnProperty(i)) {
recordsToBeLoaded.push(recordsArray[i]);
}
}

var loadNextRecord = function(record) {
/**
* Removes the first item from recordsToBeLoaded
*/
recordsToBeLoaded = recordsToBeLoaded.slice(1);

var promise = adapter.loadRelationships(type, record);

promise.then(function(recordWithRelationships) {
recordsWithRelationships.push(recordWithRelationships);

if (recordsToBeLoaded[0]) {
loadNextRecord(recordsToBeLoaded[0]);
} else {
resolve(recordsWithRelationships);
}
});
}

/**
* We start by the first record
*/
loadNextRecord(recordsToBeLoaded[0]);
});
},


/**
*
Expand Down
18 changes: 18 additions & 0 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ var FIXTURES = {
'i1': { id: 'i1', name: 'one', list: 'l1' },
'i2': { id: 'i2', name: 'two', list: 'l1' }
}
},

'App.Order': {
records: {
'o1': { id: 'o1', name: 'one', b: true, hours: ['h1', 'h2'] },
'o2': { id: 'o2', name: 'two', b: false, hours: [] },
'o3': { id: 'o3', name: 'three', b: true, hours: ['h3', 'h4'] },
'o4': { id: 'o4', name: 'four', b: true, hours: [] }
}
},

'App.Hour': {
records: {
'h1': { id: 'h1', name: 'one', amount: 4, order: 'o1' },
'h2': { id: 'h2', name: 'two', amount: 3, order: 'o1' },
'h3': { id: 'h3', name: 'three', amount: 2, order: 'o3' },
'h4': { id: 'h4', name: 'four', amount: 1, order: 'o3' }
}
}
};

Expand Down
51 changes: 51 additions & 0 deletions test/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,27 @@ module('DS.LSAdapter', {

App.Item.toString = stringify('App.Item');

App.Order = DS.Model.extend({
name: DS.attr('string'),
b: DS.attr('boolean'),
hours: DS.hasMany('hour')
});

App.Order.toString = stringify('App.Order');

App.Hour = DS.Model.extend({
name: DS.attr('string'),
amount: DS.attr('number'),
order: DS.belongsTo('order')
});

App.Hour.toString = stringify('App.Hour');

env = setupStore({
list: App.List,
item: App.Item,
order: App.Order,
hour: App.Hour,
adapter: DS.LSAdapter
});
store = env.store;
Expand Down Expand Up @@ -106,6 +124,39 @@ test('findAll', function() {
});
});

test('findQueryMany', function() {
expect(11);
stop();
store.find('order', { b: true }).then(function(records) {
var firstRecord = records.objectAt(0),
secondRecord = records.objectAt(1),
thirdRecord = records.objectAt(2);

equal(get(records, 'length'), 3, "3 orders were found");
equal(get(firstRecord, 'name'), "one", "First order's name is one");
equal(get(secondRecord, 'name'), "three", "Second order's name is three");
equal(get(thirdRecord, 'name'), "four", "Third order's name is four");
var firstHours = firstRecord.get('hours'),
secondHours = secondRecord.get('hours'),
thirdHours = thirdRecord.get('hours');

equal(get(firstHours, 'length'), 2, "Order one has two hours");
equal(get(secondHours, 'length'), 2, "Order three has two hours");
equal(get(thirdHours, 'length'), 0, "Order four has no hours");

var hourOne = firstHours.objectAt(0),
hourTwo = firstHours.objectAt(1),
hourThree = secondHours.objectAt(0),
hourFour = secondHours.objectAt(1);
equal(get(hourOne, 'amount'), 4, "Hour one has amount of 4");
equal(get(hourTwo, 'amount'), 3, "Hour two has amount of 3");
equal(get(hourThree, 'amount'), 2, "Hour three has amount of 2");
equal(get(hourFour, 'amount'), 1, "Hour four has amount of 1");

start();
});
});

test('createRecord', function() {
expect(5);
stop();
Expand Down

0 comments on commit 64db967

Please sign in to comment.