-
-
Save benolee/5381730 to your computer and use it in GitHub Desktop.
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
.DS_Store | |
tmp/ |
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(){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(){}(); |
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
source :rubygems | |
gem 'goliath' | |
gem 'em-http-request' | |
gem 'oj' |
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
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 |
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Notes (Ember.js & 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&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> |
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
web: bundle exec ruby proxy.rb --stdout --verbose --environment production -p $PORT |
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
#!/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:[email protected] | |
# $ 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