1 var comb = require("comb"), 2 hitch = comb.hitch, 3 Promise = comb.Promise, 4 PromiseList = comb.PromiseList, 5 _Association = require("./_Association"); 6 7 /** 8 * @class Class to define a one to many association. 9 * 10 * </br> 11 * <b>NOT to be instantiated directly</b> 12 * Its just documented for reference. 13 * 14 * @name OneToMany 15 * @augments _Association 16 * 17 * */ 18 module.exports = exports = comb.define(_Association, { 19 instance : { 20 /**@lends OneToMany.prototype*/ 21 /** 22 * Compares our associtaion and handle any creation, removal, reloading of the associated model. 23 * @param {Function} next funciton to call to pass control up the middleware chain. 24 * @param {Object} reference to the acting association class. 25 */ 26 27 diff : function(next, self) { 28 if (!this[self.loadedKey]) { 29 if (!self.filter || (self.filter && self.isEager())) { 30 this[self.name].then(hitch(this, self.diff, next, self)); 31 } else { 32 next(); 33 } 34 } else if (!this.filter) { 35 var values = this[self.addedKey]; 36 var removeValues = this[self.removedKey]; 37 var pl = []; 38 if (values.length) { 39 pl = pl.concat(values.map(function(v) { 40 v[self.rightKey] = this[self.leftKey]; 41 return v.save(); 42 }, this)); 43 } 44 if (removeValues.length) { 45 pl = pl.concat(removeValues.map(function(v) { 46 return v.remove(); 47 }, this)); 48 } 49 if (pl.length) { 50 new PromiseList(pl).addCallback(hitch(this, function() { 51 this[self.addedKey].length = 0; 52 this[self.removedKey].length = 0; 53 this[self.loadedKey] = true; 54 next(); 55 })); 56 } else { 57 this[self.loadedKey] = true; 58 next(); 59 } 60 } else { 61 next(); 62 } 63 }, 64 65 _preRemove : function(next, self) { 66 if (self.filter) next(); 67 if (!this[self.loadedKey] && !self.isEager()) { 68 this[self.name].then(hitch(this, function() { 69 var values = this[self.name]; 70 values.forEach(function(v) { 71 v.remove(); 72 }); 73 next(); 74 })); 75 } else { 76 var values = this[self.name]; 77 values.forEach(function(v) { 78 v.remove(); 79 }); 80 next(); 81 } 82 83 }, 84 85 _postSave : function(next, self) { 86 self.diff.call(this, next, self); 87 }, 88 89 _postUpdate : function(next, self) { 90 self.diff.call(this, next, self); 91 }, 92 93 _postLoad : function(next, self) { 94 if (self.isEager()) { 95 self.fetch(this).then(hitch(this, function(results) { 96 this[self.loadedKey] = true; 97 results = results.map(function(result) { 98 var m = result; 99 m.__isNew = false; 100 return m; 101 }); 102 this["_" + self.name] = results; 103 next(); 104 })); 105 } else { 106 next(); 107 } 108 }, 109 110 _getter : function(self) { 111 //if we have them return them; 112 if (this[self.loadedKey]) return this["_" + self.name]; 113 114 //Else we dont have 115 if (this.isNew) throw new Error("Model is a new object and no associations have been fetched"); 116 var retPromise = new Promise(); 117 self.fetch(this).then(hitch(this, function(results) { 118 this[self.loadedKey] = true; 119 results = results.map(function(result) { 120 var m = result; 121 m.__isNew = false; 122 return m; 123 }); 124 this["_" + self.name] = results; 125 retPromise.callback(results); 126 }), hitch(retPromise, "errback")); 127 return retPromise; 128 }, 129 130 _setter : function(vals, self) { 131 if (this.isNew) { 132 vals.forEach(function(v, i) { 133 if (!(v instanceof self.model)) { 134 vals[i] = new self.model(v); 135 } 136 }, this); 137 this["_" + self.name] = vals; 138 this[self.addedKey] = this[self.addedKey].concat(vals); 139 this[self.loadedKey] = true; 140 } else { 141 //set the pk on each value 142 vals.forEach(function(v, i) { 143 v[self.rightKey] = this[self.leftKey]; 144 if (!(v instanceof self.model)) { 145 vals[i] = new self.model(v); 146 } 147 }, this); 148 this[self.removedKey] = this[self.removedKey].concat(this["_" + self.name]); 149 this[self.addedKey] = this[self.addedKey].concat(vals); 150 this.__isChanged = true; 151 this["_" + self.name] = vals; 152 } 153 154 }, 155 156 /** 157 *SEE {@link _Association#inject}. 158 * </br> 159 *Adds the following methods to each model. 160 * <ul> 161 * <li>add<ModelName> - add an association</li> 162 * <li>add<ModelsName>s - add multiple associations</li> 163 * <li>remove<ModelName> - remove an association</li> 164 * <li>splice<ModelName>s - splice a number of associations</li> 165 * </ul> 166 **/ 167 inject : function(parent, name) { 168 this.super(arguments); 169 this.removedKey = "__removed" + name + ""; 170 this.addedKey = "__added_" + name + ""; 171 parent.prototype[this.removedKey] = []; 172 parent.prototype[this.addedKey] = []; 173 if (!this.filter) { 174 var self = this; 175 var singular = name, m; 176 if ((m = name.match(/(\w+)s$/)) != null) { 177 singular = m[1]; 178 } 179 var addName = "add" + singular.charAt(0).toUpperCase() + singular.slice(1); 180 var addNames = "add" + name.charAt(0).toUpperCase() + name.slice(1); 181 var removeName = "remove" + singular.charAt(0).toUpperCase() + singular.slice(1); 182 var spliceName = "splice" + name.charAt(0).toUpperCase() + name.slice(1); 183 parent.prototype[addName] = function(item) { 184 if (!this.isNew) { 185 item[self.rightKey] = this[self.leftKey]; 186 if (!(item instanceof self.model)) { 187 item = new self.model(item); 188 } 189 if (!this[self.loadedKey]) { 190 var ret = new Promise(); 191 this[name].then(hitch(this, function() { 192 this["_" + name].push(item); 193 this[self.addedKey].push(item); 194 this.__isChanged = true; 195 ret.callback(); 196 }), hitch(ret, "errback")); 197 return ret; 198 } 199 } else { 200 if (!(item instanceof self.model)) { 201 item = new self.model(item); 202 } 203 } 204 this[self.addedKey].push(item); 205 this.__isChanged = true; 206 return this; 207 }; 208 209 parent.prototype[addNames] = function(items) { 210 if (!this.isNew && !this[self.loadedKey]) { 211 212 var ret = new Promise(); 213 this[name].then(hitch(this, function() { 214 items.forEach(hitch(this, addName)); 215 ret.callback(); 216 })); 217 return ret; 218 } 219 items.forEach(hitch(this, addName)); 220 return this; 221 }; 222 223 parent.prototype[removeName] = function(index) { 224 return this[spliceName](index, index + 1); 225 }; 226 227 //if remove is specified then the item is deleted from db, 228 parent.prototype[spliceName] = function(start, end) { 229 var items = this[name]; 230 if (!this[self.loadedKey]) { 231 var ret = new Promise(); 232 items.then(hitch(this, function() { 233 var items = this["_" + name].splice(start, end - start); 234 if (items && items.length) { 235 this[self.removedKey] = this[self.removedKey].concat(items); 236 } 237 this.__isChanged = true; 238 ret.callback(); 239 }), hitch(ret, "errback")); 240 return ret; 241 } else { 242 items = items.splice(start, end - start); 243 if (items && items.length) { 244 this[self.removedKey] = this[self.removedKey].concat(items); 245 } 246 } 247 this.__isChanged = true; 248 return this; 249 }; 250 } 251 } 252 } 253 });