diff --git a/README.md b/README.md index 4986ace..f58808d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,32 @@ pattern persistence methods. ## Installation: +### Download + +#### With Bower + +If you have bower install just run + +```javascript +bower install ngActiveResource --save-dev +``` + +#### Manually + +To manually download head over to the [latest release](https://github.com/FacultyCreative/ngActiveResource/releases/latest) and hit the big 'source code' button. + +### Install + +Once downloaded you'll find everything you need in the `/dist` directory, include either file and add it as a dependency in your module. + +```javascript +angular.module('app', ['ngActiveResource']); +``` + +> Note: Don't forget to include lodash.js before active-resource.js. + +## Installation: + In your bower.json: "ngActiveResource": "0.7.0" diff --git a/bower.json b/bower.json index cdc4f2d..59bc256 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "ActiveResource", - "version": "0.7.1", + "version": "0.7.4", "authors": [ "Brett Shollenberger " ], @@ -14,7 +14,7 @@ "tests" ], "dependencies": { - "active-support": "0.0.7", + "active-support": "latest", "angular": "latest", "lodash": "latest", "async": "~0.2.5", diff --git a/dist/ng-active-resource.js b/dist/ng-active-resource.js index 3339cf1..cf7a94d 100644 --- a/dist/ng-active-resource.js +++ b/dist/ng-active-resource.js @@ -517,7 +517,6 @@ angular.module('ActiveResource').provider('ARAPI', function () { this.deleteURL = ''; this.updateURL = ''; this.set = function (url) { - console.log('The API#set method has recently changed its defaults. Please see https://github.com/FacultyCreative/ngActiveResource/pull/19 for details'); if (url.slice(-1) != '/') url = url + '/'; this.createURL = url + plural; @@ -1228,12 +1227,6 @@ angular.module('ActiveResource').provider('ARGET', function () { return truth; } ; - function appendSlashForQueryString(url) { - if (url.slice(-1) == '/') - return url; - return url + '/'; - } - ; return function generateGET(instance, url, terms, options) { var instanceAndTerms = transformSearchTermsToForeignKeys(instance, terms); var associatedInstance, terms, propertyName; @@ -1246,10 +1239,9 @@ angular.module('ActiveResource').provider('ARGET', function () { if (queryableByParams(url, terms)) { url = URLify(url, terms); } else if (Object.keys(terms).length) { - url = url.replace(/\:\w+/, ''); + url = url.replace(/\/\:\w+/, '').replace(/\:\w+/g, ''); config.params = terms; } - url = appendSlashForQueryString(url); return $http.get(url, config).then(function (response) { var data = response.data; if (propertyName && associatedInstance) { @@ -1287,7 +1279,8 @@ angular.module('ActiveResource').provider('ARBase', function () { 'ARGET', 'ARMixin', 'URLify', - function (API, Collection, Association, Associations, Cache, Serializer, Eventable, Validations, $http, $q, $injector, deferred, GET, mixin, URLify) { + 'ARHelpers', + function (API, Collection, Association, Associations, Cache, Serializer, Eventable, Validations, $http, $q, $injector, deferred, GET, mixin, URLify, Helpers) { function Base() { var _this = this; _this.watchedCollections = []; @@ -1777,7 +1770,6 @@ angular.module('ActiveResource').provider('ARBase', function () { } else { config = { params: queryterms }; } - url += '/'; return $http.delete(url, config).then(function (response) { if (response.status == 200) { removeFromWatchedCollections(instance); @@ -1867,22 +1859,34 @@ angular.module('ActiveResource').provider('ARBase', function () { this[name] = collection; } ; - function removeOldHasManyInstances(hasManyCollection, newHasManyObjects) { - _.each(hasManyCollection, function (hasManyInstance) { - var found = _.where(newHasManyObjects, function (newHasManyObject) { - return hasManyInstance && hasManyInstance[primaryKey] == newHasManyObject[primaryKey]; - }); - if (!found.length) - _.remove(hasManyCollection, hasManyInstance); + function removeOldHasManyInstances(hasManyCollection, newHasManyObjects, primaryKeyName, primaryKeys) { + _.remove(hasManyCollection, function (hasManyInstance) { + var keep = _.include(primaryKeys, hasManyInstance[primaryKeyName]); + if (keep == false) { + delete hasManyInstance.constructor.cached[hasManyInstance[primaryKeyName]]; + return true; + } }); } - function createOrUpdateHasManyInstances(hasManyCollection, newHasManyObjects) { + function createOrUpdateHasManyInstances(hasManyCollection, newHasManyObjects, primaryKeyName, primaryKeys) { + var _first, _cons, _cached; + _first = _.first(hasManyCollection); + if (_first !== undefined) + _cons = _first.constructor; + if (_cons !== undefined) + _cached = _cons.cached; _.each(newHasManyObjects, function (hasManyInstanceAttrs) { - var instance = _.where(hasManyCollection, hasManyInstanceAttrs); - if (!instance.length) - hasManyCollection.new(hasManyInstanceAttrs); - else + var instance; + if (_cached !== undefined) { + var search = {}; + search[primaryKeyName] = hasManyInstanceAttrs[primaryKeyName]; + instance = _cached.where(search); + } + if (instance && instance.length) { instance[0].update(hasManyInstanceAttrs); + } else { + hasManyCollection.new(hasManyInstanceAttrs); + } }, this); } // Receives an array of new plain-old Javascript objects, and the name of a collection @@ -1900,8 +1904,16 @@ angular.module('ActiveResource').provider('ARBase', function () { // collection#new. function updateHasManyRelationship(newCollectionPOJOs, collectionName) { var hasManyCollection = this[collectionName.camelize()]; - removeOldHasManyInstances(hasManyCollection, newCollectionPOJOs); - createOrUpdateHasManyInstances(hasManyCollection, newCollectionPOJOs); + var _first = _.first(hasManyCollection); + if (!_first) { + return createOrUpdateHasManyInstances(hasManyCollection, newCollectionPOJOs); + } + var primaryKeyName = Helpers.getPrimaryKeyFor(_first); + var primaryKeys = _.chain(newCollectionPOJOs).map(function (o) { + return o[primaryKeyName]; + }).compact().unique().value(); + removeOldHasManyInstances(hasManyCollection, newCollectionPOJOs, primaryKeyName, primaryKeys); + createOrUpdateHasManyInstances(hasManyCollection, newCollectionPOJOs, primaryKeyName, primaryKeys); } ; function updateHasOneRelationship(association, name) { diff --git a/dist/ng-active-resource.min.js b/dist/ng-active-resource.min.js index 1860b19..a9580d5 100644 --- a/dist/ng-active-resource.min.js +++ b/dist/ng-active-resource.min.js @@ -1 +1 @@ -angular.module("dojo",[]).provider("stamp",function(){this.$get=function(){var a={};return a.fromISOString=function(b,c){a._isoRegExp||(a._isoRegExp=/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/);var d=a._isoRegExp.exec(b),e=null;if(d){d.shift(),d[1]&&d[1]--,d[6]&&(d[6]*=1e3),c&&(c=new Date(c),array.forEach(array.map(["FullYear","Month","Date","Hours","Minutes","Seconds","Milliseconds"],function(a){return c["get"+a]()}),function(a,b){d[b]=d[b]||a})),e=new Date(d[0]||1970,d[1]||0,d[2]||1,d[3]||0,d[4]||0,d[5]||0,d[6]||0),d[0]<100&&e.setFullYear(d[0]||1970);var f=0,g=d[7]&&d[7].charAt(0);"Z"!=g&&(f=60*(d[8]||0)+(Number(d[9])||0),"-"!=g&&(f*=-1)),g&&(f-=e.getTimezoneOffset()),f&&e.setTime(e.getTime()+6e4*f)}return e},a.toISOString=function(a,b){var c=function(a){return 10>a?"0"+a:a};b=b||{};var d=[],e=b.zulu?"getUTC":"get",f="";if("time"!=b.selector){var g=a[e+"FullYear"]();f=["0000".substr((g+"").length)+g,c(a[e+"Month"]()+1),c(a[e+"Date"]())].join("-")}if(d.push(f),"date"!=b.selector){var h=[c(a[e+"Hours"]()),c(a[e+"Minutes"]()),c(a[e+"Seconds"]())].join(":"),i=a[e+"Milliseconds"]();if(b.milliseconds&&(h+="."+(100>i?"0":"")+c(i)),b.zulu)h+="Z";else if("time"!=b.selector){var j=a.getTimezoneOffset(),k=Math.abs(j);h+=(j>0?"-":"+")+c(Math.floor(k/60))+":"+c(k%60)}d.push(h)}return d.join("T")},a}}).provider("json",function(){this.$get=["stamp",function(stamp){djson={},djson.fromJson=function(js){return eval("("+js+")")},djson._escapeString=JSON.stringify,djson.toJsonIndentStr=" ",djson.toJson=function(a,b){return JSON.stringify(a,function(a,b){if(b){var c=b.__json__||b.json;if("function"==typeof c)return c.call(b)}return b},b&&djson.toJsonIndentStr)};var json={};return json.resolveJson=function(a,b){function c(p,q,r,s,t,u){var v,w,x,y=e in p?p[e]:r;(e in p||void 0!==y&&s)&&(y=(h+y).replace(m,"$2$3"));var z=u||p;if(void 0!==y){if(i&&(p.__id=y),!b.schemas||p instanceof Array||!(x=y.match(/^(.+\/)[^\.\[]*$/))||(t=b.schemas[x[1]]),j[y]&&p instanceof Array==j[y]instanceof Array)z=j[y],delete z.$ref,delete z._loadObject,w=!0;else{var A=t&&t.prototype;A&&(o.prototype=A,z=new o)}j[y]=z,k&&(k[y]=b.time)}for(;t;){var B=t.properties;if(B)for(v in p){var C=B[v];C&&"date-time"==C.format&&"string"==typeof p[v]&&(p[v]=stamp.fromISOString(p[v]))}t=t["extends"]}var D=p.length;for(v in p){if(v==D)break;if(p.hasOwnProperty(v)){if(x=p[v],"object"==typeof x&&x&&!(x instanceof Date)&&"__parent"!=v)if(d=x[f]||g&&x[e],d&&x.__parent||p!=l&&(x.__parent=z),d){delete p[v];var E=d.toString().replace(/(#)([^\.\[])/,"$1.$2").match(/(^([^\[]*\/)?[^#\.\[]*)#?([\.\[].*)?/);if(j[(h+d).replace(m,"$2$3")]?d=j[(h+d).replace(m,"$2$3")]:(d="$"==E[1]||"this"==E[1]||""==E[1]?a:j[(h+E[1]).replace(m,"$2$3")])&&E[3]&&E[3].replace(/(\[([^\]]+)\])|(\.?([^\.\[]+))/g,function(a,b,c,e,f){d=d&&d[c?c.replace(/[\"\'\\]/,""):f]}),d)x=d;else if(!q){var F;F||l.push(z),F=!0,x=c(x,!1,x[f],!0,C),x._loadObject=b.loader}}else q||(x=c(x,l==p,void 0===y?void 0:n(y,v),!1,C,z!=p&&"object"==typeof z[v]&&z[v]));if(p[v]=x,z!=p&&!z.__isDirty){var G=z[v];z[v]=x,!w||x===G||z._loadObject||"_"==v.charAt(0)&&"_"==v.charAt(1)||"$ref"==v||x instanceof Date&&G instanceof Date&&x.getTime()==G.getTime()||"function"==typeof x&&"function"==typeof G&&x.toString()==G.toString()||!j.onUpdate||j.onUpdate(z,v,G,x)}}}if(w&&(e in p||z instanceof Array)){for(v in z)if(!(z.__isDirty||!z.hasOwnProperty(v)||p.hasOwnProperty(v)||"_"==v.charAt(0)&&"_"==v.charAt(1)||z instanceof Array&&isNaN(v)))for(j.onUpdate&&"_loadObject"!=v&&"_idAttr"!=v&&j.onUpdate(z,v,z[v],void 0),delete z[v];z instanceof Array&&z.length&&void 0===z[z.length-1];)z.length--}else j.onLoad&&j.onLoad(z);return z}b=b||{};var d,e=b.idAttribute||"id",f=this.refAttribute,g=b.idAsRef,h=b.idPrefix||"",i=b.assignAbsoluteIds,j=b.index||{},k=b.timeStamps,l=[],m=/^(.*\/)?(\w+:\/\/)|[^\/\.]+\/\.\.\/|^.*\/(\/)/,n=this._addProp,o=function(){};return a&&"object"==typeof a&&(a=c(a,!1,b.defaultId,!0),c(l,!1)),a},json.fromJson=function(str,args){function ref(a){var b={};return b[this.refAttribute]=a,b}try{var root=eval("("+str+")")}catch(e){throw new SyntaxError("Invalid JSON string: "+e.message+" parsing: "+str)}return root?this.resolveJson(root,args):root},json.toJson=function(a,b,c,d,e){function f(a,d,l){if("object"==typeof a&&a){if(a instanceof Date)return'"'+stamp.toISOString(a,{zulu:!0})+'"';var m=a.__id;if(m){if("#"!=d&&(g&&!m.match(/#/)||j[m])){var n=m;"#"!=m.charAt(0)&&(n=a.__clientId==m?"cid:"+m:m.substring(0,c.length)==c?m.substring(c.length):m);var o={};return o[i]=n,djson.toJson(o,b)}d=m}else a.__id=d,k[d]=a;j[d]=a,l=l||"";var p=b?l+djson.toJsonIndentStr:"",q=b?"\n":"",r=b?" ":"";if(a instanceof Array){var s=_.map(a,function(a,b){var c=f(a,h(d,b),p);return"string"!=typeof c&&(c="undefined"),q+p+c});return"["+s.join(","+r)+q+l+"]"}var t=[];for(var u in a)if(a.hasOwnProperty(u)){var v;if("number"==typeof u)v='"'+u+'"';else{if("string"!=typeof u||"_"==u.charAt(0)&&"_"==u.charAt(1))continue;v=djson._escapeString(u)}var w=f(a[u],h(d,u),p);if(void 0===w&&e.includeEmptyKeys===!0&&(w='" "'),"string"!=typeof w)continue;t.push(q+p+v+":"+r+w)}return"{"+t.join(","+r)+q+l+"}"}return djson.toJson(a)}e||(e={}),e.instance&&(a=e.instance);var g=this._useRefs,h=this._addProp,i=this.refAttribute;c=c||"";var j={},k={},l=f(a,"#","");if(!d)for(var m in k)delete k[m].__id;return l},json._addProp=function(a,b){return a+(a.match(/#/)?1==a.length?"":".":"#")+b},json.refAttribute="$ref",json._useRefs=!1,json.serializeFunctions=!1,json.serialize=function(a,b){var c;return b||(b={}),b.prettyPrint&&(c=!0),json.toJson(a,c,"",!1,b)},json}]}),Function.prototype.inherits=function(a){var b;return b=this,b=a.apply(b)},angular.module("ActiveResource",["ng","dojo"]).provider("ActiveResource",function(){this.$get=["ARBase",function(a){return ActiveResource={},ActiveResource.Base=a,ActiveResource}]}),angular.module("ActiveResource").provider("ARAPI",function(){this.$get=["ARHelpers","Mime",function(a,b){function c(a,c){var d,e=a.name.hyphenate(),f=e.toLowerCase(),g=f.pluralize(),h=c||"id";this.indexURL="",this.createURL="",this.showURL="",this.deleteURL="",this.updateURL="",this.set=function(a){return console.log("The API#set method has recently changed its defaults. Please see https://github.com/FacultyCreative/ngActiveResource/pull/19 for details"),"/"!=a.slice(-1)&&(a+="/"),this.createURL=a+g,this.updateURL=this.showURL=this.deleteURL=a+g+"/:"+h,this.indexURL=a+g,this},this.updatePrimaryKey=function(a){return h=a,this.updateURL=this.updateURL,this},this.format=function(a){b.types.register(a),a.match(/\.\w+/)||(a="."+a),d=a;for(var c in this)c.match(/URL/)&&(_.each(b.types,function(a){var b=new RegExp("."+a);this[c]=this[c].replace(b,"")},this),this[c]+=d)}}return c}]}),angular.module("ActiveResource").provider("ARAssociation",function(){this.$get=["$injector","ARBelongsTo",function(a,b){function c(d,e,f,g){if(g.provider)var h=g.provider;else var h=f.classify();"BelongsTo"==d&&(this.foreignKey=g.foreignKey?g.foreignKey:f.singularize()+"_id",b(this,e,f,g)),this.klass=a.get(h),this.propertyName=f,this.type=d,c.cached[e.constructor.name.toLowerCase()+":"+f]=this}return c.cached={},c}]}).provider("ARBelongsTo",function(){this.$get=["ARAssociations","ARHelpers",function(a,b){function c(c,d,e){var f=void 0;Object.defineProperty(d,e,{enumerable:!0,get:function(){return f},set:function(e){if(void 0===e)return void(f=e);var g=b.getPrimaryKeyFor(d);if("String"==e.constructor.name||"Number"==e.constructor.name)c.klass.find(e).then(function(a){f=a;var b=d.constructor.name.pluralize().camelize(),c=f[b];d[g]&&c&&!_.include(c,d)&&c.push(d)});else if(c.klass.name==e.constructor.name){f=e;var h,i,j=a.get(e);_.each([j.hasMany,j.hasOne],function(a){_.each(a,function(a){a.klass==d.constructor&&(h=a,i=a.klass.name.camelize(),"hasMany"==h.type&&(i=i.pluralize()))})});var k=f[i];void 0!==d[g]&&k&&("hasMany"==h.type?_.include(k,d)||k.push(d):"hasOne"==h.type&&Object.defineProperty(f,i,{enumerable:!0,configurable:!0,value:d}))}}})}return c}]}).provider("ARAssociations",function(){this.$get=["$q",function(){function a(b){function c(b){var c=!0;_.each(this,function(a){a.klass==b.klass&&(c=!1)}),c&&this.push(b),"hasOne"==b.type&&(b.klass.prototype.find=function(){var c=a.get(b.klass).belongsTo,d={};return _.each(c,function(a){d[a.propertyName]=this[a.propertyName]},this),b.klass.find(d)})}var d=b.name.toLowerCase();this.belongsTo=[],this.hasMany=[],this.hasOne=[],this.belongsTo.add=c,this.hasMany.add=c,this.hasOne.add=c,a.cached[d]||(a.cached[d]=this)}return a.cached={},a.get=function(b){return model=b.klass?b.klass:b,"Function"!==model.constructor.name?model=model.constructor:model.new(),a.cached[model.name.toLowerCase()]},a.getBelongs=function(b,c){var d=a.get(b),e=void 0;return _.each(d.belongsTo,function(a){a.klass==c.constructor&&(e=a)}),e},a.getDependents=function(b,c){var d=a.getBelongs(b,c);return d},a}]}).provider("ARCollection",function(){this.$get=function(){function a(a,b){var c=b.name.toLowerCase(),d=[];return Object.defineProperty(d,"new",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.new(b)}}),Object.defineProperty(d,"$create",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.$create(b)}}),Object.defineProperty(d,"where",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.where(b)}}),Object.defineProperty(d,"find",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.find(b)}}),Object.defineProperty(d,"all",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.where(b)}}),Object.defineProperty(d,"first",{enumerable:!1,get:function(){return d[0]}}),Object.defineProperty(d,"last",{enumerable:!1,get:function(){return d.slice(-1)[0]}}),d}return a}}),String.prototype.downcase=function(){return this.toLowerCase()},angular.module("ActiveResource").provider("ARCache",function(){this.$get=function(){function a(){Object.defineProperty(this,"cache",{enumerable:!1,value:function(a,b){a&&void 0!==a[b]&&(a.constructor.cached[a[b]]=a)}}),Object.defineProperty(this,"isEmpty",{enumerable:!1,value:function(){return!Object.keys(this).length}}),Object.defineProperty(this,"where",{enumerable:!1,value:function(a){return 0==Object.keys(a).length&&(a=void 0),_.where(this,a,this)}})}return a}}),angular.module("ActiveResource").provider("ARSerializer",function(){this.$get=["json","ARMixin","ARAssociations","ARHelpers","ARDeferred",function(a,b,c,d,e){function f(){function f(a){var e=b({},a,!1),f=c.get(a);return _.each(f.belongsTo,function(b){var c=b.foreignKey,f=d.getClassNameFor(b),g=a[f];if(g){var h=d.getPrimaryKeyFor(g),i=g[h];e[c]=i,e[f]=void 0}}),e}function i(a,b){var d=!1,e=c.get(b);return _.each(e.belongsTo,function(b){var c=b.foreignKey;(a[c]||a==c)&&(d=!0)}),d}this.serialize=function(b,c){var d=f(b);return a.serialize(d,c)},this.deserialize=function(a,b,c){var d,c;return d=a&&a.data?a.data:a,c||(c={lazy:!0}),i(d,b)?h(b,d,c):g(b,d,c).then(function(a){return b=a,e(b)})}}function g(a,b,c){function f(){return a.establishBelongsTo(),e(a)}if(c&&0==c.update)return e(a);a.update(b);var g=d.getPrimaryKeyFor(a);return a.constructor.cached.cache(a,g),a.validations.updateInstance(a),c.lazy?f():i(a).then(f)}function h(a,b,d){d&&0==d.update&&(d.update=!0);var f=[],h=c.get(a);return h.belongsTo.length>=1&&_.each(h.belongsTo,function(a){if(b[a.foreignKey]){var c=a.klass,e=c.name.camelize(),g=a.foreignKey,h=b[g],i={};for(var j in d)i[j]=d[j];d.overEager||(i.lazy=!0),f.push(function(c){a.klass.find(h,i).then(function(a){b[e]=a,delete b[g],c(null,b)})})}}),async.series(f,function(b,c){c=_.first(c),g(a,c,d)}),e(a)}function i(a){var b=[],f=c.get(a),g=[f.hasMany,f.hasOne];return _.each(g,function(e){_.each(e,function(e){var g=c.getDependents(e,a),h=g.foreignKey,i={},j=d.getPrimaryKeyFor(a);i[h]=a[j],b.push(function(b){e.klass.where(i,{lazy:!0}).then(function(c){_.each(c,function(c){if(_.include(f.hasMany,e)){var d=e.klass.name.pluralize().camelize();a[d].nodupush(c)}else{var d=e.klass.name.singularize().camelize();a[d]||(a[d]=c)}b(null,a)})})})})}),async.series(b,function(){}),e(a)}return f}]}),angular.module("ActiveResource").provider("ARHelpers",function(){this.$get=function(){return Object.defineProperty(Array.prototype,"nodupush",{enumerable:!1,configurable:!0,value:function(a){_.include(this,a)||this.push(a)}}),{getClassNameFor:function(a){return a.klass.name.camelize()},getPrimaryKeyFor:function(a){return"Function"==a.constructor?a.primaryKey:a.constructor.primaryKey}}}}),angular.module("ActiveResource").provider("ARMixin",function(){this.$get=function(){return function(a,b,c){function d(){a.hasOwnProperty(e)||(!function(){var b;Object.defineProperty(a,e,{enumerable:!0,get:function(){return b},set:function(a){b=a}})}(),a[e]=b[e])}"Function"==b.constructor.name&&(b=new b);for(var e in b)c?"function"!=typeof b[e]&&d():d();return a}}}),angular.module("ActiveResource").provider("ARDeferred",function(){this.$get=["$q",function(a){return function b(c,d){var b=a.defer();return d?b.reject(d):b.resolve(c),b.promise}}]}),angular.module("ActiveResource").provider("ARQuerystring",function(){this.$get=function(){var a={stringify:function(a){var b="";return _.map(a,function(a,c){b+=0==b.length?c+"="+a:"&"+c+"="+a}),b},parse:function(){}};return a}}),angular.module("ActiveResource").provider("ARParameterize",function(){this.$get=function(){return function(a,b){return a?b?a.replace(/\:\_*[A-Za-z]+/g,function(a){return a=a.replace(/\:*/,""),b[a]}):a:void 0}}}),angular.module("ActiveResource").provider("URLify",function(){this.$get=["ARParameterize","ARQuerystring",function(a,b){return function(c,d){if(c){if(!d)return c;var e="";return b.stringify(d)&&(e="?"+b.stringify(d)),c.match(/\[\:[A-Za-z]+\]/)?c=c.replace(/\[\:[A-Za-z]+\]/,e):c.match(/\:\_*[A-Za-z]+/)&&(c=a(c,d)),c}}}]}),angular.module("ActiveResource").provider("ARGET",function(){this.$get=["$http","ARDeferred","ARAssociations","ARHelpers","URLify",function(a,b,c,d,e){function f(a,b,c){return a&&a.length>=1?_.first(c.noInstanceEndpoint?_.where(a,b):a):a}function g(a){return a}function h(a,b){var e,f,g=c.get(a);return g?(g=g.belongsTo,_.each(g,function(a){if(b[a.propertyName]){e=b[a.propertyName],f=a.propertyName;var c=a.foreignKey,g=d.getPrimaryKeyFor(b[a.propertyName]);b[c]=e[g],b[c]&&delete b[a.propertyName]}}),[e,b,f]):void 0}function i(a){var b=[];return a.replace(/\:[a-zA-Z_]+/g,function(a){b.push(a)}),b=_.map(b,function(a){return a.slice(1)})}function j(a,b){var c=i(a),d=!0;return _.each(c,function(a){void 0===b[a]&&(d=!1)}),_.each(b,function(a,b){_.include(c,b)||(d=!1)}),d}function k(a){return"/"==a.slice(-1)?a:a+"/"}return function(b,c,d,i){var l,d,m,n=h(b,d);n&&(l=n[0],d=n[1],m=n[2]);var o={};return j(c,d)?c=e(c,d):Object.keys(d).length&&(c=c.replace(/\:\w+/,""),o.params=d),c=k(c),a.get(c,o).then(function(a){var b=a.data;return m&&l&&(b&&b.push?_.each(b,function(a){a[m]=l}):b[m]=l),i.multi?g(b,d,i):f(b,d,i)})}}]}),angular.module("ActiveResource").provider("ARBase",function(){this.$get=["ARAPI","ARCollection","ARAssociation","ARAssociations","ARCache","ARSerializer","AREventable","ARValidations","$http","$q","$injector","ARDeferred","ARGET","ARMixin","URLify",function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){function p(){function k(a){M.cached.cache(a,N)}function p(a){_.each(M.watchedCollections,function(b){_.remove(b,a)})}function q(a,b){var c=r(b);return!(!a[c]||a[c].constructor!=b)}function r(a){return a&&(a.klass||a.name)?a.klass?a.klass.name.camelize():a.name.camelize():void 0}function s(a){return _.include(E(M),a)}function t(a){return _.include(B(),a.camelize())}function u(a){return _.include(C(),a.camelize())}function v(a){return _.include(D(),a.camelize())}function w(a,b){("Number"==a.constructor.name||"String"==a.constructor.name)&&(a=void 0),a&&(this[b]=a)}function x(a,b){_.each(a,function(c){var d=_.where(b,function(a){return c&&c[N]==a[N]});d.length||_.remove(a,c)})}function y(a,b){_.each(b,function(b){var c=_.where(a,b);c.length?c[0].update(b):a.new(b)},this)}function z(a,b){var c=this[b.camelize()];x(c,a),y(c,a)}function A(a,b){_.each(O.hasOne,function(c){if(c.klass.name==b.classify()){Object.defineProperty(this,b,{enumerable:!0,configurable:!0,value:c.klass.new(a)});var e=d.cached[c.klass.name.downcase()].belongsTo;_.each(e,function(a){a.klass==this.constructor&&(this[b][a.propertyName]=this)},this)}},this)}function B(){return _.map(O.belongsTo,function(a){return a.klass.name.camelize()})}function C(){return _.map(O.hasMany,function(a){return a.klass.name.camelize().pluralize()})}function D(){return _.map(O.hasOne,function(a){return a.klass.name.camelize()})}function E(a){var b=a.new(),c=Object.getOwnPropertyNames(b),d=[];for(var e in b)d.nodupush(e);_.each(c,function(a){d.nodupush(a)}),d.nodupush(N);var f=["establishBelongsTo","$save","update","$delete","associations","primaryKey","hasMany","hasOne","belongsTo","validate","$valid","$invalid","$errors","validations","validates"];return _.remove(d,function(a){return _.include(f,a)}),d}function F(a){void 0===a.validations&&a.validates({}),a.validate=function(b){return a.validations.validate(b,a)},a.validateIfErrored=function(b){a.$errors[b]&&a.validate(b,a)},Object.defineProperty(a,"$valid",{get:function(){return a.validate()}}),Object.defineProperty(a,"$invalid",{get:function(){return!a.$valid}}),Object.defineProperty(a,"$errors",{get:function(){return a.validations.$errors}}),a.clearErrors=function(b){a.validations.clearErrors(b)}}function G(a,b){a[N]=b[N]}function H(a,b,c){_.each(a[b],function(a){c[b].new(a)})}function I(a,b,c,e){var f=a[e.propertyName];if(f&&f.constructor.name==e.klass.name){var g=f,h=d.get(f).belongsTo;_.each(h,function(a){a.klass==c.constructor&&(f[a.propertyName]=c)})}else{var i,j=d.get(e).belongsTo;_.each(j,function(a){a.klass==c.constructor&&(i=a)}),a[b][i.propertyName]=c;var g=e.klass.new(a[b])}Object.defineProperty(c,b,{enumerable:!0,configurable:!0,value:g})}function J(a){_.each(P,function(b){var c=a[b];_.each(c,function(a){a.$delete()})}),_.defer(function(){K(a),delete M.cached[a[N]]})}function K(a){_.each(O.hasMany,function(b){var c=a[b.klass.name.pluralize().camelize()],d=a.constructor.name.camelize();_.each(c,function(a){a[d]=void 0})}),_.each(O.belongsTo,function(b){var c=a[b.klass.name.camelize()];if(c){var d=a.constructor.name.pluralize().camelize();_.remove(c[d],a)}})}function L(a){var b=a;return a={},a[N]=b,a}var M=this;M.watchedCollections=[];var N="id";Object.defineProperty(M,"primaryKey",{configurable:!0,get:function(){return N},set:function(a){N=a,this.api.updatePrimaryKey(N)}});var O=new d(M),P=[];this.api=new a(this),n(M,g),M.cached||(M.cached=new e),serializer=new f,M.prototype.toJSON=function(a){return M.prototype.serialize.call(this,a)},M.prototype.serialize=function(a){return serializer.serialize(this,a)},M.prototype.$save=function(a,b,c){a||(a=this),a&&a[N]&&!b?(b=o(M.api.updateURL,a),c!==!1&&(c=!0)):b||(b=M.api.createURL),M.emit("$save:called",a);var d=serializer.serialize(a);return a.$invalid?(M.emit("$save:fail",a),l(null,a)):(method=c?"put":"post",i[method](b,d).then(function(b){return serializer.deserialize(b,a).then(function(a){return M.emit("$save:complete",a),l(a)})}))},M.prototype.$update=function(a){function b(){return c.$save(c,d,"put")}var c=this,d=M.api.updateURL;return d?(d.match(/\:\w+/)&&(d=d.replace(/\:\w+/,this[N])),a?c.update(a).then(function(a){return c=a,b()}):b()):void 0},M.prototype.update=function(a){M.emit("update:called",{data:a,instance:this});for(var b in a)u(b)?z.call(this,a[b],b):v(b)?A.call(this,a[b],b):t(b)?w.call(this,a[b],b):s(b)&&(this[b]=a[b]);var c=this;return serializer.deserialize(a,c,{lazy:!0,update:!1}).then(function(){return M.emit("update:complete",{data:a,instance:c}),l(c)})},M.$create=function(a){a||(a={}),M.emit("$create:called",a);var b=M.new(a);return b.establishBelongsTo(),b.$save().then(function(a){return b=a,k(b),M.emit("$create:complete",b),l(b)})},M.new=function(a){if(a||(a={}),M.emit("new:called",a),"Number"==typeof a&&(a=L(a)),a&&this.cached[a[N]])return this.cached[a[N]];M.prototype.integer=function(b){var c=this,d={};d[b]={integer:{ignore:/\,/g}},c.validates(d),c[b]=a[b]},M.prototype.number=function(b){var c=this,d={};d[b]={numericality:{ignore:/\,/g}},c.validates(d),c[b]=a[b]},M.prototype.boolean=function(b){var c=this,d={};d[b]={"boolean":!0},c.validates(d),c[b]=a[b]},M.prototype.string=function(b){var c=this,d={};d[b]={string:!0},c.validates(d),c[b]=a[b]},M.prototype.computedProperty=function(b,c,d){var e=this;d||(d=[]),d.push||(d=[d]);var f;Object.defineProperty(e,b,{enumerable:!0,configurable:!0,get:function(){return f},set:function(){return f=c.apply(e)}}),_.each(d,function(c){var d,f=e.__lookupSetter__(c),g=e[c];Object.defineProperty(e,c,{enumerable:!0,configurable:!0,get:function(){return d},set:function(a){return void 0!==a&&"set"!=a&&(d=a),f&&(d==a?f():d=f()),e[b]="set",d}}),e[c]=a&&a[c]?a[c]:g})};var b=new this(a);return G(b,a),k(b),_.each(O.belongsTo,function(c){var d=r(c);a&&void 0!==a[d]&&(b[d]=a[d])}),_.each(O.hasMany,function(c){var d=c.klass.name.pluralize().camelize();b[d][this.name.camelize()]=b,void 0!==a[d]&&H(a,d,b)},this),_.each(O.hasOne,function(c){var d=c.propertyName;void 0!==a[c.propertyName]&&I(a,d,b,c)}),F(b),M.emit("new:complete",b),b},M.where=function(a,b){if(M.emit("where:called",a),"object"!=typeof a)throw"Argument to where must be an object";b||(b={lazy:!1,overEager:!1});var c=M.cached.where(a);b.cached=c,b.multi=!0;var d=M.api.indexURL||M.api.showURL;return m(M,d,a,b).then(function(a){var c=[];for(var d in a){var e=M.new(a[d]);c.push(e),serializer.deserialize(a[d],e,b)}return M.watchedCollections.push(c),M.emit("where:complete",{instance:c,data:a}),c})},M.all=function(a){return M.emit("all:called"),M.where({},a).then(function(a){var b=j.defer();return b.resolve(a),M.emit("all:complete",a),b.promise})},M.find=function(a,b){var c;if(M.emit("find:called",a),("number"==typeof a||"string"==typeof a)&&(a=L(a)),"object"!=typeof a)throw"Argument to find must be an object";b||(b={lazy:!1}),b.forceGET||(c=_.first(M.cached.where(a)));var d=M.api.showURL||M.api.indexURL;return void 0!==c?(M.emit("find:complete",c),l(c)):m(M,d,a,b).then(function(a){var c=M.new(a);return M.emit("find:complete",c),serializer.deserialize(a,c,b)})},M.prototype.$delete=function(){var a=this;M.emit("$delete:called",this);var b={},c={},d=M.api.deleteURL;return b[N]=a[N],-1!==M.api.deleteURL.indexOf("/:")?d=o(M.api.deleteURL,b):c={params:b},d+="/",i.delete(d,c).then(function(b){if(200==b.status){if(p(a),M.emit("$delete:complete",a),P.length>=1)return J(a);K(a),delete M.cached[a[N]]}})},M.prototype.establishBelongsTo=function(){if(O.belongsTo.length)for(var a in O.belongsTo){var b=O.belongsTo[a].klass,c=r(b);if(q(this,b)){var d=this[c][this.constructor.name.pluralize().camelize()];d&&d.push?d.nodupush(this):d=this}}};var Q;return Object.defineProperty(M.prototype,"validates",{enumerable:!1,value:function(a){Object.defineProperty(this,"validations",{enumerable:!1,configurable:!0,get:function(){return Q},set:function(a){void 0===Q&&(Q=new h(a,this)),Q.addValidations(a)}}),this.validations=a}}),M.prototype.hasMany=function(a,d){d||(d={});var e=new c("hasMany",this,a,d);O.hasMany.add(e),M[a.classify()]=e.klass,this[a]=new b(e.klass,M),this[a][M.name.camelize()]=this},M.prototype.hasOne=function(a,b){b||(b={});var e=new c("hasOne",this,a,b);O.hasOne.add(e),this[a]||Object.defineProperty(this,a,{enumerable:!1,configurable:!0,value:e.klass.new()});var f=d.getBelongs(e.klass,this);this[a][f.propertyName]=this},M.prototype.belongsTo=function(a,b){b||(b={});var d=new c("BelongsTo",this,a,b);O.belongsTo.add(d)},M.dependentDestroy=function(a){"Array"!=a.constructor.name&&(a=[a]);for(var b in a)P.push(a[b])},M}return p}]}),angular.module("ActiveResource").provider("AREventable",function(){this.$get=function(){function a(){function a(a,c){return a in b.handlers||(b.handlers[a]=[]),b.handlers[a].push(c),this}var b={handlers:{}};Object.defineProperty(this,"emit",{enumerable:!0,value:function(a){if(b.handlers[a]){for(var c=Array.prototype.slice.call(arguments,1),d=0,e=c.length;e>d;d++)b.handlers[a][d].apply(this,c);return b}}}),this.before=function(b,c){return a(b+":called",c)},this.after=function(b,c){return a(b+":complete",c)},this.fail=function(b,c){return a(b+":fail",c)}}return a}}),angular.module("ActiveResource").provider("Mime",function(){this.$get=[function(){return types=["json"],types.register=function(a){a=a.replace(/\./g,""),types.nodupush(a)},{types:types}}]}),angular.module("ActiveResource").provider("ARValidations",function(){this.$get=["$q",function(){function a(a,b){function c(){return function(a){return void 0===a||null===a?!1:"String"==a.constructor.name?!!(a&&a.length||"object"==typeof a):void 0!==a}}function d(a){return function(b,c,d){return a(b,c,d)===!0?!!(b&&b.length||"object"==typeof b):!0}}function e(){return function(a){return!a}}function f(){return function(a){return void 0===a||""===a||null===a?!0:/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/.test(a)}}function g(){return function(a){return void 0===a||""===a||null===a?!0:/(^\d{5}$)|(^\d{5}-{0,1}\d{4}$)/.test(a)}}function h(a){return function(b){return void 0===b||""===b||null===b?!0:a.test(b)}}function i(a){return function(b){if(void 0===b||""===b||null===b)return!0;if(!a.in)throw"Inclusion validator must specify 'in' attribute.";var c=!1;return a.in.forEach(function(a){a==b&&(c=!0)}),c}}function j(a){return function(b){if(void 0===b||""===b||null===b)return!0;if(!a.from)throw"Exclusion validator must specify 'from' attribute.";var c=!0;return a.from.forEach(function(a){a==b&&(c=!1)}),c}}function k(a){return function(b){return void 0===b||""===b||null==b?!0:b.length>=a[0]&&b.length<=a[a.length-1]}}function l(a){return function(b){return void 0===b||""===b||null===b?!0:b.length>=a}}function m(a){return function(b){return void 0===b||""===b||null===b?!0:b.length<=a}}function n(a){return function(b){return void 0===b||""===b||null===b?!0:String(b).length==a}}function o(){return function(a){return w(a)?!0:1==a}}function p(){return function(a,c){return confirmationName=c+"Confirmation",confirmationField=b[confirmationName],a==confirmationField}}function q(a){return function(b){return b?(b=String(b),a.ignore&&(b=b.replace(a.ignore,"")),!isNaN(Number(b))):!0}}function r(a){return function(b){return b?"Array"==b.constructor.name?!1:"Object"==b.constructor.name?!1:(b=String(b),b.match(/\./)?!1:(a.ignore&&(b=b.replace(a.ignore,"")),!isNaN(Number(b)))):!0}}function s(){return function(a){return void 0===a||""===a||null===a?!0:!(a!==!0&&a!==!1&&"true"!==a&&"false"!==a)}}function t(){return function(a){return void 0===a||""===a||null===a?!0:"Array"===a.constructor.name?!1:"Object"===a.constructor.name?!1:(a=a.toString(),_.isString(a))}}function u(a){return function(b,c,d){var e=d[a];return e?e&&e.length>=1?_.all(e,function(a){return a.$valid}):e.$valid:!0}}function v(a){return function(){return function(b,c,d){return void 0===b||""===b||null===b?!0:a(b,c,d)}}}function w(a){return a===!1?!1:!a}function x(a){var b;return b=void 0===a?A:A[a],void 0!==b?("Array"==b.constructor.name&&(b=_.zipObject([a],b)),b):void 0}function y(a,b,c){var d=c.split("."),e=b;_.each(d,function(a){e&&(e=e[a])});var f=a(e,c,b);return void 0===f?!1:f===!1?!1:!0}function z(a,b){function c(a,b,d,i,j){if(f(d[a]))return void h(d[a],b,i,j);if(e(d[a]))return d=d[a],keys=Object.keys(b),void keys.forEach(function(a){nestedValue=b[a],c(a,nestedValue,d,i,j)});if(g(d[a])){if("message"==a)return;if(!b.validates)throw"Custom validators must provide a validates key containing a Boolean function."}h(v(b.validates),b,i,j)}function d(a){return Object.prototype.toString.call(a)}function e(a){return"[object Object]"===d(a)}function f(a){return"[object Function]"===d(a)}function g(a){return void 0===a}function h(a,b,c,d){var e=function(c,d,e){return b.validates&&(b=b.validates),a(b)(c,d,e)?!0:!1};e.message=d.message?d.message:b.message?b.message:a.message(b),i.push(e)}var i=[];for(var j in a)c(j,a[j],B,b,a[j]);return i}var A={};this.$errors={},c.message=function(){return"Can't be blank"},e.message=function(){return"Can't be defined"},f.message=function(){return"Is not a valid email address"},g.message=function(){return"Is not a valid zip code"},h.message=function(){return"Is not the proper format"},i.message=function(a){return lastVal="or "+a.in.slice(-1),joinedArray=a.in.slice(0,-1),joinedArray.push(lastVal),list=joinedArray.join(joinedArray.length>=3?", ":" "),"Must be included in "+list},j.message=function(a){return lastVal="or "+a.from.slice(-1),joinedArray=a.from.slice(0,-1),joinedArray.push(lastVal),list=joinedArray.join(joinedArray.length>=3?", ":" "),"Must not be "+list},k.message=function(a){var b=a[0],c=a.slice(-1);return"Must be between "+b+" and "+c+" characters"},l.message=function(a){return"Must be at least "+a+" characters"},m.message=function(a){return"Must be no more than "+a+" characters"},n.message=function(a){return"Must be exactly "+a+" characters"},o.message=function(){return"Must be accepted"},p.message=function(){return"Must match confirmation field"},q.message=function(){return"Must be a number"},r.message=function(){return"Must be an integer"},s.message=function(){return"Must be true or false"},t.message=function(){return"Must be text"},u.message=function(a){return a.classify()+" invalid"};var B={presence:c,requiredIf:{requiredIf:d},absence:e,format:{email:f,zip:g,regex:h},inclusion:i,exclusion:j,length:{"in":k,min:l,max:m,is:n},acceptance:o,confirmation:p,numericality:q,integer:r,"boolean":s,string:t,association:u};for(var C in a)A[C]=new z(a[C],C);this.clearErrors=function(a){var b=x(a);for(var a in b)delete this.$errors[a]},this.validate=function(a,b){var c=x(a);for(var a in c)_.each(A[a],function(c){if(y(c,b,a)){if(!this.$errors[a])return;_.remove(this.$errors[a],function(a){return a==c.message}),0===this.$errors[a].length&&delete this.$errors[a]}else this.$errors[a]||(this.$errors[a]=[]),this.$errors[a].nodupush(c.message)},this);return 0===Object.keys(this.$errors).length},this.updateInstance=function(a){b=a},this.addValidations=function(a){for(var b in a)A[b]?(A[b].push(new z(a[b],b)),A[b]=_.flatten(A[b])):A[b]=new z(a[b],b)}}return a}]});var simpleForm=angular.module("simpleForm",["ActiveResource"]);simpleForm.directive("form",function(){return{restrict:"E",require:"^form",compile:function(){return{pre:function(a,b,c,d){function e(){return c["for"]?c["for"]+"Form":""}function f(a,b){if("hasOwnProperty"===a)throw ngMinErr("badname","hasOwnProperty is not a valid {0} name",b)}d.$name=c.name||e()||c.ngForm,d.$fields={},d.$addControl=function(a){f(a.$name,"input"),a.$name&&(d.$fields[a.$name]=a,d[a.$name]=a)}}}}}}),simpleForm.directive("ngModel",["$compile",function(){return{restrict:"A",require:["^ngModel","^form"],compile:function(){return{post:function(a,b,c,d){{var e,f=d[0];d[1]||nullFormCtrl}f.$name=c.name||c.ngModel||"unnamedInput",$modelname=c.ngModel.replace(/\.\w{0,}/g,""),a.$watch($modelname,function(a){function b(a){e.$errors[a]&&f.$setValidity(a,!1)}function d(a){e.$errors[a]||f.$setValidity(a,!0)}if(e=a){fieldNames=_.without(Object.getOwnPropertyNames(e),"validate","$valid","$invalid","$errors","validateIfErrored");var g=angular.copy(e.validate);e.validate=function(a){var f=c.ngModel.replace(/\w{0,}\./,"");return g.call(e,a),d(f),b(f),0===Object.keys(e.$errors).length}}})}}}}}]); \ No newline at end of file +angular.module("dojo",[]).provider("stamp",function(){this.$get=function(){var a={};return a.fromISOString=function(b,c){a._isoRegExp||(a._isoRegExp=/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(.\d+)?)?((?:[+-](\d{2}):(\d{2}))|Z)?)?$/);var d=a._isoRegExp.exec(b),e=null;if(d){d.shift(),d[1]&&d[1]--,d[6]&&(d[6]*=1e3),c&&(c=new Date(c),array.forEach(array.map(["FullYear","Month","Date","Hours","Minutes","Seconds","Milliseconds"],function(a){return c["get"+a]()}),function(a,b){d[b]=d[b]||a})),e=new Date(d[0]||1970,d[1]||0,d[2]||1,d[3]||0,d[4]||0,d[5]||0,d[6]||0),d[0]<100&&e.setFullYear(d[0]||1970);var f=0,g=d[7]&&d[7].charAt(0);"Z"!=g&&(f=60*(d[8]||0)+(Number(d[9])||0),"-"!=g&&(f*=-1)),g&&(f-=e.getTimezoneOffset()),f&&e.setTime(e.getTime()+6e4*f)}return e},a.toISOString=function(a,b){var c=function(a){return 10>a?"0"+a:a};b=b||{};var d=[],e=b.zulu?"getUTC":"get",f="";if("time"!=b.selector){var g=a[e+"FullYear"]();f=["0000".substr((g+"").length)+g,c(a[e+"Month"]()+1),c(a[e+"Date"]())].join("-")}if(d.push(f),"date"!=b.selector){var h=[c(a[e+"Hours"]()),c(a[e+"Minutes"]()),c(a[e+"Seconds"]())].join(":"),i=a[e+"Milliseconds"]();if(b.milliseconds&&(h+="."+(100>i?"0":"")+c(i)),b.zulu)h+="Z";else if("time"!=b.selector){var j=a.getTimezoneOffset(),k=Math.abs(j);h+=(j>0?"-":"+")+c(Math.floor(k/60))+":"+c(k%60)}d.push(h)}return d.join("T")},a}}).provider("json",function(){this.$get=["stamp",function(stamp){djson={},djson.fromJson=function(js){return eval("("+js+")")},djson._escapeString=JSON.stringify,djson.toJsonIndentStr=" ",djson.toJson=function(a,b){return JSON.stringify(a,function(a,b){if(b){var c=b.__json__||b.json;if("function"==typeof c)return c.call(b)}return b},b&&djson.toJsonIndentStr)};var json={};return json.resolveJson=function(a,b){function c(p,q,r,s,t,u){var v,w,x,y=e in p?p[e]:r;(e in p||void 0!==y&&s)&&(y=(h+y).replace(m,"$2$3"));var z=u||p;if(void 0!==y){if(i&&(p.__id=y),!b.schemas||p instanceof Array||!(x=y.match(/^(.+\/)[^\.\[]*$/))||(t=b.schemas[x[1]]),j[y]&&p instanceof Array==j[y]instanceof Array)z=j[y],delete z.$ref,delete z._loadObject,w=!0;else{var A=t&&t.prototype;A&&(o.prototype=A,z=new o)}j[y]=z,k&&(k[y]=b.time)}for(;t;){var B=t.properties;if(B)for(v in p){var C=B[v];C&&"date-time"==C.format&&"string"==typeof p[v]&&(p[v]=stamp.fromISOString(p[v]))}t=t["extends"]}var D=p.length;for(v in p){if(v==D)break;if(p.hasOwnProperty(v)){if(x=p[v],"object"==typeof x&&x&&!(x instanceof Date)&&"__parent"!=v)if(d=x[f]||g&&x[e],d&&x.__parent||p!=l&&(x.__parent=z),d){delete p[v];var E=d.toString().replace(/(#)([^\.\[])/,"$1.$2").match(/(^([^\[]*\/)?[^#\.\[]*)#?([\.\[].*)?/);if(j[(h+d).replace(m,"$2$3")]?d=j[(h+d).replace(m,"$2$3")]:(d="$"==E[1]||"this"==E[1]||""==E[1]?a:j[(h+E[1]).replace(m,"$2$3")])&&E[3]&&E[3].replace(/(\[([^\]]+)\])|(\.?([^\.\[]+))/g,function(a,b,c,e,f){d=d&&d[c?c.replace(/[\"\'\\]/,""):f]}),d)x=d;else if(!q){var F;F||l.push(z),F=!0,x=c(x,!1,x[f],!0,C),x._loadObject=b.loader}}else q||(x=c(x,l==p,void 0===y?void 0:n(y,v),!1,C,z!=p&&"object"==typeof z[v]&&z[v]));if(p[v]=x,z!=p&&!z.__isDirty){var G=z[v];z[v]=x,!w||x===G||z._loadObject||"_"==v.charAt(0)&&"_"==v.charAt(1)||"$ref"==v||x instanceof Date&&G instanceof Date&&x.getTime()==G.getTime()||"function"==typeof x&&"function"==typeof G&&x.toString()==G.toString()||!j.onUpdate||j.onUpdate(z,v,G,x)}}}if(w&&(e in p||z instanceof Array)){for(v in z)if(!(z.__isDirty||!z.hasOwnProperty(v)||p.hasOwnProperty(v)||"_"==v.charAt(0)&&"_"==v.charAt(1)||z instanceof Array&&isNaN(v)))for(j.onUpdate&&"_loadObject"!=v&&"_idAttr"!=v&&j.onUpdate(z,v,z[v],void 0),delete z[v];z instanceof Array&&z.length&&void 0===z[z.length-1];)z.length--}else j.onLoad&&j.onLoad(z);return z}b=b||{};var d,e=b.idAttribute||"id",f=this.refAttribute,g=b.idAsRef,h=b.idPrefix||"",i=b.assignAbsoluteIds,j=b.index||{},k=b.timeStamps,l=[],m=/^(.*\/)?(\w+:\/\/)|[^\/\.]+\/\.\.\/|^.*\/(\/)/,n=this._addProp,o=function(){};return a&&"object"==typeof a&&(a=c(a,!1,b.defaultId,!0),c(l,!1)),a},json.fromJson=function(str,args){function ref(a){var b={};return b[this.refAttribute]=a,b}try{var root=eval("("+str+")")}catch(e){throw new SyntaxError("Invalid JSON string: "+e.message+" parsing: "+str)}return root?this.resolveJson(root,args):root},json.toJson=function(a,b,c,d,e){function f(a,d,l){if("object"==typeof a&&a){if(a instanceof Date)return'"'+stamp.toISOString(a,{zulu:!0})+'"';var m=a.__id;if(m){if("#"!=d&&(g&&!m.match(/#/)||j[m])){var n=m;"#"!=m.charAt(0)&&(n=a.__clientId==m?"cid:"+m:m.substring(0,c.length)==c?m.substring(c.length):m);var o={};return o[i]=n,djson.toJson(o,b)}d=m}else a.__id=d,k[d]=a;j[d]=a,l=l||"";var p=b?l+djson.toJsonIndentStr:"",q=b?"\n":"",r=b?" ":"";if(a instanceof Array){var s=_.map(a,function(a,b){var c=f(a,h(d,b),p);return"string"!=typeof c&&(c="undefined"),q+p+c});return"["+s.join(","+r)+q+l+"]"}var t=[];for(var u in a)if(a.hasOwnProperty(u)){var v;if("number"==typeof u)v='"'+u+'"';else{if("string"!=typeof u||"_"==u.charAt(0)&&"_"==u.charAt(1))continue;v=djson._escapeString(u)}var w=f(a[u],h(d,u),p);if(void 0===w&&e.includeEmptyKeys===!0&&(w='" "'),"string"!=typeof w)continue;t.push(q+p+v+":"+r+w)}return"{"+t.join(","+r)+q+l+"}"}return djson.toJson(a)}e||(e={}),e.instance&&(a=e.instance);var g=this._useRefs,h=this._addProp,i=this.refAttribute;c=c||"";var j={},k={},l=f(a,"#","");if(!d)for(var m in k)delete k[m].__id;return l},json._addProp=function(a,b){return a+(a.match(/#/)?1==a.length?"":".":"#")+b},json.refAttribute="$ref",json._useRefs=!1,json.serializeFunctions=!1,json.serialize=function(a,b){var c;return b||(b={}),b.prettyPrint&&(c=!0),json.toJson(a,c,"",!1,b)},json}]}),Function.prototype.inherits=function(a){var b;return b=this,b=a.apply(b)},angular.module("ActiveResource",["ng","dojo"]).provider("ActiveResource",function(){this.$get=["ARBase",function(a){return ActiveResource={},ActiveResource.Base=a,ActiveResource}]}),angular.module("ActiveResource").provider("ARAPI",function(){this.$get=["ARHelpers","Mime",function(a,b){function c(a,c){var d,e=a.name.hyphenate(),f=e.toLowerCase(),g=f.pluralize(),h=c||"id";this.indexURL="",this.createURL="",this.showURL="",this.deleteURL="",this.updateURL="",this.set=function(a){return"/"!=a.slice(-1)&&(a+="/"),this.createURL=a+g,this.updateURL=this.showURL=this.deleteURL=a+g+"/:"+h,this.indexURL=a+g,this},this.updatePrimaryKey=function(a){return h=a,this.updateURL=this.updateURL,this},this.format=function(a){b.types.register(a),a.match(/\.\w+/)||(a="."+a),d=a;for(var c in this)c.match(/URL/)&&(_.each(b.types,function(a){var b=new RegExp("."+a);this[c]=this[c].replace(b,"")},this),this[c]+=d)}}return c}]}),angular.module("ActiveResource").provider("ARAssociation",function(){this.$get=["$injector","ARBelongsTo",function(a,b){function c(d,e,f,g){if(g.provider)var h=g.provider;else var h=f.classify();"BelongsTo"==d&&(this.foreignKey=g.foreignKey?g.foreignKey:f.singularize()+"_id",b(this,e,f,g)),this.klass=a.get(h),this.propertyName=f,this.type=d,c.cached[e.constructor.name.toLowerCase()+":"+f]=this}return c.cached={},c}]}).provider("ARBelongsTo",function(){this.$get=["ARAssociations","ARHelpers",function(a,b){function c(c,d,e){var f=void 0;Object.defineProperty(d,e,{enumerable:!0,get:function(){return f},set:function(e){if(void 0===e)return void(f=e);var g=b.getPrimaryKeyFor(d);if("String"==e.constructor.name||"Number"==e.constructor.name)c.klass.find(e).then(function(a){f=a;var b=d.constructor.name.pluralize().camelize(),c=f[b];d[g]&&c&&!_.include(c,d)&&c.push(d)});else if(c.klass.name==e.constructor.name){f=e;var h,i,j=a.get(e);_.each([j.hasMany,j.hasOne],function(a){_.each(a,function(a){a.klass==d.constructor&&(h=a,i=a.klass.name.camelize(),"hasMany"==h.type&&(i=i.pluralize()))})});var k=f[i];void 0!==d[g]&&k&&("hasMany"==h.type?_.include(k,d)||k.push(d):"hasOne"==h.type&&Object.defineProperty(f,i,{enumerable:!0,configurable:!0,value:d}))}}})}return c}]}).provider("ARAssociations",function(){this.$get=["$q",function(){function a(b){function c(b){var c=!0;_.each(this,function(a){a.klass==b.klass&&(c=!1)}),c&&this.push(b),"hasOne"==b.type&&(b.klass.prototype.find=function(){var c=a.get(b.klass).belongsTo,d={};return _.each(c,function(a){d[a.propertyName]=this[a.propertyName]},this),b.klass.find(d)})}var d=b.name.toLowerCase();this.belongsTo=[],this.hasMany=[],this.hasOne=[],this.belongsTo.add=c,this.hasMany.add=c,this.hasOne.add=c,a.cached[d]||(a.cached[d]=this)}return a.cached={},a.get=function(b){return model=b.klass?b.klass:b,"Function"!==model.constructor.name?model=model.constructor:model.new(),a.cached[model.name.toLowerCase()]},a.getBelongs=function(b,c){var d=a.get(b),e=void 0;return _.each(d.belongsTo,function(a){a.klass==c.constructor&&(e=a)}),e},a.getDependents=function(b,c){var d=a.getBelongs(b,c);return d},a}]}).provider("ARCollection",function(){this.$get=function(){function a(a,b){var c=b.name.toLowerCase(),d=[];return Object.defineProperty(d,"new",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.new(b)}}),Object.defineProperty(d,"$create",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.$create(b)}}),Object.defineProperty(d,"where",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.where(b)}}),Object.defineProperty(d,"find",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.find(b)}}),Object.defineProperty(d,"all",{enumerable:!1,value:function(b){return b||(b={}),b[c]=d[c],a.where(b)}}),Object.defineProperty(d,"first",{enumerable:!1,get:function(){return d[0]}}),Object.defineProperty(d,"last",{enumerable:!1,get:function(){return d.slice(-1)[0]}}),d}return a}}),String.prototype.downcase=function(){return this.toLowerCase()},angular.module("ActiveResource").provider("ARCache",function(){this.$get=function(){function a(){Object.defineProperty(this,"cache",{enumerable:!1,value:function(a,b){a&&void 0!==a[b]&&(a.constructor.cached[a[b]]=a)}}),Object.defineProperty(this,"isEmpty",{enumerable:!1,value:function(){return!Object.keys(this).length}}),Object.defineProperty(this,"where",{enumerable:!1,value:function(a){return 0==Object.keys(a).length&&(a=void 0),_.where(this,a,this)}})}return a}}),angular.module("ActiveResource").provider("ARSerializer",function(){this.$get=["json","ARMixin","ARAssociations","ARHelpers","ARDeferred",function(a,b,c,d,e){function f(){function f(a){var e=b({},a,!1),f=c.get(a);return _.each(f.belongsTo,function(b){var c=b.foreignKey,f=d.getClassNameFor(b),g=a[f];if(g){var h=d.getPrimaryKeyFor(g),i=g[h];e[c]=i,e[f]=void 0}}),e}function i(a,b){var d=!1,e=c.get(b);return _.each(e.belongsTo,function(b){var c=b.foreignKey;(a[c]||a==c)&&(d=!0)}),d}this.serialize=function(b,c){var d=f(b);return a.serialize(d,c)},this.deserialize=function(a,b,c){var d,c;return d=a&&a.data?a.data:a,c||(c={lazy:!0}),i(d,b)?h(b,d,c):g(b,d,c).then(function(a){return b=a,e(b)})}}function g(a,b,c){function f(){return a.establishBelongsTo(),e(a)}if(c&&0==c.update)return e(a);a.update(b);var g=d.getPrimaryKeyFor(a);return a.constructor.cached.cache(a,g),a.validations.updateInstance(a),c.lazy?f():i(a).then(f)}function h(a,b,d){d&&0==d.update&&(d.update=!0);var f=[],h=c.get(a);return h.belongsTo.length>=1&&_.each(h.belongsTo,function(a){if(b[a.foreignKey]){var c=a.klass,e=c.name.camelize(),g=a.foreignKey,h=b[g],i={};for(var j in d)i[j]=d[j];d.overEager||(i.lazy=!0),f.push(function(c){a.klass.find(h,i).then(function(a){b[e]=a,delete b[g],c(null,b)})})}}),async.series(f,function(b,c){c=_.first(c),g(a,c,d)}),e(a)}function i(a){var b=[],f=c.get(a),g=[f.hasMany,f.hasOne];return _.each(g,function(e){_.each(e,function(e){var g=c.getDependents(e,a),h=g.foreignKey,i={},j=d.getPrimaryKeyFor(a);i[h]=a[j],b.push(function(b){e.klass.where(i,{lazy:!0}).then(function(c){_.each(c,function(c){if(_.include(f.hasMany,e)){var d=e.klass.name.pluralize().camelize();a[d].nodupush(c)}else{var d=e.klass.name.singularize().camelize();a[d]||(a[d]=c)}b(null,a)})})})})}),async.series(b,function(){}),e(a)}return f}]}),angular.module("ActiveResource").provider("ARHelpers",function(){this.$get=function(){return Object.defineProperty(Array.prototype,"nodupush",{enumerable:!1,configurable:!0,value:function(a){_.include(this,a)||this.push(a)}}),{getClassNameFor:function(a){return a.klass.name.camelize()},getPrimaryKeyFor:function(a){return"Function"==a.constructor?a.primaryKey:a.constructor.primaryKey}}}}),angular.module("ActiveResource").provider("ARMixin",function(){this.$get=function(){return function(a,b,c){function d(){a.hasOwnProperty(e)||(!function(){var b;Object.defineProperty(a,e,{enumerable:!0,get:function(){return b},set:function(a){b=a}})}(),a[e]=b[e])}"Function"==b.constructor.name&&(b=new b);for(var e in b)c?"function"!=typeof b[e]&&d():d();return a}}}),angular.module("ActiveResource").provider("ARDeferred",function(){this.$get=["$q",function(a){return function b(c,d){var b=a.defer();return d?b.reject(d):b.resolve(c),b.promise}}]}),angular.module("ActiveResource").provider("ARQuerystring",function(){this.$get=function(){var a={stringify:function(a){var b="";return _.map(a,function(a,c){b+=0==b.length?c+"="+a:"&"+c+"="+a}),b},parse:function(){}};return a}}),angular.module("ActiveResource").provider("ARParameterize",function(){this.$get=function(){return function(a,b){return a?b?a.replace(/\:\_*[A-Za-z]+/g,function(a){return a=a.replace(/\:*/,""),b[a]}):a:void 0}}}),angular.module("ActiveResource").provider("URLify",function(){this.$get=["ARParameterize","ARQuerystring",function(a,b){return function(c,d){if(c){if(!d)return c;var e="";return b.stringify(d)&&(e="?"+b.stringify(d)),c.match(/\[\:[A-Za-z]+\]/)?c=c.replace(/\[\:[A-Za-z]+\]/,e):c.match(/\:\_*[A-Za-z]+/)&&(c=a(c,d)),c}}}]}),angular.module("ActiveResource").provider("ARGET",function(){this.$get=["$http","ARDeferred","ARAssociations","ARHelpers","URLify",function(a,b,c,d,e){function f(a,b,c){return a&&a.length>=1?_.first(c.noInstanceEndpoint?_.where(a,b):a):a}function g(a){return a}function h(a,b){var e,f,g=c.get(a);return g?(g=g.belongsTo,_.each(g,function(a){if(b[a.propertyName]){e=b[a.propertyName],f=a.propertyName;var c=a.foreignKey,g=d.getPrimaryKeyFor(b[a.propertyName]);b[c]=e[g],b[c]&&delete b[a.propertyName]}}),[e,b,f]):void 0}function i(a){var b=[];return a.replace(/\:[a-zA-Z_]+/g,function(a){b.push(a)}),b=_.map(b,function(a){return a.slice(1)})}function j(a,b){var c=i(a),d=!0;return _.each(c,function(a){void 0===b[a]&&(d=!1)}),_.each(b,function(a,b){_.include(c,b)||(d=!1)}),d}return function(b,c,d,i){var k,d,l,m=h(b,d);m&&(k=m[0],d=m[1],l=m[2]);var n={};return j(c,d)?c=e(c,d):Object.keys(d).length&&(c=c.replace(/\/\:\w+/,"").replace(/\:\w+/g,""),n.params=d),a.get(c,n).then(function(a){var b=a.data;return l&&k&&(b&&b.push?_.each(b,function(a){a[l]=k}):b[l]=k),i.multi?g(b,d,i):f(b,d,i)})}}]}),angular.module("ActiveResource").provider("ARBase",function(){this.$get=["ARAPI","ARCollection","ARAssociation","ARAssociations","ARCache","ARSerializer","AREventable","ARValidations","$http","$q","$injector","ARDeferred","ARGET","ARMixin","URLify","ARHelpers",function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p){function q(){function k(a){N.cached.cache(a,O)}function q(a){_.each(N.watchedCollections,function(b){_.remove(b,a)})}function r(a,b){var c=s(b);return!(!a[c]||a[c].constructor!=b)}function s(a){return a&&(a.klass||a.name)?a.klass?a.klass.name.camelize():a.name.camelize():void 0}function t(a){return _.include(F(N),a)}function u(a){return _.include(C(),a.camelize())}function v(a){return _.include(D(),a.camelize())}function w(a){return _.include(E(),a.camelize())}function x(a,b){("Number"==a.constructor.name||"String"==a.constructor.name)&&(a=void 0),a&&(this[b]=a)}function y(a,b,c,d){_.remove(a,function(a){var b=_.include(d,a[c]);return 0==b?(delete a.constructor.cached[a[c]],!0):void 0})}function z(a,b,c){var d,e,f;d=_.first(a),void 0!==d&&(e=d.constructor),void 0!==e&&(f=e.cached),_.each(b,function(b){var d;if(void 0!==f){var e={};e[c]=b[c],d=f.where(e)}d&&d.length?d[0].update(b):a.new(b)},this)}function A(a,b){var c=this[b.camelize()],d=_.first(c);if(!d)return z(c,a);var e=p.getPrimaryKeyFor(d),f=_.chain(a).map(function(a){return a[e]}).compact().unique().value();y(c,a,e,f),z(c,a,e,f)}function B(a,b){_.each(P.hasOne,function(c){if(c.klass.name==b.classify()){Object.defineProperty(this,b,{enumerable:!0,configurable:!0,value:c.klass.new(a)});var e=d.cached[c.klass.name.downcase()].belongsTo;_.each(e,function(a){a.klass==this.constructor&&(this[b][a.propertyName]=this)},this)}},this)}function C(){return _.map(P.belongsTo,function(a){return a.klass.name.camelize()})}function D(){return _.map(P.hasMany,function(a){return a.klass.name.camelize().pluralize()})}function E(){return _.map(P.hasOne,function(a){return a.klass.name.camelize()})}function F(a){var b=a.new(),c=Object.getOwnPropertyNames(b),d=[];for(var e in b)d.nodupush(e);_.each(c,function(a){d.nodupush(a)}),d.nodupush(O);var f=["establishBelongsTo","$save","update","$delete","associations","primaryKey","hasMany","hasOne","belongsTo","validate","$valid","$invalid","$errors","validations","validates"];return _.remove(d,function(a){return _.include(f,a)}),d}function G(a){void 0===a.validations&&a.validates({}),a.validate=function(b){return a.validations.validate(b,a)},a.validateIfErrored=function(b){a.$errors[b]&&a.validate(b,a)},Object.defineProperty(a,"$valid",{get:function(){return a.validate()}}),Object.defineProperty(a,"$invalid",{get:function(){return!a.$valid}}),Object.defineProperty(a,"$errors",{get:function(){return a.validations.$errors}}),a.clearErrors=function(b){a.validations.clearErrors(b)}}function H(a,b){a[O]=b[O]}function I(a,b,c){_.each(a[b],function(a){c[b].new(a)})}function J(a,b,c,e){var f=a[e.propertyName];if(f&&f.constructor.name==e.klass.name){var g=f,h=d.get(f).belongsTo;_.each(h,function(a){a.klass==c.constructor&&(f[a.propertyName]=c)})}else{var i,j=d.get(e).belongsTo;_.each(j,function(a){a.klass==c.constructor&&(i=a)}),a[b][i.propertyName]=c;var g=e.klass.new(a[b])}Object.defineProperty(c,b,{enumerable:!0,configurable:!0,value:g})}function K(a){_.each(Q,function(b){var c=a[b];_.each(c,function(a){a.$delete()})}),_.defer(function(){L(a),delete N.cached[a[O]]})}function L(a){_.each(P.hasMany,function(b){var c=a[b.klass.name.pluralize().camelize()],d=a.constructor.name.camelize();_.each(c,function(a){a[d]=void 0})}),_.each(P.belongsTo,function(b){var c=a[b.klass.name.camelize()];if(c){var d=a.constructor.name.pluralize().camelize();_.remove(c[d],a)}})}function M(a){var b=a;return a={},a[O]=b,a}var N=this;N.watchedCollections=[];var O="id";Object.defineProperty(N,"primaryKey",{configurable:!0,get:function(){return O},set:function(a){O=a,this.api.updatePrimaryKey(O)}});var P=new d(N),Q=[];this.api=new a(this),n(N,g),N.cached||(N.cached=new e),serializer=new f,N.prototype.toJSON=function(a){return N.prototype.serialize.call(this,a)},N.prototype.serialize=function(a){return serializer.serialize(this,a)},N.prototype.$save=function(a,b,c){a||(a=this),a&&a[O]&&!b?(b=o(N.api.updateURL,a),c!==!1&&(c=!0)):b||(b=N.api.createURL),N.emit("$save:called",a);var d=serializer.serialize(a);return a.$invalid?(N.emit("$save:fail",a),l(null,a)):(method=c?"put":"post",i[method](b,d).then(function(b){return serializer.deserialize(b,a).then(function(a){return N.emit("$save:complete",a),l(a)})}))},N.prototype.$update=function(a){function b(){return c.$save(c,d,"put")}var c=this,d=N.api.updateURL;return d?(d.match(/\:\w+/)&&(d=d.replace(/\:\w+/,this[O])),a?c.update(a).then(function(a){return c=a,b()}):b()):void 0},N.prototype.update=function(a){N.emit("update:called",{data:a,instance:this});for(var b in a)v(b)?A.call(this,a[b],b):w(b)?B.call(this,a[b],b):u(b)?x.call(this,a[b],b):t(b)&&(this[b]=a[b]);var c=this;return serializer.deserialize(a,c,{lazy:!0,update:!1}).then(function(){return N.emit("update:complete",{data:a,instance:c}),l(c)})},N.$create=function(a){a||(a={}),N.emit("$create:called",a);var b=N.new(a);return b.establishBelongsTo(),b.$save().then(function(a){return b=a,k(b),N.emit("$create:complete",b),l(b)})},N.new=function(a){if(a||(a={}),N.emit("new:called",a),"Number"==typeof a&&(a=M(a)),a&&this.cached[a[O]])return this.cached[a[O]];N.prototype.integer=function(b){var c=this,d={};d[b]={integer:{ignore:/\,/g}},c.validates(d),c[b]=a[b]},N.prototype.number=function(b){var c=this,d={};d[b]={numericality:{ignore:/\,/g}},c.validates(d),c[b]=a[b]},N.prototype.boolean=function(b){var c=this,d={};d[b]={"boolean":!0},c.validates(d),c[b]=a[b]},N.prototype.string=function(b){var c=this,d={};d[b]={string:!0},c.validates(d),c[b]=a[b]},N.prototype.computedProperty=function(b,c,d){var e=this;d||(d=[]),d.push||(d=[d]);var f;Object.defineProperty(e,b,{enumerable:!0,configurable:!0,get:function(){return f},set:function(){return f=c.apply(e)}}),_.each(d,function(c){var d,f=e.__lookupSetter__(c),g=e[c];Object.defineProperty(e,c,{enumerable:!0,configurable:!0,get:function(){return d},set:function(a){return void 0!==a&&"set"!=a&&(d=a),f&&(d==a?f():d=f()),e[b]="set",d}}),e[c]=a&&a[c]?a[c]:g})};var b=new this(a);return H(b,a),k(b),_.each(P.belongsTo,function(c){var d=s(c);a&&void 0!==a[d]&&(b[d]=a[d])}),_.each(P.hasMany,function(c){var d=c.klass.name.pluralize().camelize();b[d][this.name.camelize()]=b,void 0!==a[d]&&I(a,d,b)},this),_.each(P.hasOne,function(c){var d=c.propertyName;void 0!==a[c.propertyName]&&J(a,d,b,c)}),G(b),N.emit("new:complete",b),b},N.where=function(a,b){if(N.emit("where:called",a),"object"!=typeof a)throw"Argument to where must be an object";b||(b={lazy:!1,overEager:!1});var c=N.cached.where(a);b.cached=c,b.multi=!0;var d=N.api.indexURL||N.api.showURL;return m(N,d,a,b).then(function(a){var c=[];for(var d in a){var e=N.new(a[d]);c.push(e),serializer.deserialize(a[d],e,b)}return N.watchedCollections.push(c),N.emit("where:complete",{instance:c,data:a}),c})},N.all=function(a){return N.emit("all:called"),N.where({},a).then(function(a){var b=j.defer();return b.resolve(a),N.emit("all:complete",a),b.promise})},N.find=function(a,b){var c;if(N.emit("find:called",a),("number"==typeof a||"string"==typeof a)&&(a=M(a)),"object"!=typeof a)throw"Argument to find must be an object";b||(b={lazy:!1}),b.forceGET||(c=_.first(N.cached.where(a)));var d=N.api.showURL||N.api.indexURL;return void 0!==c?(N.emit("find:complete",c),l(c)):m(N,d,a,b).then(function(a){var c=N.new(a);return N.emit("find:complete",c),serializer.deserialize(a,c,b)})},N.prototype.$delete=function(){var a=this;N.emit("$delete:called",this);var b={},c={},d=N.api.deleteURL;return b[O]=a[O],-1!==N.api.deleteURL.indexOf("/:")?d=o(N.api.deleteURL,b):c={params:b},i.delete(d,c).then(function(b){if(200==b.status){if(q(a),N.emit("$delete:complete",a),Q.length>=1)return K(a);L(a),delete N.cached[a[O]]}})},N.prototype.establishBelongsTo=function(){if(P.belongsTo.length)for(var a in P.belongsTo){var b=P.belongsTo[a].klass,c=s(b);if(r(this,b)){var d=this[c][this.constructor.name.pluralize().camelize()];d&&d.push?d.nodupush(this):d=this}}};var R;return Object.defineProperty(N.prototype,"validates",{enumerable:!1,value:function(a){Object.defineProperty(this,"validations",{enumerable:!1,configurable:!0,get:function(){return R},set:function(a){void 0===R&&(R=new h(a,this)),R.addValidations(a)}}),this.validations=a}}),N.prototype.hasMany=function(a,d){d||(d={});var e=new c("hasMany",this,a,d);P.hasMany.add(e),N[a.classify()]=e.klass,this[a]=new b(e.klass,N),this[a][N.name.camelize()]=this},N.prototype.hasOne=function(a,b){b||(b={});var e=new c("hasOne",this,a,b);P.hasOne.add(e),this[a]||Object.defineProperty(this,a,{enumerable:!1,configurable:!0,value:e.klass.new()});var f=d.getBelongs(e.klass,this);this[a][f.propertyName]=this},N.prototype.belongsTo=function(a,b){b||(b={});var d=new c("BelongsTo",this,a,b);P.belongsTo.add(d)},N.dependentDestroy=function(a){"Array"!=a.constructor.name&&(a=[a]);for(var b in a)Q.push(a[b])},N}return q}]}),angular.module("ActiveResource").provider("AREventable",function(){this.$get=function(){function a(){function a(a,c){return a in b.handlers||(b.handlers[a]=[]),b.handlers[a].push(c),this}var b={handlers:{}};Object.defineProperty(this,"emit",{enumerable:!0,value:function(a){if(b.handlers[a]){for(var c=Array.prototype.slice.call(arguments,1),d=0,e=c.length;e>d;d++)b.handlers[a][d].apply(this,c);return b}}}),this.before=function(b,c){return a(b+":called",c)},this.after=function(b,c){return a(b+":complete",c)},this.fail=function(b,c){return a(b+":fail",c)}}return a}}),angular.module("ActiveResource").provider("Mime",function(){this.$get=[function(){return types=["json"],types.register=function(a){a=a.replace(/\./g,""),types.nodupush(a)},{types:types}}]}),angular.module("ActiveResource").provider("ARValidations",function(){this.$get=["$q",function(){function a(a,b){function c(){return function(a){return void 0===a||null===a?!1:"String"==a.constructor.name?!!(a&&a.length||"object"==typeof a):void 0!==a}}function d(a){return function(b,c,d){return a(b,c,d)===!0?!!(b&&b.length||"object"==typeof b):!0}}function e(){return function(a){return!a}}function f(){return function(a){return void 0===a||""===a||null===a?!0:/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/.test(a)}}function g(){return function(a){return void 0===a||""===a||null===a?!0:/(^\d{5}$)|(^\d{5}-{0,1}\d{4}$)/.test(a)}}function h(a){return function(b){return void 0===b||""===b||null===b?!0:a.test(b)}}function i(a){return function(b){if(void 0===b||""===b||null===b)return!0;if(!a.in)throw"Inclusion validator must specify 'in' attribute.";var c=!1;return a.in.forEach(function(a){a==b&&(c=!0)}),c}}function j(a){return function(b){if(void 0===b||""===b||null===b)return!0;if(!a.from)throw"Exclusion validator must specify 'from' attribute.";var c=!0;return a.from.forEach(function(a){a==b&&(c=!1)}),c}}function k(a){return function(b){return void 0===b||""===b||null==b?!0:b.length>=a[0]&&b.length<=a[a.length-1]}}function l(a){return function(b){return void 0===b||""===b||null===b?!0:b.length>=a}}function m(a){return function(b){return void 0===b||""===b||null===b?!0:b.length<=a}}function n(a){return function(b){return void 0===b||""===b||null===b?!0:String(b).length==a}}function o(){return function(a){return w(a)?!0:1==a}}function p(){return function(a,c){return confirmationName=c+"Confirmation",confirmationField=b[confirmationName],a==confirmationField}}function q(a){return function(b){return b?(b=String(b),a.ignore&&(b=b.replace(a.ignore,"")),!isNaN(Number(b))):!0}}function r(a){return function(b){return b?"Array"==b.constructor.name?!1:"Object"==b.constructor.name?!1:(b=String(b),b.match(/\./)?!1:(a.ignore&&(b=b.replace(a.ignore,"")),!isNaN(Number(b)))):!0}}function s(){return function(a){return void 0===a||""===a||null===a?!0:!(a!==!0&&a!==!1&&"true"!==a&&"false"!==a)}}function t(){return function(a){return void 0===a||""===a||null===a?!0:"Array"===a.constructor.name?!1:"Object"===a.constructor.name?!1:(a=a.toString(),_.isString(a))}}function u(a){return function(b,c,d){var e=d[a];return e?e&&e.length>=1?_.all(e,function(a){return a.$valid}):e.$valid:!0}}function v(a){return function(){return function(b,c,d){return void 0===b||""===b||null===b?!0:a(b,c,d)}}}function w(a){return a===!1?!1:!a}function x(a){var b;return b=void 0===a?A:A[a],void 0!==b?("Array"==b.constructor.name&&(b=_.zipObject([a],b)),b):void 0}function y(a,b,c){var d=c.split("."),e=b;_.each(d,function(a){e&&(e=e[a])});var f=a(e,c,b);return void 0===f?!1:f===!1?!1:!0}function z(a,b){function c(a,b,d,i,j){if(f(d[a]))return void h(d[a],b,i,j);if(e(d[a]))return d=d[a],keys=Object.keys(b),void keys.forEach(function(a){nestedValue=b[a],c(a,nestedValue,d,i,j)});if(g(d[a])){if("message"==a)return;if(!b.validates)throw"Custom validators must provide a validates key containing a Boolean function."}h(v(b.validates),b,i,j)}function d(a){return Object.prototype.toString.call(a)}function e(a){return"[object Object]"===d(a)}function f(a){return"[object Function]"===d(a)}function g(a){return void 0===a}function h(a,b,c,d){var e=function(c,d,e){return b.validates&&(b=b.validates),a(b)(c,d,e)?!0:!1};e.message=d.message?d.message:b.message?b.message:a.message(b),i.push(e)}var i=[];for(var j in a)c(j,a[j],B,b,a[j]);return i}var A={};this.$errors={},c.message=function(){return"Can't be blank"},e.message=function(){return"Can't be defined"},f.message=function(){return"Is not a valid email address"},g.message=function(){return"Is not a valid zip code"},h.message=function(){return"Is not the proper format"},i.message=function(a){return lastVal="or "+a.in.slice(-1),joinedArray=a.in.slice(0,-1),joinedArray.push(lastVal),list=joinedArray.join(joinedArray.length>=3?", ":" "),"Must be included in "+list},j.message=function(a){return lastVal="or "+a.from.slice(-1),joinedArray=a.from.slice(0,-1),joinedArray.push(lastVal),list=joinedArray.join(joinedArray.length>=3?", ":" "),"Must not be "+list},k.message=function(a){var b=a[0],c=a.slice(-1);return"Must be between "+b+" and "+c+" characters"},l.message=function(a){return"Must be at least "+a+" characters"},m.message=function(a){return"Must be no more than "+a+" characters"},n.message=function(a){return"Must be exactly "+a+" characters"},o.message=function(){return"Must be accepted"},p.message=function(){return"Must match confirmation field"},q.message=function(){return"Must be a number"},r.message=function(){return"Must be an integer"},s.message=function(){return"Must be true or false"},t.message=function(){return"Must be text"},u.message=function(a){return a.classify()+" invalid"};var B={presence:c,requiredIf:{requiredIf:d},absence:e,format:{email:f,zip:g,regex:h},inclusion:i,exclusion:j,length:{"in":k,min:l,max:m,is:n},acceptance:o,confirmation:p,numericality:q,integer:r,"boolean":s,string:t,association:u};for(var C in a)A[C]=new z(a[C],C);this.clearErrors=function(a){var b=x(a);for(var a in b)delete this.$errors[a]},this.validate=function(a,b){var c=x(a);for(var a in c)_.each(A[a],function(c){if(y(c,b,a)){if(!this.$errors[a])return;_.remove(this.$errors[a],function(a){return a==c.message}),0===this.$errors[a].length&&delete this.$errors[a]}else this.$errors[a]||(this.$errors[a]=[]),this.$errors[a].nodupush(c.message)},this);return 0===Object.keys(this.$errors).length},this.updateInstance=function(a){b=a},this.addValidations=function(a){for(var b in a)A[b]?(A[b].push(new z(a[b],b)),A[b]=_.flatten(A[b])):A[b]=new z(a[b],b)}}return a}]});var simpleForm=angular.module("simpleForm",["ActiveResource"]);simpleForm.directive("form",function(){return{restrict:"E",require:"^form",compile:function(){return{pre:function(a,b,c,d){function e(){return c["for"]?c["for"]+"Form":""}function f(a,b){if("hasOwnProperty"===a)throw ngMinErr("badname","hasOwnProperty is not a valid {0} name",b)}d.$name=c.name||e()||c.ngForm,d.$fields={},d.$addControl=function(a){f(a.$name,"input"),a.$name&&(d.$fields[a.$name]=a,d[a.$name]=a)}}}}}}),simpleForm.directive("ngModel",["$compile",function(){return{restrict:"A",require:["^ngModel","^form"],compile:function(){return{post:function(a,b,c,d){{var e,f=d[0];d[1]||nullFormCtrl}f.$name=c.name||c.ngModel||"unnamedInput",$modelname=c.ngModel.replace(/\.\w{0,}/g,""),a.$watch($modelname,function(a){function b(a){e.$errors[a]&&f.$setValidity(a,!1)}function d(a){e.$errors[a]||f.$setValidity(a,!0)}if(e=a){fieldNames=_.without(Object.getOwnPropertyNames(e),"validate","$valid","$invalid","$errors","validateIfErrored");var g=angular.copy(e.validate);e.validate=function(a){var f=c.ngModel.replace(/\w{0,}\./,"");return g.call(e,a),d(f),b(f),0===Object.keys(e.$errors).length}}})}}}}}]); \ No newline at end of file diff --git a/lib/angular-resource/base/base.js b/lib/angular-resource/base/base.js index b5ef90b..2af840e 100644 --- a/lib/angular-resource/base/base.js +++ b/lib/angular-resource/base/base.js @@ -418,11 +418,6 @@ angular return truth; }; - function appendSlashForQueryString(url) { - if (url.slice(-1) == '/') return url; - return url + '/'; - }; - return function generateGET(instance, url, terms, options) { var instanceAndTerms = transformSearchTermsToForeignKeys(instance, terms); var associatedInstance, terms, propertyName; @@ -435,10 +430,9 @@ angular if (queryableByParams(url, terms)) { url = URLify(url, terms); } else if(Object.keys(terms).length) { - url = url.replace(/\:\w+/, ''); + url = url.replace(/\/\:\w+/, '').replace(/\:\w+/g, ''); config.params = terms; } - url = appendSlashForQueryString(url); return $http.get(url, config).then(function(response) { var data = response.data; if (propertyName && associatedInstance) { @@ -460,10 +454,10 @@ angular .provider('ARBase', function() { this.$get = ['ARAPI', 'ARCollection', 'ARAssociation', 'ARAssociations', 'ARCache', 'ARSerializer', 'AREventable', 'ARValidations', '$http', '$q', - '$injector', 'ARDeferred', 'ARGET', 'ARMixin', 'URLify', + '$injector', 'ARDeferred', 'ARGET', 'ARMixin', 'URLify', 'ARHelpers', function(API, Collection, Association, Associations, Cache, Serializer, Eventable, Validations, $http, $q, - $injector, deferred, GET, mixin, URLify) { + $injector, deferred, GET, mixin, URLify, Helpers) { function Base() { var _this = this; @@ -960,7 +954,6 @@ angular } else { config = {params: queryterms}; } - url += '/'; return $http.delete(url, config).then(function(response) { if (response.status == 200) { removeFromWatchedCollections(instance); @@ -1045,20 +1038,37 @@ angular if (collection) this[name] = collection; }; - function removeOldHasManyInstances(hasManyCollection, newHasManyObjects) { - _.each(hasManyCollection, function(hasManyInstance) { - var found = _.where(newHasManyObjects, function(newHasManyObject) { - return hasManyInstance && hasManyInstance[primaryKey] == newHasManyObject[primaryKey]; - }); - if (!found.length) _.remove(hasManyCollection, hasManyInstance); + function removeOldHasManyInstances(hasManyCollection, newHasManyObjects, + primaryKeyName, primaryKeys) { + _.remove(hasManyCollection, function(hasManyInstance) { + var keep = _.include(primaryKeys, hasManyInstance[primaryKeyName]); + if (keep == false) { + delete hasManyInstance.constructor.cached[hasManyInstance[primaryKeyName]]; + return true; + } }); } - function createOrUpdateHasManyInstances(hasManyCollection, newHasManyObjects) { + function createOrUpdateHasManyInstances(hasManyCollection, newHasManyObjects, + primaryKeyName, primaryKeys) { + var _first, _cons, _cached; + + _first = _.first(hasManyCollection); + if (_first !== undefined) _cons = _first.constructor; + if (_cons !== undefined) _cached = _cons.cached; + _.each(newHasManyObjects, function(hasManyInstanceAttrs) { - var instance = _.where(hasManyCollection, hasManyInstanceAttrs); - if (!instance.length) hasManyCollection.new(hasManyInstanceAttrs); - else instance[0].update(hasManyInstanceAttrs); + var instance; + if (_cached !== undefined) { + var search = {}; + search[primaryKeyName] = hasManyInstanceAttrs[primaryKeyName]; + instance = _cached.where(search); + } + if (instance && instance.length) { + instance[0].update(hasManyInstanceAttrs); + } else { + hasManyCollection.new(hasManyInstanceAttrs); + } }, this); } @@ -1077,9 +1087,20 @@ angular // collection#new. function updateHasManyRelationship(newCollectionPOJOs, collectionName) { var hasManyCollection = this[collectionName.camelize()]; + var _first = _.first(hasManyCollection); + if (!_first) { + return createOrUpdateHasManyInstances(hasManyCollection, newCollectionPOJOs); + } + + var primaryKeyName = Helpers.getPrimaryKeyFor(_first); + var primaryKeys = _.chain(newCollectionPOJOs) + .map(function(o) { return o[primaryKeyName]; }) + .compact() + .unique() + .value(); - removeOldHasManyInstances(hasManyCollection, newCollectionPOJOs); - createOrUpdateHasManyInstances(hasManyCollection, newCollectionPOJOs); + removeOldHasManyInstances(hasManyCollection, newCollectionPOJOs, primaryKeyName, primaryKeys); + createOrUpdateHasManyInstances(hasManyCollection, newCollectionPOJOs, primaryKeyName, primaryKeys); }; function updateHasOneRelationship(association, name) { diff --git a/package.json b/package.json index 7df1540..53b7e5b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ActiveResource", - "version": "0.7.1", + "version": "0.7.4", "description": "RESTful resource modeling", "main": "index.js", "author": "Brett Shollenberger ", diff --git a/spec/angular-resource/active-resource.spec.js b/spec/angular-resource/active-resource.spec.js index 8c2975b..5494a25 100644 --- a/spec/angular-resource/active-resource.spec.js +++ b/spec/angular-resource/active-resource.spec.js @@ -34,22 +34,22 @@ describe('ActiveResource', function() { // // GET SYSTEM // Requests for mock "persisted" systems that will be returned from the mock API - backend.whenGET('http://api.faculty.com/systems/1/') + backend.whenGET('http://api.faculty.com/systems/1') .respond([{id: 1}]); - backend.whenGET('http://api.faculty.com/systems/2/') + backend.whenGET('http://api.faculty.com/systems/2') .respond([{id: 2}]); - backend.whenGET('http://api.faculty.com/systems/3/') + backend.whenGET('http://api.faculty.com/systems/3') .respond([{id: 3}]); - backend.whenGET('http://api.faculty.com/systems/4/') + backend.whenGET('http://api.faculty.com/systems/4') .respond([{id: 4}]); - backend.whenGET('http://api.faculty.com/systems/?id=5&placement=door') + backend.whenGET('http://api.faculty.com/systems?id=5&placement=door') .respond([{id: 5, placement: 'door'}]); - backend.whenGET('http://api.faculty.com/systems/?placement=window') + backend.whenGET('http://api.faculty.com/systems?placement=window') .respond([{id: 6, placement: 'window'}, {id: 7, placement: 'window'}]); // POST SYSTEM @@ -69,10 +69,10 @@ describe('ActiveResource', function() { // GET SENSOR // Requests for mock "persisted" sensors - backend.whenGET('http://api.faculty.com/sensors/1/') + backend.whenGET('http://api.faculty.com/sensors/1') .respond({id: 1, system_id: 1}); - backend.whenGET('http://api.faculty.com/sensors/2/') + backend.whenGET('http://api.faculty.com/sensors/2') .respond({id: 2, system_id: 2}); // POST SENSOR @@ -360,18 +360,18 @@ describe('ActiveResource', function() { it('deletes using the primary key', function() { post.$delete(); - backend.expectDELETE('http://api.faculty.com/posts/52a8b80d251c5395b485cfe6/') + backend.expectDELETE('http://api.faculty.com/posts/52a8b80d251c5395b485cfe6') .respond({data: 'Success'}); backend.flush(); - expect($http.delete).toHaveBeenCalledWith('http://api.faculty.com/posts/52a8b80d251c5395b485cfe6/', { method : 'delete', url : 'http://api.faculty.com/posts/52a8b80d251c5395b485cfe6/' }); + expect($http.delete).toHaveBeenCalledWith('http://api.faculty.com/posts/52a8b80d251c5395b485cfe6', { method : 'delete', url : 'http://api.faculty.com/posts/52a8b80d251c5395b485cfe6' }); }); it('finds using the primary key if no object is passed to find', function() { Post.find('52a8b80d251c5395b485cfe7', {lazy: true}).then(function(response) { post = response; }); - backend.expectGET('http://api.faculty.com/posts/52a8b80d251c5395b485cfe7/') + backend.expectGET('http://api.faculty.com/posts/52a8b80d251c5395b485cfe7') .respond({"_id": "52a8b80d251c5395b485cfe7", "title": "An Incredible Post"}); backend.flush(); - expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/posts/52a8b80d251c5395b485cfe7/', {method : 'get', url : 'http://api.faculty.com/posts/52a8b80d251c5395b485cfe7/' }); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/posts/52a8b80d251c5395b485cfe7', {method : 'get', url : 'http://api.faculty.com/posts/52a8b80d251c5395b485cfe7' }); }); }); @@ -494,18 +494,18 @@ describe('ActiveResource', function() { system = response; }); - backend.expectGET('http://api.faculty.com/systems/100/') + backend.expectGET('http://api.faculty.com/systems/100') .respond({id: 100}); - backend.expectGET('http://api.faculty.com/sensors/?system_id=100') + backend.expectGET('http://api.faculty.com/sensors?system_id=100') .respond([{id: 25, system_id: 100}]); - backend.expectGET('http://api.faculty.com/grid-controllers/?system_id=100') + backend.expectGET('http://api.faculty.com/grid-controllers?system_id=100') .respond({id: 25, system_id: 100}); backend.flush(); - expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/grid-controllers/', { params : { system_id : 100 }, method : 'get', url : 'http://api.faculty.com/grid-controllers/' }); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/grid-controllers', { params : { system_id : 100 }, method : 'get', url : 'http://api.faculty.com/grid-controllers' }); }); it('grabs the association when the dependent calls find', function() { @@ -514,10 +514,10 @@ describe('ActiveResource', function() { gc = response; }); - backend.expectGET('http://api.faculty.com/grid-controllers/?id=100') + backend.expectGET('http://api.faculty.com/grid-controllers?id=100') .respond({id: 100, system_id: 100}); - backend.expectGET('http://api.faculty.com/systems/100/') + backend.expectGET('http://api.faculty.com/systems/100') .respond({id: 100}); backend.flush(); @@ -530,11 +530,11 @@ describe('ActiveResource', function() { gc = response; }); - backend.expectGET('http://api.faculty.com/grid-controllers/?id=100') + backend.expectGET('http://api.faculty.com/grid-controllers?id=100') .respond({id: 100}); backend.flush(); - expect($http.get).not.toHaveBeenCalledWith('http://api.faculty.com/systems/?id=100'); + expect($http.get).not.toHaveBeenCalledWith('http://api.faculty.com/systems?id=100'); }); it('sets associations for all hasOnes found through #where', function() { @@ -543,14 +543,14 @@ describe('ActiveResource', function() { gcList = response; }); - backend.expectGET('http://api.faculty.com/grid-controllers/?status=ready') + backend.expectGET('http://api.faculty.com/grid-controllers?status=ready') .respond([ {id: 100, status: 'ready', system_id: 100}, {id: 25, status: 'ready', system_id: 25}]); - backend.expectGET('http://api.faculty.com/systems/100/') + backend.expectGET('http://api.faculty.com/systems/100') .respond({id: 100}); - backend.expectGET('http://api.faculty.com/systems/25/') + backend.expectGET('http://api.faculty.com/systems/25') .respond({id: 25}); backend.flush(); @@ -613,12 +613,12 @@ describe('ActiveResource', function() { it('updates associated instances if found at a later time', function() { var system, gridController; System.find(50, {lazy: true}).then(function(response) { system = response; }); - backend.expectGET('http://api.faculty.com/systems/50/').respond({ + backend.expectGET('http://api.faculty.com/systems/50').respond({ id: 50 }); backend.flush(); system.gridController.find({lazy: true}).then(function(response) { gridController = response; }); - backend.expectGET('http://api.faculty.com/grid-controllers/?system_id=50').respond({ + backend.expectGET('http://api.faculty.com/grid-controllers?system_id=50').respond({ id: 2 }); backend.flush(); @@ -727,10 +727,10 @@ describe('ActiveResource', function() { Post.find(1).then(function(response) { post = response; }); - backend.expectGET('http://api.faculty.com/posts/1/') + backend.expectGET('http://api.faculty.com/posts/1') .respond({_id: 1, title: "Great Post"}); - backend.expectGET('http://api.faculty.com/comments/?post_id=1') + backend.expectGET('http://api.faculty.com/comments?post_id=1') .respond([{id: 1, text: "Great one!", post_id: 1}]); backend.flush(); @@ -741,7 +741,7 @@ describe('ActiveResource', function() { }); it('Uses foreign keys set to query for associations', function() { - expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/comments/', { params : { post_id : 1 }, method : 'get', url : 'http://api.faculty.com/comments/' }); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/comments', { params : { post_id : 1 }, method : 'get', url : 'http://api.faculty.com/comments' }); }); }); @@ -867,7 +867,7 @@ describe('ActiveResource', function() { backend.expectPOST('http://api.faculty.com/sensors').respond({id: 2, system_id: 1}); backend.flush(); sensor1.$delete(); - backend.expectDELETE('http://api.faculty.com/sensors/1/').respond({data: 'success'}); + backend.expectDELETE('http://api.faculty.com/sensors/1').respond({data: 'success'}); backend.flush(); }); @@ -888,7 +888,7 @@ describe('ActiveResource', function() { beforeEach(function() { tshirt = Tshirt.new({_id: 1}); tshirt.$delete().then(function(response) { tshirt = response }); - backend.expectDELETE('http://api.faculty.com:3000/tshirt/1/') + backend.expectDELETE('http://api.faculty.com:3000/tshirt/1') .respond({}); backend.flush(); @@ -910,7 +910,7 @@ describe('ActiveResource', function() { backend.expectPOST('http://api.faculty.com/sensors').respond({id: 1, system_id: 1}); backend.flush(); system.$delete(); - backend.expectDELETE('http://api.faculty.com/systems/1/').respond({data: 'success'}); + backend.expectDELETE('http://api.faculty.com/systems/1').respond({data: 'success'}); backend.flush(); }); @@ -931,8 +931,8 @@ describe('ActiveResource', function() { .respond({id: 1, post_id: 1}); backend.flush(); post.$delete().then(function(response) { post = comment = response; }); - backend.expectDELETE('http://api.faculty.com/posts/1/').respond({data: 'success'}); - backend.expectDELETE('http://api.faculty.com/comments/1/').respond({data: 'success'}); + backend.expectDELETE('http://api.faculty.com/posts/1').respond({data: 'success'}); + backend.expectDELETE('http://api.faculty.com/comments/1').respond({data: 'success'}); backend.flush(); $timeout.flush(); }); @@ -944,7 +944,7 @@ describe('ActiveResource', function() { it('deletes the primary resource from collections containing that resource', function() { var posts; Post.all({lazy: true}).then(function(results) { posts = results; }); - backend.expectGET('http://api.faculty.com/posts/').respond([ + backend.expectGET('http://api.faculty.com/posts').respond([ { _id: 1, title: 'Cool post!' @@ -957,7 +957,7 @@ describe('ActiveResource', function() { backend.flush(); var post = posts[0]; posts[0].$delete(); - backend.expectDELETE('http://api.faculty.com/posts/1/').respond({ + backend.expectDELETE('http://api.faculty.com/posts/1').respond({ status: 200 }); backend.flush(); @@ -969,7 +969,7 @@ describe('ActiveResource', function() { }); it('calls the backend to delete the dependents', function() { - expect($http.delete).toHaveBeenCalledWith('http://api.faculty.com/comments/1/', { method : 'delete', url : 'http://api.faculty.com/comments/1/' }); + expect($http.delete).toHaveBeenCalledWith('http://api.faculty.com/comments/1', { method : 'delete', url : 'http://api.faculty.com/comments/1' }); }); }); @@ -1006,10 +1006,10 @@ describe('ActiveResource', function() { gridController = response; }); - backend.expectGET('http://api.faculty.com/comments/?post_id=1') + backend.expectGET('http://api.faculty.com/comments?post_id=1') .respond([{id: 1}, {id: 2}]); - backend.expectGET('http://api.faculty.com/grid-controllers/?system_id=1') + backend.expectGET('http://api.faculty.com/grid-controllers?system_id=1') .respond({id: 1}); backend.flush(); @@ -1039,7 +1039,7 @@ describe('ActiveResource', function() { comments = response; }); - backend.expectGET('http://api.faculty.com/comments/?post_id=1') + backend.expectGET('http://api.faculty.com/comments?post_id=1') .respond([{id: 1}, {id: 2}]); backend.flush(); @@ -1067,7 +1067,7 @@ describe('ActiveResource', function() { comment = response; }); - backend.expectGET('http://api.faculty.com/comments/?post_id=1') + backend.expectGET('http://api.faculty.com/comments?post_id=1') .respond([{id: 1}]); backend.flush(); @@ -1170,7 +1170,7 @@ describe('ActiveResource', function() { it('updates when the primary key is 0', function() { var post; Post.find(0, {lazy: true}).then(function(results) { post = results; }); - backend.expectGET('http://api.faculty.com/posts/0/').respond({_id: 0}); + backend.expectGET('http://api.faculty.com/posts/0').respond({_id: 0}); backend.flush(); var comments = {comments: [{id: 0}, {id: 1}]}; post.update(comments); @@ -1193,6 +1193,13 @@ describe('ActiveResource', function() { expect(system.sensors.length).toBe(2); }); + it('deals with a bunch of duplicate primary keys', function() { + post.update({comments: [{id: 1, text: 'My old comment'}]}); + post.update({comments: [{id: 1, text: 'My Comment'}, {id: 1, text: 'My Comment 2'}, {id: 1, text: 'My Comment 3'}, {id: 2, text: 'My Comment 4'}]}); + expect(Object.keys(Comment.cached).length).toBe(2); + expect(post.comments.first.text).toEqual('My Comment 3'); + }); + it('will not set "unsettable" properties', function() { post.update({tags: "awesome!"}); expect(post.tags).not.toBeDefined(); @@ -1258,7 +1265,7 @@ describe('ActiveResource', function() { it('finds by id', function() { var foundSystems; System.where({id: 1}, {lazy: true}).then(function(response) { foundSystems = response; }); - backend.expectGET('http://api.faculty.com/systems/?id=1').respond([{id: 1}]); + backend.expectGET('http://api.faculty.com/systems?id=1').respond([{id: 1}]); backend.flush(); expect(foundSystems).toEqual([system]); }); @@ -1267,7 +1274,7 @@ describe('ActiveResource', function() { var foundSystems; System.where({placement: 'window'}, {lazy: true}) .then(function(response) { foundSystems = response; }); - backend.expectGET('http://api.faculty.com/systems/?placement=window') + backend.expectGET('http://api.faculty.com/systems?placement=window') .respond([{id: 8, placement: 'window', name: 'Bretts System'}, {id: 9, placement: 'window', name: 'Matts System'}, {id: 10, placement: 'window', name: 'Pickles System'}]); @@ -1280,18 +1287,18 @@ describe('ActiveResource', function() { it('always queries the backend to get all instances, even if some are found in the cache', function() { var foundSystems; System.where({placement: 'window'}, {lazy: true}).then(function(response) { foundSystems = response; }); - backend.expectGET('http://api.faculty.com/systems/?placement=window') + backend.expectGET('http://api.faculty.com/systems?placement=window') .respond([{id: 8, placement: 'window', name: 'Bretts System'}, {id: 9, placement: 'window', name: 'Matts System'}, {id: 10, placement: 'window', name: 'Pickles System'}]); backend.flush(); - expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/systems/', { params : { placement : 'window' }, method : 'get', url : 'http://api.faculty.com/systems/' }); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/systems', { params : { placement : 'window' }, method : 'get', url : 'http://api.faculty.com/systems' }); }); it('adds the instance to the cache', function() { var foundSystems; System.where({id: 4}, {lazy: true}).then(function(response) { foundSystems = response; }); - backend.expectGET('http://api.faculty.com/systems/?id=4').respond({}); + backend.expectGET('http://api.faculty.com/systems?id=4').respond({}); backend.flush(); expect(System.cached[4]).toBe(foundSystems[0]); }); @@ -1299,18 +1306,18 @@ describe('ActiveResource', function() { it('has associated method #all that returns all instances', function() { var foundSystems; System.all().then(function(response) { foundSystems = response; }); - backend.expectGET('http://api.faculty.com/systems/') + backend.expectGET('http://api.faculty.com/systems') .respond([{id: 8, placement: 'window', name: 'Bretts System'}, {id: 9, placement: 'window', name: 'Matts System'}, {id: 10, placement: 'window', name: 'Pickles System'}]); - backend.expectGET('http://api.faculty.com/sensors/?system_id=8') + backend.expectGET('http://api.faculty.com/sensors?system_id=8') .respond([]); - backend.expectGET('http://api.faculty.com/sensors/?system_id=9') + backend.expectGET('http://api.faculty.com/sensors?system_id=9') .respond([]); - backend.expectGET('http://api.faculty.com/sensors/?system_id=10') + backend.expectGET('http://api.faculty.com/sensors?system_id=10') .respond([]); backend.flush(); @@ -1325,7 +1332,7 @@ describe('ActiveResource', function() { tshirts = response; }); - backend.expectGET('http://api.faculty.com/tshirts/?size=M') + backend.expectGET('http://api.faculty.com/tshirts?size=M') .respond([{id: 1, size: 'M'}, {id: 2, size: 'M'}]); backend.flush(); @@ -1343,7 +1350,7 @@ describe('ActiveResource', function() { tshirt = response; }); - backend.expectGET('http://api.faculty.com:3000/tshirt/1/') + backend.expectGET('http://api.faculty.com:3000/tshirt/1') .respond({_id: 1, size: 'M'}); backend.flush(); @@ -1388,7 +1395,7 @@ describe('ActiveResource', function() { System.find({id: 1}, {lazy: true, forceGET: true, noInstanceEndpoint: true}) .then(function(response) { foundSystem = response; }); - backend.expectGET('http://api.faculty.com/systems/1/') + backend.expectGET('http://api.faculty.com/systems/1') .respond([{id: 2, name: 'Wrong System'}, {id: 1, name: 'Right System'}]); backend.flush(); @@ -1400,7 +1407,7 @@ describe('ActiveResource', function() { System.find({id: 1}, {lazy: true, forceGET: true, noInstanceEndpoint: true}) .then(function(response) { foundSystem = response; }); - backend.expectGET('http://api.faculty.com/systems/1/') + backend.expectGET('http://api.faculty.com/systems/1') .respond([{id: 2, name: 'Wrong System'}, {id: 1, name: 'Right System'}]); backend.flush(); @@ -1415,7 +1422,7 @@ describe('ActiveResource', function() { System.find({id: 6, name: 'Right System'}, {lazy: true, noInstanceEndpoint: true}) .then(function(response) { foundSystem = response; }); - backend.expectGET('http://api.faculty.com/systems/?id=6&name=Right+System') + backend.expectGET('http://api.faculty.com/systems?id=6&name=Right+System') .respond([{id: 2, name: 'Wrong System'}, {id: 6, name: 'Right System'}]); backend.flush(); @@ -1451,7 +1458,7 @@ describe('ActiveResource', function() { }); it('returns the first object only', function() { - // No cached system will be found, triggering a $http.get('http://api.faculty.com/systems/?placement=window'); + // No cached system will be found, triggering a $http.get('http://api.faculty.com/systems?placement=window'); // // The mock API is setup to respond with sensors 6 & 7. Find will only respond with the first instance (6). var foundSystem; @@ -1561,18 +1568,26 @@ describe('ActiveResource', function() { it('adds the mimetype after any params', function() { Project.find(1); - backend.expectGET('http://api.faculty.com/projects/1.json/').respond({}); + backend.expectGET('http://api.faculty.com/projects/1.json').respond({}); backend.flush(); - expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/projects/1.json/', - { method : 'get', url : 'http://api.faculty.com/projects/1.json/' }); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/projects/1.json', + { method : 'get', url : 'http://api.faculty.com/projects/1.json' }); }); it('falls back on a querystring if params other than those specified in the url are utilized', function() { Project.where({author_id: 1}); - backend.expectGET('http://api.faculty.com/projects.json/?author_id=1').respond({}); + backend.expectGET('http://api.faculty.com/projects.json?author_id=1').respond({}); + backend.flush(); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/projects.json', + { params : { author_id : 1 }, method : 'get', url : 'http://api.faculty.com/projects.json' }); + }); + + it('does stuff', function() { + Project.where({id: 1, embed: 'widgets,users'}); + backend.expectGET('http://api.faculty.com/projects.json?embed=widgets,users&id=1').respond({}); backend.flush(); - expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/projects.json/', - { params : { author_id : 1 }, method : 'get', url : 'http://api.faculty.com/projects.json/' }); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/projects.json', + { params : { id : 1, embed: 'widgets,users' }, method : 'get', url : 'http://api.faculty.com/projects.json' }); }); }); @@ -1580,12 +1595,12 @@ describe('ActiveResource', function() { describe('Model#find', function() { it('calls GET to the specified API, filling in the correct ID', function() { System.find(4); - expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/systems/4/', { method : 'get', url : 'http://api.faculty.com/systems/4/' }); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/systems/4', { method : 'get', url : 'http://api.faculty.com/systems/4' }); }); it('calls GET passing the specified parameters', function() { System.find({placement: 'window'}); - expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/systems/', { params : { placement : 'window' }, method : 'get', url : 'http://api.faculty.com/systems/' }); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/systems', { params : { placement : 'window' }, method : 'get', url : 'http://api.faculty.com/systems' }); }); }); }); @@ -1596,26 +1611,26 @@ describe('ActiveResource', function() { beforeEach(function() { Post.find(1, {overEager: true}).then(function(response) { post = response; }); - backend.expectGET('http://api.faculty.com/posts/1/') + backend.expectGET('http://api.faculty.com/posts/1') .respond({_id: 1, title: 'Great post!', author_id: 1}); - backend.expectGET('http://api.faculty.com/authors/1/') + backend.expectGET('http://api.faculty.com/authors/1') .respond({_id: 1, name: 'Yorn Lomborg'}); - backend.expectGET('http://api.faculty.com/comments/?author_id=1') + backend.expectGET('http://api.faculty.com/comments?author_id=1') .respond({_id: 1, text: 'Great!', author_id: 1, post_id: 1}); - backend.expectGET('http://api.faculty.com/comments/?post_id=1') + backend.expectGET('http://api.faculty.com/comments?post_id=1') .respond({_id: 1, text: 'Great!', author_id: 1, post_id: 1}); - backend.expectGET('http://api.faculty.com/posts/?author_id=1') + backend.expectGET('http://api.faculty.com/posts?author_id=1') .respond({_id: 1, title: 'Great post!', author_id: 1}); backend.flush(); }); it('Loads associations, and then all associations of associations, etc., recursively', function() { - expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/posts/1/', { method : 'get', url : 'http://api.faculty.com/posts/1/' }); + expect($http.get).toHaveBeenCalledWith('http://api.faculty.com/posts/1', { method : 'get', url : 'http://api.faculty.com/posts/1' }); }); }); @@ -1624,13 +1639,13 @@ describe('ActiveResource', function() { beforeEach(function() { spyOn(window, 'alert'); Post.find(1).then(function(response) { post = response; }); - backend.expectGET('http://api.faculty.com/posts/1/') + backend.expectGET('http://api.faculty.com/posts/1') .respond({_id: 1, title: 'Great post!', author_id: 1}); - backend.expectGET('http://api.faculty.com/authors/1/') + backend.expectGET('http://api.faculty.com/authors/1') .respond({_id: 1, name: 'Yorn Lomborg'}); - backend.expectGET('http://api.faculty.com/comments/?post_id=1') + backend.expectGET('http://api.faculty.com/comments?post_id=1') .respond({}); backend.whenPUT('http://api.faculty.com/posts/1') @@ -1701,7 +1716,7 @@ describe('ActiveResource', function() { Post.after('$delete', function(response) { window.alert(response.instance.title + ' deleted successfully!')}); post.$delete(); - backend.expectDELETE('http://api.faculty.com/posts/1/').respond({ + backend.expectDELETE('http://api.faculty.com/posts/1').respond({ status: 200}); backend.flush(); expect(window.alert).toHaveBeenCalledWith('Great post! deleted successfully!'); @@ -1711,7 +1726,7 @@ describe('ActiveResource', function() { Post.after('$delete', function(response) { window.alert(response.data.data)}); post.$delete(); - backend.expectDELETE('http://api.faculty.com/posts/1/').respond({ + backend.expectDELETE('http://api.faculty.com/posts/1').respond({ status: 200}); backend.flush(); expect(window.alert).toHaveBeenCalledWith({status: 200}); @@ -1756,7 +1771,7 @@ describe('ActiveResource', function() { }); Post.find(789, {lazy: true}); - backend.expectGET('http://api.faculty.com/posts/789/').respond({ + backend.expectGET('http://api.faculty.com/posts/789').respond({ _id: 789, title: 'Great post!' }); @@ -1771,7 +1786,7 @@ describe('ActiveResource', function() { alert('Finding instances that match ' + terms.title); }); Post.where({title: 'Great post!'}, {lazy: true}); - backend.expectGET('http://api.faculty.com/posts/?title=Great+post!') + backend.expectGET('http://api.faculty.com/posts?title=Great+post!') .respond({title: 'Great post!', _id: 1}); backend.flush(); expect(window.alert).toHaveBeenCalledWith('Finding instances that match Great post!'); @@ -1784,7 +1799,7 @@ describe('ActiveResource', function() { alert(results.instance); }); Post.where({title: 'Great post!'}, {lazy: true}); - backend.expectGET('http://api.faculty.com/posts/?title=Great+post!') + backend.expectGET('http://api.faculty.com/posts?title=Great+post!') .respond([{title: 'Great post!', _id: 1}]); backend.flush(); expect(window.alert).toHaveBeenCalledWith([post]); @@ -1796,7 +1811,7 @@ describe('ActiveResource', function() { results = response.data; }); Post.where({title: 'Great post!'}, {lazy: true}); - backend.expectGET('http://api.faculty.com/posts/?title=Great+post!') + backend.expectGET('http://api.faculty.com/posts?title=Great+post!') .respond([{title: 'Great post!', _id: 1}]); backend.flush(); expect(results[0].title).toEqual('Great post!'); @@ -1809,7 +1824,7 @@ describe('ActiveResource', function() { json = response.data; }); Post.where({title: 'Great post!'}, {lazy: true}); - backend.expectGET('http://api.faculty.com/posts/?title=Great+post!') + backend.expectGET('http://api.faculty.com/posts?title=Great+post!') .respond({title: 'Great post!', _id: 1}); backend.flush(); expect(json).toEqual({ title : 'Great post!', _id : 1 }); @@ -1821,7 +1836,7 @@ describe('ActiveResource', function() { instances = response.instance; }); Post.where({title: 'Great post!'}, {lazy: true}); - backend.expectGET('http://api.faculty.com/posts/?title=Great+post!') + backend.expectGET('http://api.faculty.com/posts?title=Great+post!') .respond({title: 'Great post!', _id: 1}); backend.flush(); expect(instances[0].circularRef).toBeDefined(); @@ -2571,7 +2586,7 @@ describe('ActiveResource', function() { var user; beforeEach(function() { User.find({id: 1}).then(function(response) { user = response; }); - backend.expectGET('http://api.faculty.com/users/1/').respond({ + backend.expectGET('http://api.faculty.com/users/1').respond({ id: 1, name: 'Brett', username: 'brettcassette',