Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for issue #42 #44

Merged
merged 9 commits into from
Sep 22, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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