Skip to content

Commit

Permalink
Merge pull request #44 from sebweaver/issue-42
Browse files Browse the repository at this point in the history
Proposal for issue #42
  • Loading branch information
frederikbosch committed Sep 22, 2015
2 parents 422f23e + 6daa383 commit c902dff
Show file tree
Hide file tree
Showing 10 changed files with 468 additions and 730 deletions.
301 changes: 13 additions & 288 deletions addon/adapters/localforage.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,14 @@ export default DS.Adapter.extend(Ember.Evented, {
*/
findRecord: function (store, type, id, snapshot) {
return new Ember.RSVP.Promise((resolve, reject) => {
var allowRecursive = true;
this._namespaceForType(type).then((namespace) => {
/**
* In the case where there are relationships, this method is called again
* for each relation. Given the relations have references to the main
* object, we use allowRecursive to avoid going further into infinite
* recursiveness.
*
* Concept from ember-indexdb-adapter
*/
if (snapshot && typeof snapshot.allowRecursive !== 'undefined') {
allowRecursive = snapshot.allowRecursive;
}

var record = namespace.records[id];
if (!record) {
reject();
return;
}

if (allowRecursive) {
this.loadRelationships(store, type, record).then(function (finalRecord) {
resolve(finalRecord);
});
} else {
resolve(record);
}
resolve(record);
});
});
},
Expand All @@ -64,19 +45,19 @@ export default DS.Adapter.extend(Ember.Evented, {
return new Ember.RSVP.Promise((resolve, reject) => {
this._namespaceForType(type).then(function (namespace) {
var results = [];
var record;

for (var i = 0; i < ids.length; i++) {
results.push(Ember.copy(namespace.records[ids[i]]));
record = namespace.records[ids[i]];
if (!record) {
reject();
return;
}
results.push(Ember.copy(record));
}

resolve(results);
});
}).then((records) => {
if (records.get('length')) {
return this.loadRelationshipsForMany(store, type, records);
} else {
return records;
}
});
},

Expand All @@ -98,10 +79,6 @@ export default DS.Adapter.extend(Ember.Evented, {
this._namespaceForType(type).then((namespace) => {
var results = this._query(namespace.records, query);

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

resolve(results);
});
});
Expand All @@ -112,13 +89,12 @@ export default DS.Adapter.extend(Ember.Evented, {
return new Ember.RSVP.Promise((resolve, reject) => {
this._namespaceForType(type).then((namespace) => {
var result = this._query(namespace.records, query, true);

if (result) {
result = this.loadRelationships(store, type, result);
resolve(result);
} else {
if (!result) {
reject();
return;
}

resolve(result);
});
});

Expand Down Expand Up @@ -163,6 +139,7 @@ export default DS.Adapter.extend(Ember.Evented, {
for (var id in namespace.records) {
results.push(Ember.copy(namespace.records[id]));
}

resolve(results);
});
});
Expand Down Expand Up @@ -252,258 +229,6 @@ export default DS.Adapter.extend(Ember.Evented, {
modelNamespace: function (type) {
return type.url || type.modelName;
},


/**
* This takes a record, then analyzes the model relationships and replaces
* ids with the actual values.
*
* Stolen from ember-indexdb-adapter
*
* Consider the following JSON is entered:
*
* ```js
* {
* "id": 1,
* "title": "Rails Rambo",
* "comments": [1, 2]
* }
*
* This will return:
*
* ```js
* {
* "id": 1,
* "title": "Rails Rambo",
* "comments": [1, 2]
*
* "_embedded": {
* "comment": [{
* "_id": 1,
* "comment_title": "FIRST"
* }, {
* "_id": 2,
* "comment_title": "Rails is unagi"
* }]
* }
* }
*
* This way, whenever a resource returned, its relationships will be also
* returned.
*
* @method loadRelationships
* @private
* @param {DS.Store} store
* @param {DS.Model} type
* @param {Object} record
*/
loadRelationships: function (store, type, record) {
return new Ember.RSVP.Promise((resolve, reject) => {
var resultJSON = {},
modelName = type.modelName,
relationshipNames, relationships,
relationshipPromises = [];

relationshipNames = Ember.get(type, 'relationshipNames');
relationships = relationshipNames.belongsTo;
relationships = relationships.concat(relationshipNames.hasMany);

relationships.forEach((relationName) => {
var relationModel = type.typeForRelationship(relationName, store),
relationEmbeddedId = record[relationName],
relationProp = this.relationshipProperties(type, relationName),
relationType = relationProp.kind,
promise, embedPromise;

var opts = {allowRecursive: false};

/**
* embeddedIds are ids of relations that are included in the main
* payload, such as:
*
* {
* cart: {
* id: "s85fb",
* customer: "rld9u"
* }
* }
*
* In this case, cart belongsTo customer and its id is present in the
* main payload. We find each of these records and add them to _embedded.
*/
var embeddedAlways = this.isEmbeddedAlways(store, type.modelName, relationProp.key);

// For embeddedAlways-style data, we assume the data to be present already, so no further loading is needed.
if (relationEmbeddedId && !embeddedAlways) {
if (relationType === 'belongsTo' || relationType === 'hasOne') {
promise = this.findRecord(store, relationModel, relationEmbeddedId, opts);
} else if (relationType === 'hasMany') {
promise = this.findMany(store, relationModel, relationEmbeddedId, opts);
}

embedPromise = new Ember.RSVP.Promise((resolve, reject) => {
promise.then((relationRecord) => {
resolve(this.addEmbeddedPayload(record, relationName, relationRecord));
});
});

relationshipPromises.push(embedPromise);
}
});

Ember.RSVP.all(relationshipPromises).then(function () {
resolve(record);
});
});
},


/**
* Given the following payload,
*
* {
* cart: {
* id: "1",
* customer: "2"
* }
* }
*
* With `relationshipName` being `customer` and `relationshipRecord`
*
* {id: "2", name: "Rambo"}
*
* This method returns the following payload:
*
* {
* cart: {
* id: "1",
* customer: "2"
* },
* _embedded: {
* customer: {
* id: "2",
* name: "Rambo"
* }
* }
* }
*
* which is then treated by the serializer later.
*
* @method addEmbeddedPayload
* @private
* @param {Object} payload
* @param {String} relationshipName
* @param {Object} relationshipRecord
*/
addEmbeddedPayload: function (payload, relationshipName, relationshipRecord) {
var objectHasId = (relationshipRecord && relationshipRecord.id),
arrayHasIds = (relationshipRecord.length && relationshipRecord.isEvery("id")),
isValidRelationship = (objectHasId || arrayHasIds);

if (isValidRelationship) {
if (!payload._embedded) {
payload._embedded = {};
}

payload._embedded[relationshipName] = relationshipRecord;
if (relationshipRecord.length) {
payload[relationshipName] = relationshipRecord.mapBy('id');
} else {
payload[relationshipName] = relationshipRecord.id;
}
}

if (this.isArray(payload[relationshipName])) {
payload[relationshipName] = payload[relationshipName].filter(function (id) {
return id;
});
}

return payload;
},


isArray: function (value) {
return Object.prototype.toString.call(value) === '[object Array]';
},

/**
* Same as `loadRelationships`, but for an array of records.
*
* @method loadRelationshipsForMany
* @private
* @param {DS.Store} store
* @param {DS.Model} type
* @param {Object} recordsArray
*/
loadRelationshipsForMany: function (store, type, recordsArray) {
return new Ember.RSVP.Promise((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 = (record) => {
/**
* Removes the first item from recordsToBeLoaded
*/
recordsToBeLoaded = recordsToBeLoaded.slice(1);

var promise = this.loadRelationships(store, 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]);
});
},


/**
*
* @method relationshipProperties
* @private
* @param {DS.Model} type
* @param {String} relationName
*/
relationshipProperties: function (type, relationName) {
var relationships = Ember.get(type, 'relationshipsByName');
if (relationName) {
return relationships.get(relationName);
} else {
return relationships;
}
},

isEmbeddedAlways: function (store, modelName, relationKey) {
if (store === undefined || store === null) {
return false;
}

var serializer = store.serializerFor(modelName);
return typeof(serializer.hasEmbeddedAlwaysOption) === 'function' &&
serializer.hasEmbeddedAlwaysOption(relationKey);
}
});

function updateOrCreate(store, type, snapshot) {
Expand Down
Loading

0 comments on commit c902dff

Please sign in to comment.