Created
May 13, 2014 18:11
-
-
Save m19/31606a34f1ac89ed612d to your computer and use it in GitHub Desktop.
Ember Data Sails Adapter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
/*global Ember*/ | |
/*global DS*/ | |
/*global io*/ | |
/*global _*/ | |
'use strict'; | |
var RSVP = Ember.RSVP; | |
var get = Ember.get; | |
var useOldDefaultSerializer = DS.VERSION.match(/beta/) && parseInt(DS.VERSION.match(/1.0.0-beta.(\d)/)[1]) < 6; | |
var SailsAdapterMixin = Ember.Mixin.create({ | |
/* | |
Sails error objects look something like this: | |
error: "E_VALIDATION", | |
model: "User", | |
summary: "1 attribute is invalid", | |
status: 400, | |
invalidAttributes: { | |
name: [ | |
{ | |
rule: "minLength", | |
message: "Validation error: "bar" Rule "minLength(10)" failed." | |
} | |
] | |
} | |
*/ | |
formatError: function(error) { | |
return Object.keys(error.invalidAttributes).reduce(function(memo, property) { | |
memo[property] = error.invalidAttributes[property].map(function(err) { | |
return err.message; | |
}); | |
return memo; | |
}, {}); | |
} | |
}); | |
DS.SailsRESTAdapter = DS.RESTAdapter.extend(SailsAdapterMixin, { | |
ajaxError: function(jqXHR) { | |
var error = this._super(jqXHR); | |
var data = Ember.$.parseJSON(jqXHR.responseText); | |
if (data.errors) { | |
return new DS.InvalidError(this.formatError(data)); | |
} else { | |
return error; | |
} | |
} | |
}); | |
DS.SailsSocketAdapter = DS.SailsAdapter = DS.Adapter.extend(SailsAdapterMixin, { | |
defaultSerializer: '-default', | |
prefix: '', | |
camelize: true, | |
log: false, | |
useCSRF: false, | |
CSRFToken: "", | |
listeningModels: {}, | |
init: function () { | |
this._super(); | |
if(this.useCSRF) { | |
io.socket.get('/csrfToken', function response(tokenObject) { | |
this.CSRFToken = tokenObject._csrf; | |
}.bind(this)); | |
} | |
}, | |
// TODO find a better way to handle this | |
// Reason: In a Sails Model updated, created, or destoryed message | |
// the model name is always lowercase. This makes it difficult to | |
// lookup models with multipart names on the ember container. | |
// One solution could be to implement a custom resolveModel method | |
// on the resolver to work with lowercase names. But in some cases | |
// we don't want to impose a custom resolver requirement on users of | |
// the sails_adapter. | |
// This modelName hash is an ugly escape has that allows a user to | |
// define a mapping between the sails lowercase model name and a | |
// string that ember can use to recognize a model with multiple | |
// parts. | |
// For Example A `User` model would not need to be registered with | |
// this map because ember can use the string 'user' to look up the | |
// model just fine. However a `ContentType` model will need to be | |
// registered with this map because attempting to lookup a model | |
// named 'contenttype' will not return the `ContentType` model. | |
// modelNameMap: {'contenttype': 'ContentType'} | |
modelNameMap: {}, | |
find: function(store, type, id) { | |
this._listenToSocket(type.typeKey); | |
return this.socket(this.buildURL(type.typeKey, id), 'get'); | |
}, | |
createRecord: function(store, type, record) { | |
this._listenToSocket(type.typeKey); | |
var serializer = store.serializerFor(type.typeKey); | |
var data = serializer.serialize(record, { includeId: true }); | |
return this.socket(this.buildURL(type.typeKey), 'post', data); | |
}, | |
updateRecord: function(store, type, record) { | |
this._listenToSocket(type.typeKey); | |
var serializer = store.serializerFor(type.typeKey); | |
var data = serializer.serialize(record); | |
var id = get(record, 'id'); | |
return this.socket(this.buildURL(type.typeKey, id), 'put', data); | |
}, | |
deleteRecord: function(store, type, record) { | |
this._listenToSocket(type.typeKey); | |
var serializer = store.serializerFor(type.typeKey); | |
var id = get(record, 'id'); | |
var data = serializer.serialize(record); | |
return this.socket(this.buildURL(type.typeKey, id), 'delete', data); | |
}, | |
findAll: function(store, type, sinceToken) { | |
this._listenToSocket(type.typeKey); | |
return this.socket(this.buildURL(type.typeKey), 'get'); | |
}, | |
findQuery: function(store, type, query) { | |
this._listenToSocket(type.typeKey); | |
return this.socket(this.buildURL(type.typeKey), 'get', query); | |
}, | |
isErrorObject: function(data) { | |
return !!(data.error); | |
}, | |
socket: function(url, method, data ) { | |
var isErrorObject = this.isErrorObject.bind(this); | |
method = method.toLowerCase(); | |
var adapter = this; | |
adapter._log(method, url, data); | |
if(method !== 'get') | |
this.checkCSRF(data); | |
return new RSVP.Promise(function(resolve, reject) { | |
io.socket[method](url, data, function (data) { | |
if (isErrorObject(data)) { | |
adapter._log('error:', data); | |
if (data.errors) { | |
reject(new DS.InvalidError(data.error)); | |
} else { | |
reject(data); | |
} | |
} else { | |
resolve(data); | |
} | |
}); | |
}); | |
}, | |
buildURL: function(type, id) { | |
var url = []; | |
type = type || ''; | |
if (this.camelize) { | |
type = Ember.String.camelize(type); | |
} | |
if (type) { | |
url.push(type); | |
} | |
if (id) { url.push(id); } | |
url = url.join('/'); | |
var namespace = this.namespace || this.prefix; | |
url = namespace + '/' + url; | |
return url; | |
}, | |
_listenToSocket: function(model) { | |
if(model in this.listeningModels) { | |
return; | |
} | |
var self = this; | |
var store = this.container.lookup('store:main'); | |
var socketModel = model; | |
function findModelName(model) { | |
var mappedName = self.modelNameMap[model]; | |
return mappedName || model; | |
} | |
function pushMessage(message) { | |
var type = store.modelFor(socketModel); | |
var serializer = store.serializerFor(type.typeKey); | |
var data, record; | |
data = {}; | |
if(message.verb === "addedTo") { | |
var addedModel = Ember.Inflector.inflector.singularize(message.attribute); | |
var added = store.find(addedModel, message.addedId); | |
} | |
if(message.verb === "created") { | |
// message = {"verb":"created","data":{"clientSeed":"12345","game":1,"user":null,"createdAt":"2014-04-15T07:55:00.459Z","updatedAt":"2014-04-15T07:55:00.459Z","id":34},"id":34} | |
data[model] = message.data; | |
record = serializer.extractSingle(store, type, data); | |
store.push(socketModel, record); | |
} | |
if(message.verb === "updated") { | |
data[model] = Ember.merge({ id: message.id } , message.data); | |
record = serializer.extractSingle(store, type, data); | |
// store.push(socketModel, record, true); | |
store.update(socketModel, record); | |
} | |
} | |
function destroy(message) { | |
var type = store.modelFor(socketModel); | |
var record = store.getById(type, message.id); | |
if ( record && typeof record.get('dirtyType') === 'undefined' ) { | |
record.unloadRecord(); | |
} | |
} | |
var eventName = Ember.String.camelize(model).toLowerCase(); | |
io.socket.on(eventName, function (message) { | |
// Left here to help further debugging. | |
console.log("Got message on Socket : " + JSON.stringify(message)); | |
if (message.verb === 'created') { | |
pushMessage(message); | |
} | |
if (message.verb === 'addedTo') { | |
pushMessage(message); | |
} | |
if (message.verb === 'updated') { | |
pushMessage(message); | |
} | |
if (message.verb === 'destroyed') { | |
destroy(message); | |
} | |
}); | |
// We add an emtpy property instead of using an array | |
// ao we can utilize the 'in' keyword in first test in this function. | |
this.listeningModels[model] = 0; | |
}, | |
_log: function() { | |
if (this.log) { | |
console.log.apply(console, arguments); | |
} | |
}, | |
checkCSRF: function(data) { | |
if(!this.useCSRF) return data; | |
if(this.CSRFToken.length === 0) { | |
throw new Error("CSRF Token not fetched yet."); | |
} | |
data['_csrf'] = this.CSRFToken; | |
return data; | |
} | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment