Skip to content

Instantly share code, notes, and snippets.

@benolee
Forked from karmi/.gitignore
Created April 14, 2013 06:50
Show Gist options
  • Save benolee/5381730 to your computer and use it in GitHub Desktop.
Save benolee/5381730 to your computer and use it in GitHub Desktop.
.DS_Store
tmp/
(function(){window.DS=Ember.Namespace.create({CURRENT_API_REVISION:4})})(),function(){var e=Ember.get,t=Ember.set;DS.RecordArray=Ember.ArrayProxy.extend({type:null,content:null,store:null,objectAtContent:function(t){var n=e(this,"content"),r=n.objectAt(t),i=e(this,"store");if(r!==undefined)return i.findByClientId(e(this,"type"),r)}})}(),function(){var e=Ember.get;DS.FilteredRecordArray=DS.RecordArray.extend({filterFunction:null,replace:function(){var t=e(this,"type").toString();throw new Error("The result of a client-side filter (on "+t+") is immutable.")},updateFilter:Ember.observer(function(){var t=e(this,"store");t.updateRecordArrayFilter(this,e(this,"type"),e(this,"filterFunction"))},"filterFunction")})}(),function(){var e=Ember.get,t=Ember.set;DS.AdapterPopulatedRecordArray=DS.RecordArray.extend({query:null,isLoaded:!1,replace:function(){var t=e(this,"type").toString();throw new Error("The result of a server query (on "+t+") is immutable.")},load:function(n){var r=e(this,"store"),i=e(this,"type"),s=r.loadMany(i,n).clientIds;this.beginPropertyChanges(),t(this,"content",Ember.A(s)),t(this,"isLoaded",!0),this.endPropertyChanges()}})}(),function(){var e=Ember.get,t=Ember.set,n=Ember.guidFor,r=function(){this.hash={},this.list=[]};r.prototype={add:function(e){var t=this.hash,r=n(e);if(t.hasOwnProperty(r))return;t[r]=!0,this.list.push(e)},remove:function(e){var t=this.hash,r=n(e);if(!t.hasOwnProperty(r))return;delete t[r];var i=this.list,s=Ember.EnumerableUtils.indexOf(this,e);i.splice(s,1)},isEmpty:function(){return this.list.length===0}};var i=Ember.State.extend({recordWasAdded:function(t,n){var r=t.dirty,i;r.add(n),i=function(){e(n,"isDirty")||(n.removeObserver("isDirty",i),t.send("childWasSaved",n))},n.addObserver("isDirty",i)},recordWasRemoved:function(t,n){var r=t.dirty,i;r.add(n),i=function(){n.removeObserver("isDirty",i),e(n,"isDirty")||t.send("childWasSaved",n)},n.addObserver("isDirty",i)}}),s={loading:Ember.State.create({isLoaded:!1,isDirty:!1,loadedRecords:function(e,t){e.decrement(t)},becameLoaded:function(e){e.transitionTo("clean")}}),clean:i.create({isLoaded:!0,isDirty:!1,recordWasAdded:function(e,t){this._super(e,t),e.goToState("dirty")},update:function(e,n){var r=e.manyArray;t(r,"content",n)}}),dirty:i.create({isLoaded:!0,isDirty:!0,childWasSaved:function(e,t){var n=e.dirty;n.remove(t),n.isEmpty()&&e.send("arrayBecameSaved")},arrayBecameSaved:function(e){e.goToState("clean")}})};DS.ManyArrayStateManager=Ember.StateManager.extend({manyArray:null,initialState:"loading",states:s,counter:0,init:function(){this._super(),this.dirty=new r,this.counter=e(this,"manyArray.length")},decrement:function(e){var t=this.counter=this.counter-e;Ember.assert("Somehow the ManyArray loaded counter went below 0. This is probably an ember-data bug. Please report it at https://github.com/emberjs/data/issues",t>=0),t===0&&this.send("becameLoaded")}})}(),function(){var e=Ember.get,t=Ember.set;DS.ManyArray=DS.RecordArray.extend({init:function(){return t(this,"stateManager",DS.ManyArrayStateManager.create({manyArray:this})),this._super()},parentRecord:null,isDirty:Ember.computed(function(){return e(this,"stateManager.currentState.isDirty")}).property("stateManager.currentState").cacheable(),isLoaded:Ember.computed(function(){return e(this,"stateManager.currentState.isLoaded")}).property("stateManager.currentState").cacheable(),send:function(e,t){this.get("stateManager").send(e,t)},fetch:function(){var t=e(this,"content"),n=e(this,"store"),r=e(this,"type");n.fetchUnloadedClientIds(r,t)},replaceContent:function(t,n,r){var i=e(this,"parentRecord"),s=i&&!e(i,"id"),o=e(this,"stateManager");r=r.map(function(t){Ember.assert("You can only add records of "+(e(this,"type")&&e(this,"type").toString())+" to this association.",!e(this,"type")||e(this,"type")===t.constructor),s&&t.send("waitingOn",i);var n=this.assignInverse(t,i);return t.get("transaction").relationshipBecameDirty(t,n,i),o.send("recordWasAdded",t),t.get("clientId")},this);var u=this.store,a=t+n,f;for(var l=t;l<a;l++){f=this.objectAt(l);var c=this.assignInverse(f,i,!0);f.get("transaction").relationshipBecameDirty(f,i,null),s&&f.send("doneWaitingOn",i),o.send("recordWasAdded",f)}this._super(t,n,r)},assignInverse:function(n,r,i){var s=e(n.constructor,"associations"),o=s.get(r.constructor),u,a,f;if(!o)return;for(var l=0,c=o.length;l<c;l++){u=o[l];if(u.kind==="belongsTo"){a=u;break}}if(a)return f=e(n,a.name),t(n,a.name,i?null:r),f},createRecord:function(t,n){var r=e(this,"parentRecord"),i=e(r,"store"),s=e(this,"type"),o;return n=n||e(r,"transaction"),o=i.createRecord.call(i,s,t,n),this.pushObject(o),o}})}(),function(){}(),function(){var e=Ember.get,t=Ember.set,n=Ember.String.fmt,r=Ember.EnumerableUtils.removeObject;DS.Transaction=Ember.Object.extend({init:function(){t(this,"buckets",{clean:Ember.Map.create(),created:Ember.Map.create(),updated:Ember.Map.create(),deleted:Ember.Map.create(),inflight:Ember.Map.create()}),this.dirtyRelationships={byChild:Ember.Map.create(),byNewParent:Ember.Map.create(),byOldParent:Ember.Map.create()}},createRecord:function(t,n){var r=e(this,"store");return r.createRecord(t,n,this)},add:function(t){Ember.assert("Once a record has changed, you cannot move it into a different transaction",!e(t,"isDirty"));var n=e(t,"transaction"),r=e(this,"store.defaultTransaction");Ember.assert("Models cannot belong to more than one transaction at a time.",n===r),this.adoptRecord(t)},commit:function(){var t=this,r;r=function(n,r,i){var s=t.bucketForType(n);s.forEach(function(t,n){if(n.isEmpty())return;var s=[];n.forEach(function(t){t.send("willCommit"),e(t,"isPending")===!1&&s.push(t)}),r.call(i,t,s)})};var i={updated:{eachType:function(e,t){r("updated",e,t)}},created:{eachType:function(e,t){r("created",e,t)}},deleted:{eachType:function(e,t){r("deleted",e,t)}}},s=e(this,"store"),o=e(s,"_adapter");this.removeCleanRecords();if(!o||!o.commit)throw n("Adapter is either null or does not implement `commit` method",this);o.commit(s,i)},rollback:function(){var t=e(this,"store"),n;["created","updated","deleted","inflight"].forEach(function(e){n=this.bucketForType(e),n.forEach(function(e,t){t.forEach(function(e){e.send("rollback")})})},this),this.removeCleanRecords()},remove:function(t){var n=e(this,"store.defaultTransaction");n.adoptRecord(t)},removeCleanRecords:function(){var e=this.bucketForType("clean"),t=this;e.forEach(function(e,n){n.forEach(function(e){t.remove(e)})})},bucketForType:function(t){var n=e(this,"buckets");return e(n,t)},adoptRecord:function(n){var r=e(n,"transaction");r&&r.removeFromBucket("clean",n),this.addToBucket("clean",n),t(n,"transaction",this)},addToBucket:function(e,t){var n=this.bucketForType(e),r=t.constructor,i=n.get(r);i||(i=Ember.OrderedSet.create(),n.set(r,i)),i.add(t)},removeFromBucket:function(e,t){var n=this.bucketForType(e),r=t.constructor,i=n.get(r);i.remove(t)},relationshipBecameDirty:function(e,t,n){var r=this.dirtyRelationships,i,s=r.byChild.get(e),o,u=!0;if(s)for(var a=0,f=s.length;a<f;a++)i=s[a],i.newParent===t&&(t=i.oldParent,this.removeRelationship(i),i.oldParent===n&&(u=!1));i={child:e,oldParent:t,newParent:n},u&&(this.addRelationshipTo("byChild",e,i),this.addRelationshipTo("byOldParent",t,i),this.addRelationshipTo("byNewParent",n,i))},removeRelationship:function(e){var t=this.dirtyRelationships;r(t.byOldParent.get(e.oldParent),e),r(t.byNewParent.get(e.newParent),e),r(t.byChild.get(e.child),e)},addRelationshipTo:function(e,t,n){var r=this.dirtyRelationships[e],i=r.get(t);i?i.push(n):(i=[n],r.set(t,i))},recordBecameDirty:function(e,t){this.removeFromBucket("clean",t),this.addToBucket(e,t)},recordBecameInFlight:function(e,t){this.removeFromBucket(e,t),this.addToBucket("inflight",t)},recordBecameClean:function(e,t){this.removeFromBucket(e,t),this.remove(t)}})}(),function(){var e=Ember.get,t=Ember.set,n=Ember.String.fmt,r={get:function(e){return this.savedData[e]}},i="unloaded",s="loading";DS.Store=Ember.Object.extend({init:function(){var n=e(this,"revision");if(n!==DS.CURRENT_API_REVISION&&!Ember.ENV.TESTING)throw new Error("Error: The Ember Data library has had breaking API changes since the last time you updated the library. Please review the list of breaking changes at https://github.com/emberjs/data/blob/master/BREAKING_CHANGES.md, then update your store's `revision` property to "+DS.CURRENT_API_REVISION);return(!e(DS,"defaultStore")||e(this,"isDefaultStore"))&&t(DS,"defaultStore",this),this.typeMaps={},this.recordCache=[],this.clientIdToId={},this.recordArraysByClientId={},this.loadingRecordArrays={},t(this,"defaultTransaction",this.transaction()),this._super()},transaction:function(){return DS.Transaction.create({store:this})},dataForRecord:function(t){var n=t.constructor,r=e(t,"clientId"),i=this.typeMapFor(n);return i.cidToHash[r]},adapter:null,_adapter:Ember.computed(function(){var t=e(this,"adapter");return typeof t=="string"?e(this,t,!1)||e(window,t):t}).property("adapter").cacheable(),clientIdCounter:1,createRecord:function(n,r,i){r=r||{};var s=n._create({store:this});i=i||e(this,"defaultTransaction"),i.adoptRecord(s);var o=e(s,"primaryKey"),u=r[o]||null,a;Ember.none(u)&&(a=e(this,"adapter"),a&&a.generateIdForRecord&&(u=a.generateIdForRecord(this,s),r.id=u));var f={},l;l=this.pushHash(f,u,n),s.send("didChangeData");var c=e(this,"recordCache");return t(s,"clientId",l),c[l]=s,s.setProperties(r),this.updateRecordArrays(n,l,e(s,"data")),s},deleteRecord:function(e){e.send("deleteRecord")},find:function(e,t,n){if(t===undefined)return this.findAll(e);if(n!==undefined)return this.findMany(e,t,n);if(Ember.typeOf(t)==="object")return this.findQuery(e,t);if(Ember.isArray(t))return this.findMany(e,t);var r=this.typeMapFor(e).idToCid[t];return this.findByClientId(e,r,t)},findByClientId:function(t,r,i){var o=e(this,"recordCache"),u,a;if(r!==undefined)a=o[r],a||(a=this.materializeRecord(t,r),u=this.typeMapFor(t).cidToHash,typeof u[r]=="object"&&a.send("didChangeData"));else{r=this.pushHash(s,i,t),a=this.materializeRecord(t,r,i);var f=e(this,"_adapter");if(!f||!f.find)throw n("Adapter is either null or does not implement `find` method",this);f.find(this,t,i)}return a},neededClientIds:function(e,t){var n=[],r=this.typeMapFor(e),o=r.cidToHash,u;for(var a=0,f=t.length;a<f;a++)u=t[a],o[u]===i&&(n.push(u),o[u]=s);return n},fetchUnloadedClientIds:function(e,t){var n=this.neededClientIds(e,t);this.fetchMany(e,n)},fetchMany:function(t,r){var i=this.clientIdToId,s=Ember.EnumerableUtils.map(r,function(e){return i[e]});if(!s.length)return;var o=e(this,"_adapter");if(!o||!o.findMany)throw n("Adapter is either null or does not implement `findMany` method",this);o.findMany(this,t,s)},findMany:function(e,t){var n=this.clientIdsForIds(e,t),r=this.neededClientIds(e,n),i=this.createManyArray(e,Ember.A(n)),s=n.length-r.length,o=this.loadingRecordArrays,u,a,f;i.send("loadedRecords",s);if(r.length){for(a=0,f=r.length;a<f;a++)u=r[a],o[u]?o[u].push(i):this.loadingRecordArrays[u]=[i];this.fetchMany(e,r)}return i},findQuery:function(t,r){var i=DS.AdapterPopulatedRecordArray.create({type:t,content:Ember.A([]),store:this}),s=e(this,"_adapter");if(!s||!s.findQuery)throw n("Adapter is either null or does not implement `findQuery` method",this);return s.findQuery(this,t,r,i),i},findAll:function(t){var n=this.typeMapFor(t),r=n.findAllCache;if(r)return r;var i=DS.RecordArray.create({type:t,content:Ember.A([]),store:this});this.registerRecordArray(i,t);var s=e(this,"_adapter");return s&&s.findAll&&s.findAll(this,t),n.findAllCache=i,i},filter:function(e,t,n){arguments.length===3?this.findQuery(e,t):arguments.length===2&&(n=t);var r=DS.FilteredRecordArray.create({type:e,content:Ember.A([]),store:this,filterFunction:n});return this.registerRecordArray(r,e,n),r},recordIsLoaded:function(e,t){return!Ember.none(this.typeMapFor(e).idToCid[t])},hashWasUpdated:function(t,n,r){if(e(r,"isDeleted"))return;this.updateRecordArrays(t,n,e(r,"data"))},commit:function(){var n=e(this,"defaultTransaction");t(this,"defaultTransaction",this.transaction()),n.commit()},didUpdateRecords:function(e,t){t?e.forEach(function(e,n){this.didUpdateRecord(e,t[n])},this):e.forEach(function(e){this.didUpdateRecord(e)},this)},didUpdateRecord:function(t,n){if(n){var r=e(t,"clientId"),i=this.typeMapFor(t.constructor).cidToHash;i[r]=n,t.send("didChangeData"),t.hashWasUpdated()}else t.send("didSaveData");t.send("didCommit")},didDeleteRecords:function(e){e.forEach(function(e){e.send("didCommit")})},didDeleteRecord:function(e){e.send("didCommit")},_didCreateRecord:function(t,n,r,i,s){var o=e(t,"data"),u,a;n?(r.cidToHash[i]=n,t.beginPropertyChanges(),t.send("didChangeData"),o.adapterDidUpdate(),t.hashWasUpdated(),t.endPropertyChanges(),u=n[s],r.idToCid[u]=i,this.clientIdToId[i]=u):o.commit(),t.send("didCommit")},didCreateRecords:function(t,n,r){var i=t.proto().primaryKey,s=this.typeMapFor(t),o;for(var u=0,a=e(n,"length");u<a;u++){var f=n[u],l=r[u];o=e(f,"clientId"),this._didCreateRecord(f,l,s,o,i)}},didCreateRecord:function(t,n){var r=t.constructor,i=this.typeMapFor(r),s,o;o=r.proto().primaryKey,n?Ember.assert("The server must provide a primary key: "+o,e(n,o)):Ember.assert("The server did not return data, and you did not create a primary key ("+o+") on the client",e(e(t,"data"),o)),s=e(t,"clientId"),this._didCreateRecord(t,n,i,s,o)},recordWasInvalid:function(e,t){e.send("becameInvalid",t)},registerRecordArray:function(e,t,n){var r=this.typeMapFor(t).recordArrays;r.push(e),this.updateRecordArrayFilter(e,t,n)},createManyArray:function(e,t){var n=DS.ManyArray.create({type:e,content:t,store:this});return t.forEach(function(e){var t=this.recordArraysForClientId(e);t.add(n)},this),n},updateRecordArrayFilter:function(t,n,i){var s=this.typeMapFor(n),o=s.cidToHash,u=s.clientIds,a,f,l,c=e(this,"recordCache"),h,p;for(var d=0,v=u.length;d<v;d++)a=u[d],h=!1,f=o[a],typeof f=="object"&&((p=c[a])?e(p,"isDeleted")||(l=e(p,"data"),h=!0):(r.savedData=f,l=r,h=!0),h&&this.updateRecordArray(t,i,n,a,l))},updateRecordArrays:function(t,n,r){var i=this.typeMapFor(t).recordArrays,s;i.forEach(function(i){s=e(i,"filterFunction"),this.updateRecordArray(i,s,t,n,r)},this);var o=this.loadingRecordArrays[n],u;if(o){for(var a=0,f=o.length;a<f;a++)o[a].send("loadedRecords",1);this.loadingRecordArrays[n]=null}},updateRecordArray:function(t,n,r,i,s){var o;n?o=n(s):o=!0;var u=e(t,"content"),a=u.indexOf(i)!==-1,f=this.recordArraysForClientId(i);o&&!a?(f.add(t),u.pushObject(i)):!o&&a&&(f.remove(t),u.removeObject(i))},removeFromRecordArrays:function(t){var n=e(t,"clientId"),r=this.recordArraysForClientId(n);r.forEach(function(t){var r=e(t,"content");r.removeObject(n)})},recordArraysForClientId:function(t){var n=e(this,"recordArraysByClientId"),r=n[t];return r||(r=n[t]=Ember.OrderedSet.create()),r},typeMapFor:function(t){var n=e(this,"typeMaps"),r=Ember.guidFor(t),i=n[r];return i?i:n[r]={idToCid:{},clientIds:[],cidToHash:{},recordArrays:[]}},clientIdForId:function(e,t){var n=this.typeMapFor(e).idToCid[t];return n!==undefined?n:this.pushHash(i,t,e)},clientIdsForIds:function(e,t){var n=this.typeMapFor(e),r=n.idToCid;return Ember.EnumerableUtils.map(t,function(t){var n=r[t];return n?n:this.pushHash(i,t,e)},this)},load:function(t,n,i){if(i===undefined){i=n;var s=t.proto().primaryKey;Ember.assert("A data hash was loaded for a record of type "+t.toString()+" but no primary key '"+s+"' was provided.",s in i),n=i[s]}var o=this.typeMapFor(t),u=o.cidToHash,a=o.idToCid[n],f=e(this,"recordCache");if(a!==undefined){u[a]=i;var l=f[a];l&&l.send("didChangeData")}else a=this.pushHash(i,n,t);return r.savedData=i,this.updateRecordArrays(t,a,r),{id:n,clientId:a}},loadMany:function(t,n,r){var i=Ember.A([]);if(r===undefined){r=n,n=[];var s=t.proto().primaryKey;n=Ember.EnumerableUtils.map(r,function(e){return e[s]})}for(var o=0,u=e(n,"length");o<u;o++){var a=this.load(t,n[o],r[o]);i.pushObject(a.clientId)}return{clientIds:i,ids:n}},pushHash:function(e,t,n){var r=this.typeMapFor(n),i=r.idToCid,s=this.clientIdToId,o=r.clientIds,u=r.cidToHash,a=++this.clientIdCounter;return u[a]=e,t&&(i[t]=a,s[a]=t),o.push(a),a},materializeRecord:function(t,n,r){var i;return e(this,"recordCache")[n]=i=t._create({store:this,clientId:n,_id:r}),e(this,"defaultTransaction").adoptRecord(i),i.send("loadingData"),i},destroy:function(){return e(DS,"defaultStore")===this&&t(DS,"defaultStore",null),this._super()}})}(),function(){var e=Ember.get,t=Ember.set,n=Ember.guidFor,r=Ember.computed(function(t){var n=e(this,"parentState");if(n)return e(n,t)}).property(),i=function(e){for(var t in e)if(e.hasOwnProperty(t))return!1;return!0},s=function(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!0;return!1};DS.State=Ember.State.extend({isLoaded:r,isDirty:r,isSaving:r,isDeleted:r,isError:r,isNew:r,isValid:r,isPending:r,dirtyType:r});var o=function(n,r){var i=r.key,s=r.value,o=e(n,"record"),u=e(o,"data");t(u,i,s)},u=function(t,n){var r=n.key,i=n.value,s=e(t,"record"),o=e(s,"data");o.setAssociation(r,i)},a=function(t){var n=e(t,"record"),r=e(n,"data");r._savedData=null,n.notifyPropertyChange("data")},f=function(t,r){var i=e(t,"record"),s=e(i,"pendingQueue"),o=n(r),u=function(){e(r,"id")&&(t.send("doneWaitingOn",r),Ember.removeObserver(r,"id",u))};s[o]=[r,u],Ember.addObserver(r,"id",u)},l=Ember.Mixin.create({setProperty:o,setAssociation:u}),c=Ember.Mixin.create({deleteRecord:function(t){var n=e(t,"record");this._super(t),n.withTransaction(function(e){e.recordBecameClean("created",n)}),t.goToState("deleted.saved")}}),h=Ember.Mixin.create({deleteRecord:function(t){this._super(t);var n=e(t,"record");n.withTransaction(function(e){e.recordBecameClean("updated",n)}),t.goToState("deleted")}}),p=DS.State.extend({initialState:"uncommitted",isDirty:!0,uncommitted:DS.State.extend({enter:function(t){var n=e(this,"dirtyType"),r=e(t,"record");r.withTransaction(function(e){e.recordBecameDirty(n,r)})},deleteRecord:Ember.K,waitingOn:function(e,t){f(e,t),e.goToState("pending")},willCommit:function(e){e.goToState("inFlight")},becameInvalid:function(t){var n=e(this,"dirtyType"),r=e(t,"record");r.withTransaction(function(e){e.recordBecameInFlight(n,r)}),t.goToState("invalid")},rollback:function(t){var n=e(t,"record"),r=e(this,"dirtyType"),i=e(n,"data");i.rollback(),n.withTransaction(function(e){e.recordBecameClean(r,n)}),t.goToState("saved")}},l),inFlight:DS.State.extend({isSaving:!0,enter:function(t){var n=e(this,"dirtyType"),r=e(t,"record");r.withTransaction(function(e){e.recordBecameInFlight(n,r)})},didCommit:function(t){var n=e(this,"dirtyType"),r=e(t,"record");r.withTransaction(function(e){e.recordBecameClean("inflight",r)}),t.goToState("saved"),t.send("invokeLifecycleCallbacks",n)},becameInvalid:function(n,r){var i=e(n,"record");t(i,"errors",r),n.goToState("invalid"),n.send("invokeLifecycleCallbacks")},becameError:function(e){e.goToState("error"),e.send("invokeLifecycleCallbacks")},didChangeData:a}),pending:DS.State.extend({initialState:"uncommitted",isPending:!0,uncommitted:DS.State.extend({deleteRecord:function(t){var n=e(t,"record"),r=e(n,"pendingQueue"),i;for(var s in r){if(!r.hasOwnProperty(s))continue;i=r[s],Ember.removeObserver(i[0],"id",i[1])}},willCommit:function(e){e.goToState("committing")},doneWaitingOn:function(t,r){var s=e(t,"record"),o=e(s,"pendingQueue"),u=n(r);delete o[u],i(o)&&t.send("doneWaiting")},doneWaiting:function(t){var n=e(this,"dirtyType");t.goToState(n+".uncommitted")}},l),committing:DS.State.extend({isSaving:!0,doneWaitingOn:function(t,r){var s=e(t,"record"),o=e(s,"pendingQueue"),u=n(r);delete o[u],i(o)&&t.send("doneWaiting")},doneWaiting:function(t){var n=e(t,"record"),r=e(n,"transaction");Ember.run.once(r,r.commit)},willCommit:function(t){var n=e(t,"record"),r=e(n,"pendingQueue");if(i(r)){var s=e(this,"dirtyType");t.goToState(s+".inFlight")}}})}),invalid:DS.State.extend({isValid:!1,exit:function(t){var n=e(t,"record");n.withTransaction(function(e){e.recordBecameClean("inflight",n)})},deleteRecord:function(e){e.goToState("deleted")},setAssociation:u,setProperty:function(n,r){o(n,r);var i=e(n,"record"),u=e(i,"errors"),a=r.key;t(u,a,null),s(u)||n.send("becameValid")},rollback:function(e){e.send("becameValid"),e.send("rollback")},becameValid:function(e){e.goToState("uncommitted")},invokeLifecycleCallbacks:function(t){var n=e(t,"record");n.trigger("becameInvalid",n)}})}),d=p.create({dirtyType:"created",isNew:!0}),v=p.create({dirtyType:"updated"});d.states.uncommitted.reopen(c),d.states.pending.states.uncommitted.reopen(c),d.states.uncommitted.reopen({rollback:function(e){this._super(e),e.goToState("deleted.saved")}}),v.states.uncommitted.reopen(h),v.states.pending.states.uncommitted.reopen(h),v.states.inFlight.reopen({didSaveData:function(t){var n=e(t,"record"),r=e(n,"data");r.saveData(),r.adapterDidUpdate()}});var m={rootState:Ember.State.create({isLoaded:!1,isDirty:!1,isSaving:!1,isDeleted:!1,isError:!1,isNew:!1,isValid:!0,isPending:!1,empty:DS.State.create({loadingData:function(e){e.goToState("loading")},didChangeData:function(e){a(e),e.goToState("loaded.created")}}),loading:DS.State.create({exit:function(t){var n=e(t,"record");n.trigger("didLoad")},didChangeData:function(e,t){a(e),e.send("loadedData")},loadedData:function(e){e.goToState("loaded")}}),loaded:DS.State.create({initialState:"saved",isLoaded:!0,saved:DS.State.create({setProperty:function(e,t){o(e,t),e.goToState("updated")},setAssociation:function(e,t){u(e,t),e.goToState("updated")},didChangeData:a,deleteRecord:function(e){e.goToState("deleted")},waitingOn:function(e,t){f(e,t),e.goToState("updated.pending")},invokeLifecycleCallbacks:function(t,n){var r=e(t,"record");n==="created"?r.trigger("didCreate",r):r.trigger("didUpdate",r)}}),created:d,updated:v}),deleted:DS.State.create({isDeleted:!0,isLoaded:!0,isDirty:!0,enter:function(t){var n=e(t,"record"),r=e(n,"store");r.removeFromRecordArrays(n)},start:DS.State.create({enter:function(t){var n=e(t,"record");n.withTransaction(function(e){e.recordBecameDirty("deleted",n)})},willCommit:function(e){e.goToState("inFlight")},rollback:function(t){var n=e(t,"record"),r=e(n,"data");r.rollback(),n.withTransaction(function(e){e.recordBecameClean("deleted",n)}),t.goToState("loaded")}}),inFlight:DS.State.create({isSaving:!0,enter:function(t){var n=e(t,"record");n.withTransaction(function(e){e.recordBecameInFlight("deleted",n)})},didCommit:function(t){var n=e(t,"record");n.withTransaction(function(e){e.recordBecameClean("inflight",n)}),t.goToState("saved"),t.send("invokeLifecycleCallbacks")}}),saved:DS.State.create({isDirty:!1,invokeLifecycleCallbacks:function(t){var n=e(t,"record");n.trigger("didDelete",n)}})}),error:DS.State.create({isError:!0,invokeLifecycleCallbacks:function(t){var n=e(t,"record");n.trigger("becameError",n)}})})};DS.StateManager=Ember.StateManager.extend({record:null,initialState:"rootState",states:m})}(),function(){var e=Ember.get,t=Ember.set,n=DS._DataProxy=function(e){this.record=e,this.unsavedData={},this.associations={}};n.prototype={get:function(e){return Ember.get(this,e)},set:function(e,t){return Ember.set(this,e,t)},setAssociation:function(e,t){this.associations[e]=t},savedData:function(){var t=this._savedData;if(t)return t;var n=this.record,r=e(n,"clientId"),i=e(n,"store");if(i)return t=i.dataForRecord(n),this._savedData=t,t},unknownProperty:function(t){var n=this.unsavedData,r=this.associations,i=this.savedData(),s,o=n[t],u;return u=r[t],u!==undefined?(s=e(this.record,"store"),s.clientIdToId[u]):(i&&o===undefined&&(o=i[t]),o)},setUnknownProperty:function(e,t){var n=this.record,r=this.unsavedData;return r[e]=t,n.hashWasUpdated(),t},commit:function(){this.saveData(),this.record.notifyPropertyChange("data")},rollback:function(){this.unsavedData={},this.record.notifyPropertyChange("data")},saveData:function(){var e=this.record,t=this.unsavedData,n=this.savedData();for(var r in t)t.hasOwnProperty(r)&&(n[r]=t[r],delete t[r])},adapterDidUpdate:function(){this.unsavedData={}}}}(),function(){var e=Ember.get,t=Ember.set,n=Ember.none,r=Ember.computed(function(t){return e(e(this,"stateManager.currentState"),t)}).property("stateManager.currentState").cacheable();DS.Model=Ember.Object.extend(Ember.Evented,{isLoaded:r,isDirty:r,isSaving:r,isDeleted:r,isError:r,isNew:r,isPending:r,isValid:r,clientId:null,transaction:null,stateManager:null,pendingQueue:null,errors:null,primaryKey:"id",id:Ember.computed(function(n,r){var i=e(this,"primaryKey"),s=e(this,"data");if(arguments.length===2)return t(s,i,r),r;var o=e(s,i);return o?o:this._id}).property("primaryKey","data"),addIdToJSON:function(e,t,n){t&&(e[n]=t)},addAttributesToJSON:function(t,n,r){n.forEach(function(n,i){var s=i.key(this.constructor),o=e(r,s);o===undefined&&(o=i.options.defaultValue),t[s]=o},this)},addHasManyToJSON:function(t,n,r,i){var s=r.key,o=e(this,s),u=[],a,f,l,c;if(r.options.embedded)o.forEach(function(e){u.push(e.toJSON(i))});else{var h=e(o,"content");for(a=0,f=h.length;a<f;a++)l=h[a],c=e(this,"store").clientIdToId[l],c!==undefined&&u.push(c)}s=r.options.key||e(this,"namingConvention").keyToJSONKey(s),t[s]=u},addBelongsToToJSON:function(t,r,i,s){var o=i.key,u,a;i.options.embedded?(o=i.options.key||e(this,"namingConvention").keyToJSONKey(o),u=e(r.record,o),t[o]=u?u.toJSON(s):null):(o=i.options.key||e(this,"namingConvention").foreignKey(o),a=r.get(o),t[o]=n(a)?null:a)},toJSON:function(t){var n=e(this,"data"),r={},i=this.constructor,s=e(i,"attributes"),o=e(this,"primaryKey"),u=e(this,"id"),a=e(this,"store"),f;return t=t||{},this.addIdToJSON(r,u,o),this.addAttributesToJSON(r,s,n),f=e(i,"associationsByName"),f.forEach(function(e,i){t.associations&&i.kind==="hasMany"?this.addHasManyToJSON(r,n,i,t):i.kind==="belongsTo"&&this.addBelongsToToJSON(r,n,i,t)},this),r},data:Ember.computed(function(){return new DS._DataProxy(this)}).cacheable(),didLoad:Ember.K,didUpdate:Ember.K,didCreate:Ember.K,didDelete:Ember.K,becameInvalid:Ember.K,becameError:Ember.K,init:function(){var e=DS.StateManager.create({record:this});t(this,"pendingQueue",{}),t(this,"stateManager",e),e.goToState("empty")},destroy:function(){e(this,"isDeleted")||this.deleteRecord(),this._super()},send:function(t,n){return e(this,"stateManager").send(t,n)},withTransaction:function(t){var n=e(this,"transaction");n&&t(n)},setProperty:function(e,t){this.send("setProperty",{key:e,value:t})},deleteRecord:function(){this.send("deleteRecord")},waitingOn:function(e){this.send("waitingOn",e)},notifyHashWasUpdated:function(){var t=e(this,"store");t&&t.hashWasUpdated(this.constructor,e(this,"clientId"),this)},unknownProperty:function(t){var n=e(this,"data");n&&t in n&&Ember.assert("You attempted to access the "+t+" property on a record without defining an attribute.",!1)},setUnknownProperty:function(t,n){var r=e(this,"data");if(!(r&&t in r))return this._super(t,n);Ember.assert("You attempted to set the "+t+" property on a record without defining an attribute.",!1)},namingConvention:{keyToJSONKey:function(e){return Ember.String.decamelize(e)},foreignKey:function(e){return Ember.String.decamelize(e)+"_id"}},hashWasUpdated:function(){Ember.run.once(this,this.notifyHashWasUpdated)},dataDidChange:Ember.observer(function(){var n=e(this.constructor,"associationsByName"),r=e(this,"data"),i=e(this,"store"),s=i.idToClientId,o;n.forEach(function(n,s){if(s.kind==="hasMany"){o=this.cacheFor(n);if(o){var u=s.options.key||e(this,"namingConvention").keyToJSONKey(n),a=r.get(u)||[],f;s.options.embedded?f=i.loadMany(s.type,a).clientIds:f=Ember.EnumerableUtils.map(a,function(e){return i.clientIdForId(s.type,e)}),t(o,"content",Ember.A(f)),o.fetch()}}},this)},"data"),trigger:function(e){Ember.tryInvoke(this,e,[].slice.call(arguments,1)),this._super.apply(this,arguments)}});var i=function(t){return function(){var n=e(DS,"defaultStore"),r=[].slice.call(arguments);return r.unshift(this),n[t].apply(n,r)}};DS.Model.reopenClass({isLoaded:i("recordIsLoaded"),find:i("find"),filter:i("filter"),_create:DS.Model.create,create:function(){throw new Ember.Error("You should not call `create` on a model. Instead, call `createRecord` with the attributes you would like to set.")},createRecord:i("createRecord")})}(),function(){function t(t,n,r){var i=e(t,"data"),s=e(i,r);return s===undefined&&(s=n.defaultValue),s}var e=Ember.get;DS.Model.reopenClass({attributes:Ember.computed(function(){var e=Ember.Map.create();return this.eachComputedProperty(function(t,n){n.isAttribute&&e.set(t,n)}),e}).cacheable(),processAttributeKeys:function(){if(this.processedAttributeKeys)return;var e=this.proto().namingConvention;this.eachComputedProperty(function(t,n){n.isAttribute&&!n.options.key&&(n.options.key=e.keyToJSONKey(t,this))},this)}}),DS.attr=function(e,n){var r=DS.attr.transforms[e];Ember.assert("Could not find model attribute of type "+e,!!r);var i=r.from,s=r.to;n=n||{};var o={type:e,isAttribute:!0,options:n,key:function(e){return e.processAttributeKeys(),n.key}};return Ember.computed(function(e,r){var u;return e=o.key(this.constructor),arguments.length===2?(r=s(r),r!==t(this,n,e)&&this.setProperty(e,r)):r=t(this,n,e),i(r)}).property("data").cacheable().meta(o)},DS.attr.transforms={string:{from:function(e){return Ember.none(e)?null:String(e)},to:function(e){return Ember.none(e)?null:String(e)}},number:{from:function(e){return Ember.none(e)?null:Number(e)},to:function(e){return Ember.none(e)?null:Number(e)}},"boolean":{from:function(e){return Boolean(e)},to:function(e){return Boolean(e)}},date:{from:function(e){var t=typeof e;return t==="string"||t==="number"?new Date(e):e===null||e===undefined?e:null},to:function(e){if(e instanceof Date){var t=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],r=function(e){return e<10?"0"+e:""+e},i=e.getUTCFullYear(),s=e.getUTCMonth(),o=e.getUTCDate(),u=e.getUTCDay(),a=e.getUTCHours(),f=e.getUTCMinutes(),l=e.getUTCSeconds(),c=t[u],h=r(o),p=n[s];return c+", "+h+" "+p+" "+i+" "+r(a)+":"+r(f)+":"+r(l)+" GMT"}return e===undefined?undefined:null}}}}(),function(){}(),function(){var e=Ember.get,t=Ember.set,n=Ember.none,r=function(t,r,i,s,o){var u=e(i,s);return n(u)?undefined:t.load(r,u).id},i=function(t,n,r,i,s){return e(r,i)},s=function(t,n,s){n=n||{};var o=n.embedded,u=o?r:i,a={type:t,isAssociation:!0,options:n,kind:"belongsTo"};return Ember.computed(function(r,i){var s=e(this,"data"),a,f,l,c=e(this,"store");return typeof t=="string"&&(t=e(this,t,!1)||e(window,t)),arguments.length===2?(r=n.key||e(this,"namingConvention").foreignKey(r),this.send("setAssociation",{key:r,value:Ember.none(i)?null:e(i,"clientId")}),i):(o?r=n.key||e(this,"namingConvention").keyToJSONKey(r):r=n.key||e(this,"namingConvention").foreignKey(r),f=u(c,t,s,r,!0),l=f?c.find(t,f):null,l)}).property("data").cacheable().meta(a)};DS.belongsTo=function(e,t){return Ember.assert("The type passed to DS.belongsTo must be defined",!!e),s(e,t)}}(),function(){var e=Ember.get,t=Ember.set,n=function(t,n,r,i){var s=e(r,i);return s?t.loadMany(n,s).ids:[]},r=function(t,n,r,i,s){return e(r,i)},i=function(i,s){s=s||{};var o=s.embedded,u=o?n:r,a={type:i,isAssociation:!0,options:s,kind:"hasMany"};return Ember.computed(function(n,r){var o=e(this,"data"),a=e(this,"store"),f,l,c;return typeof i=="string"&&(i=e(this,i,!1)||e(window,i)),n=s.key||e(this,"namingConvention").keyToJSONKey(n),f=u(a,i,o,n),c=a.findMany(i,f||[]),t(c,"parentRecord",this),c}).property().cacheable().meta(a)};DS.hasMany=function(e,t){return Ember.assert("The type passed to DS.hasMany must be defined",!!e),i(e,t)}}(),function(){var e=Ember.get;DS.Model.reopenClass({typeForAssociation:function(t){var n=e(this,"associationsByName").get(t);return n&&n.type},associations:Ember.computed(function(){var t=Ember.Map.create();return this.eachComputedProperty(function(n,r){if(r.isAssociation){var i=r.type,s=t.get(i);typeof i=="string"&&(i=e(this,i,!1)||e(window,i),r.type=i),s||(s=[],t.set(i,s)),s.push({name:n,kind:r.kind})}}),t}).cacheable(),associationsByName:Ember.computed(function(){var t=Ember.Map.create(),n;return this.eachComputedProperty(function(r,i){i.isAssociation&&(i.key=r,n=i.type,typeof n=="string"&&(n=e(this,n,!1)||e(window,n),i.type=n),t.set(r,i))}),t}).cacheable()})}(),function(){}(),function(){DS.Adapter=Ember.Object.extend({find:null,generateIdForRecord:null,commit:function(e,t){t.updated.eachType(function(t,n){this.updateRecords(e,t,n.slice())},this),t.created.eachType(function(t,n){this.createRecords(e,t,n.slice())},this),t.deleted.eachType(function(t,n){this.deleteRecords(e,t,n.slice())},this)},createRecords:function(e,t,n){n.forEach(function(n){this.createRecord(e,t,n)},this)},updateRecords:function(e,t,n){n.forEach(function(n){this.updateRecord(e,t,n)},this)},deleteRecords:function(e,t,n){n.forEach(function(n){this.deleteRecord(e,t,n)},this)},findMany:function(e,t,n){n.forEach(function(n){this.find(e,t,n)},this)}})}(),function(){var e=
Ember.set;Ember.onLoad("application",function(t){t.registerInjection({name:"store",before:"controllers",injection:function(t,n,r){r==="Store"&&e(n,"store",t[r].create())}}),t.registerInjection({name:"giveStoreToControllers",injection:function(e,t,n){if(n.match(/Controller$/)){var r=n.charAt(0).toLowerCase()+n.substr(1),i=t.get("store"),s=t.get(r);s.set("store",i)}}})})}(),function(){var e=Ember.get;DS.FixtureAdapter=DS.Adapter.extend({simulateRemoteResponse:!0,latency:50,fixturesForType:function(e){return e.FIXTURES?Ember.A(e.FIXTURES):null},queryFixtures:function(e,t){return e},mockJSON:function(e,t){return t.toJSON({associations:!0})},generateIdForRecord:function(e,t){return Ember.guidFor(t)},find:function(e,t,n){var r=this.fixturesForType(t);Ember.assert("Unable to find fixtures for model type "+t.toString(),!!r),r&&(r=r.findProperty("id",n)),r&&this.simulateRemoteCall(function(){e.load(t,r)},e,t)},findMany:function(e,t,n){var r=this.fixturesForType(t);Ember.assert("Unable to find fixtures for model type "+t.toString(),!!r),r&&(r=r.filter(function(e){return n.indexOf(e.id)!==-1})),r&&this.simulateRemoteCall(function(){e.loadMany(t,r)},e,t)},findAll:function(e,t){var n=this.fixturesForType(t);Ember.assert("Unable to find fixtures for model type "+t.toString(),!!n),this.simulateRemoteCall(function(){e.loadMany(t,n)},e,t)},findQuery:function(e,t,n,r){var i=this.fixturesForType(t);Ember.assert("Unable to find fixtures for model type "+t.toString(),!!i),i=this.queryFixtures(i,n),i&&this.simulateRemoteCall(function(){r.load(i)},e,t)},createRecord:function(e,t,n){var r=this.mockJSON(t,n);r.id=this.generateIdForRecord(e,n),this.simulateRemoteCall(function(){e.didCreateRecord(n,r)},e,t,n)},updateRecord:function(e,t,n){var r=this.mockJSON(t,n);this.simulateRemoteCall(function(){e.didUpdateRecord(n,r)},e,t,n)},deleteRecord:function(e,t,n){this.simulateRemoteCall(function(){e.didDeleteRecord(n)},e,t,n)},simulateRemoteCall:function(t,n,r,i){e(this,"simulateRemoteResponse")?setTimeout(t,e(this,"latency")):t()}}),DS.fixtureAdapter=DS.FixtureAdapter.create()}(),function(){var e=Ember.get,t=Ember.set;DS.RESTAdapter=DS.Adapter.extend({bulkCommit:!1,createRecord:function(e,t,n){var r=this.rootForType(t),i={};i[r]=n.toJSON(),this.ajax(this.buildURL(r),"POST",{data:i,context:this,success:function(r){this.didCreateRecord(e,t,n,r)}})},didCreateRecord:function(e,t,n,r){var i=this.rootForType(t);this.sideload(e,t,r,i),e.didCreateRecord(n,r[i])},createRecords:function(t,n,r){if(e(this,"bulkCommit")===!1)return this._super(t,n,r);var i=this.rootForType(n),s=this.pluralize(i),o={};o[s]=r.map(function(e){return e.toJSON()}),this.ajax(this.buildURL(i),"POST",{data:o,context:this,success:function(e){this.didCreateRecords(t,n,r,e)}})},didCreateRecords:function(e,t,n,r){var i=this.pluralize(this.rootForType(t));this.sideload(e,t,r,i),e.didCreateRecords(t,n,r[i])},updateRecord:function(t,n,r){var i=e(r,"id"),s=this.rootForType(n),o={};o[s]=r.toJSON(),this.ajax(this.buildURL(s,i),"PUT",{data:o,context:this,success:function(e){this.didUpdateRecord(t,n,r,e)}})},didUpdateRecord:function(e,t,n,r){var i=this.rootForType(t);this.sideload(e,t,r,i),e.didUpdateRecord(n,r&&r[i])},updateRecords:function(t,n,r){if(e(this,"bulkCommit")===!1)return this._super(t,n,r);var i=this.rootForType(n),s=this.pluralize(i),o={};o[s]=r.map(function(e){return e.toJSON()}),this.ajax(this.buildURL(i,"bulk"),"PUT",{data:o,context:this,success:function(e){this.didUpdateRecords(t,n,r,e)}})},didUpdateRecords:function(e,t,n,r){var i=this.pluralize(this.rootForType(t));this.sideload(e,t,r,i),e.didUpdateRecords(n,r[i])},deleteRecord:function(t,n,r){var i=e(r,"id"),s=this.rootForType(n);this.ajax(this.buildURL(s,i),"DELETE",{context:this,success:function(e){this.didDeleteRecord(t,n,r,e)}})},didDeleteRecord:function(e,t,n,r){r&&this.sideload(e,t,r),e.didDeleteRecord(n)},deleteRecords:function(t,n,r){if(e(this,"bulkCommit")===!1)return this._super(t,n,r);var i=this.rootForType(n),s=this.pluralize(i),o={};o[s]=r.map(function(t){return e(t,"id")}),this.ajax(this.buildURL(i,"bulk"),"DELETE",{data:o,context:this,success:function(e){this.didDeleteRecords(t,n,r,e)}})},didDeleteRecords:function(e,t,n,r){r&&this.sideload(e,t,r),e.didDeleteRecords(n)},find:function(e,t,n){var r=this.rootForType(t);this.ajax(this.buildURL(r,n),"GET",{success:function(n){this.sideload(e,t,n,r),e.load(t,n[r])}})},findMany:function(e,t,n){var r=this.rootForType(t),i=this.pluralize(r);this.ajax(this.buildURL(r),"GET",{data:{ids:n},success:function(n){this.sideload(e,t,n,i),e.loadMany(t,n[i])}})},findAll:function(e,t){var n=this.rootForType(t),r=this.pluralize(n);this.ajax(this.buildURL(n),"GET",{success:function(n){this.sideload(e,t,n,r),e.loadMany(t,n[r])}})},findQuery:function(e,t,n,r){var i=this.rootForType(t),s=this.pluralize(i);this.ajax(this.buildURL(i),"GET",{data:n,success:function(n){this.sideload(e,t,n,s),r.load(n[s])}})},plurals:{},pluralize:function(e){return this.plurals[e]||e+"s"},rootForType:function(e){if(e.url)return e.url;var t=e.toString().split("."),n=t[t.length-1];return n.replace(/([A-Z])/g,"_$1").toLowerCase().slice(1)},ajax:function(e,t,n){n.url=e,n.type=t,n.dataType="json",n.contentType="application/json; charset=utf-8",n.context=this,n.data&&t!=="GET"&&(n.data=JSON.stringify(n.data)),jQuery.ajax(n)},sideload:function(t,n,r,i){var s,o,u={};u[i]=!0;for(var a in r){if(!r.hasOwnProperty(a))continue;if(a===i)continue;s=n.typeForAssociation(a),s||(o=e(this,"mappings"),Ember.assert("Your server returned a hash with the key "+a+" but you have no mappings",!!o),s=e(o,a),typeof s=="string"&&(s=e(window,s)),Ember.assert("Your server returned a hash with the key "+a+" but you have no mapping for it",!!s)),this.sideloadAssociations(t,s,r,a,u)}},sideloadAssociations:function(t,n,r,i,s){s[i]=!0,e(n,"associationsByName").forEach(function(e,n){e=n.key||e,n.kind==="belongsTo"&&(e=this.pluralize(e)),r[e]&&!s[e]&&this.sideloadAssociations(t,n.type,r,e,s)},this),this.loadValue(t,n,r[i])},loadValue:function(e,t,n){n instanceof Array?e.loadMany(t,n):e.load(t,n)},buildURL:function(e,t){var n=[""];return Ember.assert("Namespace URL ("+this.namespace+") must not start with slash",!this.namespace||this.namespace.toString().charAt(0)!=="/"),Ember.assert("Record URL ("+e+") must not start with slash",!e||e.toString().charAt(0)!=="/"),Ember.assert("URL suffix ("+t+") must not start with slash",!t||t.toString().charAt(0)!=="/"),this.namespace!==undefined&&n.push(this.namespace),n.push(this.pluralize(e)),t!==undefined&&n.push(t),n.join("/")}})}(),function(){}();
source :rubygems
gem 'goliath'
gem 'em-http-request'
gem 'oj'
GEM
remote: http://rubygems.org/
specs:
addressable (2.3.2)
async-rack (0.5.1)
rack (~> 1.1)
em-http-request (1.0.0)
addressable (>= 2.2.3)
em-socksify
eventmachine (>= 1.0.0.beta.3)
http_parser.rb (>= 0.5.2)
em-socksify (0.1.0)
eventmachine
em-synchrony (1.0.2)
eventmachine (>= 1.0.0.beta.1)
em-websocket (0.3.8)
addressable (>= 2.1.1)
eventmachine (>= 0.12.9)
eventmachine (1.0.0.rc.4)
goliath (1.0.0)
async-rack
em-synchrony (>= 1.0.0)
em-websocket
eventmachine (>= 1.0.0.beta.4)
http_parser.rb (= 0.5.3)
log4r
multi_json
rack (>= 1.2.2)
rack-contrib
rack-respond_to
http_parser.rb (0.5.3)
log4r (1.1.10)
multi_json (1.3.6)
oj (1.3.4)
rack (1.4.1)
rack-accept-media-types (0.9)
rack-contrib (1.1.0)
rack (>= 0.9.1)
rack-respond_to (0.9.8)
rack-accept-media-types (>= 0.6)
PLATFORMS
ruby
DEPENDENCIES
em-http-request
goliath
oj
<!DOCTYPE html>
<html>
<head>
<title>Notes (Ember.js &amp; elasticsearch)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!-- *************** TWITTER @ANYWHERE LOGIN ************************************************ -->
<!-- Use the `Consumer Key` from OAuth settings -->
<script src="http://platform.twitter.com/anywhere.js?id=RolSFIW282BJrF4xGXyXDw&amp;v=1" type="text/javascript"></script>
<link href='http://fonts.googleapis.com/css?family=Londrina+Shadow' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Waiting+for+the+Sunrise' rel='stylesheet' type='text/css'>
<!-- *************** STYLES ***************************************************************** -->
<style>
body
{ color: #222; background-color: #f5f5f5; font-family: 'Helvetica Neue', Helvetica, sans-serif; padding: 2em 4em; }
#invitation
{ font-size: 2em; font-weight: 300; margin-right: 1em; }
#post_note
{ font: normal 300 2em/100% 'Helvetica Neue', Helvetica, sans-serif;
margin: 0 0 0.5em 0; padding: 0.25em 0.5em 0.2em 0.5em; width: 95%;
outline: none; border: none; border-bottom: 2px solid #cc3145;
box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px;
border-top-left-radius: 0.1em; border-top-right-radius: 0.1em; }
#user
{ font-family: 'Londrina Shadow', cursive; font-size: 28px; }
#user img
{ width: 30px; height: 30px; border-radius: 15px; position: relative; top: 6px; }
#user a.sign_out
{ cursor: pointer; float: right; }
#user a.sign_out:hover
{ font-weight: bold; border-bottom: 1px solid #444; }
#login
{ padding-top: 1em; margin-top: 1em; border-top: 1px solid rgba(0,0,0,0.1); height: 23px; }
#notes
{ margin: 0 0 0 -0.5em; }
.note
{ color: rgba(0,0,0,0.9);
font-family: 'Waiting for the Sunrise', cursive;
font-size: 26px;
background-color: #fff86b;
margin: 0.5em 0.5em 0.5em 0.5em;
padding: 0.5em 0.75em;
border-radius: 0.5em;
display: inline-block;
box-shadow: rgba(0,0,0,0.6) 0px 1px 2px; }
.note p
{ color: rgba(0,0,0,0.6); font-size: 80%; margin: 0; padding: 0.25em 0; }
.note p strong
{ color: rgba(0,0,0,0.75); }
.note p .time_ago
{ color: rgba(0,0,0,0.7); }
.note blockquote
{ margin: 0; font-weight: bold; }
#source
{ color: #c1c1c1; font-size: 12px; margin: 3em 0 0 0; }
#source a
{ color: #c0c0c0; text-decoration: none; }
#source a:hover
{ color: #a3a3a3; text-decoration: underline; }
</style>
</head>
<body>
<!-- *************** VIEWS ****************************************************************** -->
<div id="user">
<script type="text/x-handlebars">
{{#with App.current_user}}
<img {{bindAttr src="App.current_user.profile_image_url"}}>
Welcome, {{App.current_user.screen_name}}!
{{/with}}
{{#if App.current_user}}
<a {{action sign_out target="App" href=true}} {{bindAttr class=":sign_out"}}>Sign Out</a>
{{else}}
<p id="invitation">Please login with Twitter</p>
{{/if}}
</script>
</p>
<script type="text/x-handlebars">
{{#if App.current_user}}
{{view App.PostNote id="post_note" placeholder="Leave a note..."}}
{{/if}}
</script>
<script type="text/x-handlebars">
{{#if App.current_user}}
<div id="notes">
{{#each App.notes}}
<div class="note">
<p><strong>@{{screen_name}}</strong> left a note <span class="time_ago">{{time_ago created_at}}</span>...</p>
<blockquote>{{message}}</blockquote>
</div>
{{/each}}
</div>
{{/if}}
</script>
<!-- Twitter login button -->
<div id="login"></div>
<p id="source"><code>Source: <a href="http://gist.github.com/3369662">http://gist.github.com/3369662</a></code></p>
<!-- *************** LIBRARIES ************************************************************** -->
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script src="https://raw.github.com/timrwood/moment/1.7.0/min/moment.min.js"></script>
<script src="http://cloud.github.com/downloads/wycats/handlebars.js/handlebars-1.0.0.beta.6.js"></script>
<script src="http://cloud.github.com/downloads/emberjs/ember.js/ember-1.0.pre.min.js"></script>
<script src="ember-data.min.js"></script>
<script src="https://raw.github.com/karmi/ember-data-elasticsearch/master/ember-data/lib/adapters/elasticsearch_adapter.js"></script>
<!-- *************** APPLICATION ************************************************************ -->
<script>
// ========================
// Supporting code, patches
// ========================
// Handlerbars helper for Moment.js
//
Handlebars.registerHelper('time_ago', function(property, options) {
var value = Ember.Handlebars.getPath(this, property, options);
return value ? new Handlebars.SafeString(moment(value).fromNow()) : '';
});
// Redefine the adapter's `find()` method to load last 100 notes
//
DS.ElasticSearchAdapter.reopen({
findAll: function(store, type) {
var url = [this.url, type.url, '_search'].join('/');
var payload = {size: 100, sort: { created_at: 'desc' }};
this.http.post(url, payload, function(data, textStatus, xhr) {
store.loadMany(type, data['hits']['hits'].map( function(i) {
return Ember.Object.create(i['_source']).reopen({id: i._id, version: i._version})
} ));
});
}
});
// ========================
// The Ember.js application
// ========================
var App = Ember.Application.create({
current_user: null,
elasticsearch_url: window.location.protocol + "//" + window.location.host,
// Sign out of Twittter
//
sign_out: function() { window.twttr.anywhere.signOut() },
// Application intitializer
//
ready: function() {
var self = this;
window.twttr.anywhere(function (T) {
// Hook up Twitter login button
T("#login").connectButton({size: 'medium'});
T("#notes").hovercards();
// Set `current_user` if already logged in
//
if ( T.isConnected() ) {
console.log("Logged in as:", T.currentUser.screenName, T.currentUser)
self.set("current_user", T.currentUser.attributes)
App.notes.set("content", App.Note.find());
}
// Listeners for login/logout events
//
T.bind("authComplete", function (e, user) {
console.log("Authenticated as:", user.screenName, user)
self.set("current_user", user.attributes);
App.notes.set("content", App.Note.find());
})
T.bind("signOut", function (e) {
self.set("current_user", null);
});
});
return this._super();
}
});
// Create store in elasticsearch
//
App.store = DS.Store.create({
revision: 4,
adapter: DS.ElasticSearchAdapter.create({url: App.elasticsearch_url})
});
// The `Note` model
//
App.Note = DS.Model.extend({
screen_name: DS.attr('string'),
message: DS.attr('string'),
created_at: DS.attr('string')
});
App.Note.reopenClass({
url: 'notes/note'
});
// The `notes` collection
//
App.notes = Ember.ArrayController.create({
// Show last notes first
//
sortProperties: ['created_at'],
sortAscending: false,
// Hook up Ember/Handlebars binding for views
//
content: [],
// Create new note in collection and storage
//
createNote: function(value) {
var note = App.Note.createRecord({ screen_name: App.current_user.screen_name,
message: value,
created_at: (new Date().toJSON()) });
App.store.commit();
}
});
// Note input form
//
App.PostNote = Ember.TextField.extend({
insertNewline: function(event) {
var value = this.get('value');
if (value) {
App.notes.createNote(value);
this.set('value', '');
}
}
});
</script>
</body>
</html>
web: bundle exec ruby proxy.rb --stdout --verbose --environment production -p $PORT
#!/usr/bin/env ruby
# A Goliath-based [https://github.com/postrank-labs/goliath] web server and proxy.
#
# Serves a simple JavaScript application on root (see `index.html`).
#
# It validates backend requests with Twitter @Anywhere login:
#
# <https://dev.twitter.com/docs/anywhere/welcome#login-signup>
#
# Run the server with:
#
# $ PROXY_BASE_URL=http://localhost:9200 TWITTER_CONSUMER_SECRET=<REPLACE_ME> ruby proxy.rb --stdout --verbose
#
# On Heroku, configure backend URL and the Twitter consumer secret with environment variables:
#
# $ heroku config:add PROXY_BASE_URL=http://user:password@elasticsearch.myserver.com
# $ heroku config:add TWITTER_CONSUMER_SECRET=<REPLACE_ME>
#
require 'goliath'
require 'em-synchrony/em-http'
require 'oj'
require 'digest/sha1'
require 'uri'
class TwitterAuthProxy < Goliath::API
use Goliath::Rack::DefaultMimeType
use Goliath::Rack::JSONP
use Goliath::Rack::Params
PROXY_BASE_URL = ENV['PROXY_BASE_URL'] || "http://localhost:9200"
TWITTER_CONSUMER_SECRET = ENV['TWITTER_CONSUMER_SECRET'] || "INVALID"
HTML_APP = File.read File.expand_path('../index.html', __FILE__)
EMBER_DATA = File.read File.expand_path('../ember-data.min.js', __FILE__)
HEADERS = {}
# Get HTTP Auth credentials from URI
HTTP_AUTH_HEADER = (userinfo = URI.parse(PROXY_BASE_URL).userinfo) ? { authorization: userinfo.split(':') } : {}
def on_headers(env, headers)
# Store request headers
#
env.logger.debug 'Received headers: ' + headers.inspect
env['headers'] = headers
end
def response(env)
# 1. Serve the HTML application on root
#
html_app = Goliath.env?(:production) ? HTML_APP : File.read(File.expand_path('../index.html', __FILE__))
return [ 200, {}, html_app ] if env['REQUEST_PATH'] == '/'
return [ 200, {}, EMBER_DATA ] if env['REQUEST_PATH'] == '/ember-data.min.js'
# - Handle favicons
#
return [ 200, {}, "" ] if env['REQUEST_PATH'] == '/favicon.ico'
# 2. Make the game harder for script kiddies
#
if %w| put delete |.include?(env['REQUEST_METHOD'].downcase)
logger.info "Incorrect request."
return [ 401, {'Content-Type' => 'application/json'}, '{"error":"What are you trying to do?"}' ]
end
# 3. Return 401 Forbidden unless logged in via Twitter
#
unless valid_cookie?(env['HTTP_COOKIE'])
logger.info "Invalid Twitter @Anywhere Identity: #{env['HTTP_COOKIE']}"
return [ 401, {'Content-Type' => 'application/json'}, '{"error":"Please login with Twitter."}' ]
end
# - Get URL and JSON payload
#
url = "#{PROXY_BASE_URL}#{env['REQUEST_PATH']}?#{env['QUERY_STRING']}"
#
# FIX: Work around the fact that elasticsearch does not like `Content-Type` header
# when the request is sent via Ajax (Access-Control-Allow-Headers).
# See <https://github.com/elasticsearch/elasticsearch/issues/2186>
#
if ['POST', 'PUT'].include? env['REQUEST_METHOD'] and ( !env['params'].keys.empty? && env['params'].values.compact.empty? )
logger.info "Fixing incorrect JSON payload: #{env['params'].inspect}"
payload = env['params'].keys.first
else
payload = Oj.dump(env['params'])
end
logger.info "curl -X #{env['REQUEST_METHOD']} '#{url}pretty' -d '#{payload}'"
# 4. Perform the request on backend
#
headers = HEADERS.merge(env['headers']).merge(HTTP_AUTH_HEADER)
http = EM::HttpRequest.new(url).send( env['REQUEST_METHOD'].downcase.to_sym, { body: payload, head: headers } )
logger.info "Received response [#{http.response_header.status}] from backend"
# 5. Return backend response to client
#
[ 200, {'Content-Type' => http.response_header['CONTENT_TYPE']}, http.response ]
end
# Validates the cookie based on shared secret with Twitter:
#
# Digest::SHA1.hexdigest(user_id + consumer_secret)
#
# See https://dev.twitter.com/docs/anywhere/welcome#current-user
#
def valid_cookie?(cookie)
return false unless cookie
cookie_value = cookie.to_s.gsub(/twitter_anywhere_identity=/, '')
cookie_user_id, cookie_fingerprint = cookie_value.split(':')
return cookie_fingerprint == Digest::SHA1.hexdigest( [cookie_user_id, TWITTER_CONSUMER_SECRET].join('') )
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment