1 var comb = require("comb"); 2 3 var fetch = { 4 LAZY : "lazy", 5 EAGER : "eager" 6 }; 7 8 /** 9 * @class Base class for all associations. 10 * 11 * </br> 12 * <b>NOT to be instantiated directly</b> 13 * Its just documented for reference. 14 * 15 * @name _Association 16 * @param {Object} options 17 * @param {String} options.model a string to look up the model that we are associated with 18 * @param {Function} options.filter a callback to find association if a filter is defined then 19 * the association is read only 20 * @param {Object} options.key object with left key and right key 21 * @param {String|Object} options.orderBy<String|Object> - how to order our association @see Dataset.order 22 * @param {fetch.EAGER|fetch.LAZY} options.fetchType the fetch type of the model if fetch.Eager is supplied then 23 * the associations are automatically filled, if fetch.Lazy is supplied 24 * then a promise is returned and is called back with the loaded models 25 * @property {Model} model the model associatied with this association. 26 * */ 27 module.exports = exports = comb.define(null, { 28 /**@ignore*/ 29 instance : { 30 /**@lends _Association.prototype*/ 31 32 //Our associatied model 33 _model : null, 34 35 /** 36 * The key on this models class to look up 37 */ 38 leftKey : null, 39 40 /** 41 * The join key to look up on our associated model 42 */ 43 rightKey : null, 44 45 /** 46 * Fetch type 47 */ 48 fetchType : fetch.LAZY, 49 50 /**how to order our association*/ 51 orderBy : null, 52 53 /**Our filter method*/ 54 filter : null, 55 56 /** 57 * 58 *Method to call to look up association, 59 *called after the model has been filtered 60 **/ 61 _fetchMethod : "all", 62 63 //constructor 64 constructor : function(options, moose) { 65 if (!options.model) throw new Error("Model is required for oneToMany association"); 66 this._model = options.model; 67 this.moose = moose; 68 this.orderBy = options.orderBy; 69 if (options.filter && typeof options.filter == "function") { 70 this.filter = options.filter; 71 } else { 72 for (var i in options.key) { 73 this.leftKey = i; 74 this.rightKey = options.key[i]; 75 } 76 } 77 this.fetchType = options.fetchType || fetch.LAZY; 78 }, 79 80 /** 81 * @return {Boolean} true if the association is eager. 82 */ 83 isEager : function() { 84 return this.fetchType == fetch.EAGER; 85 }, 86 87 /** 88 *Filters our associated dataset to load our association. 89 * 90 *@return {Dataset} the dataset with all filters applied. 91 **/ 92 _filter : function(parent) { 93 if (!this.filter) { 94 var q = {}; 95 q[this.rightKey] = parent[this.leftKey]; 96 if (!this.orderBy) { 97 this.orderBy = this.model.primaryKey; 98 } 99 return this.model.filter(q).order(this.orderBy); 100 } else { 101 return this.filter.call(parent); 102 } 103 }, 104 105 106 /** 107 *Filters and loads our association. 108 * 109 *@return {comb.Promise} Called back with the associations. 110 */ 111 fetch : function(parent) { 112 return this._filter(parent)[this._fetchMethod](); 113 }, 114 115 //todo: change hook methods to be in the association scope 116 117 /** 118 * Middleware called before a model is removed. 119 * </br> 120 * <b> This is called in the scope of the model</b> 121 * @param {Function} next function to pass control up the middleware stack. 122 * @param {_Association} self reference to the Association that is being acted up. 123 */ 124 _preRemove : function(next, self) { 125 next(); 126 }, 127 128 /** 129 * Middleware called aft era model is removed. 130 * </br> 131 * <b> This is called in the scope of the model</b> 132 * @param {Function} next function to pass control up the middleware stack. 133 * @param {_Association} self reference to the Association that is being called. 134 */ 135 _postRemove : function(next, self) { 136 next(); 137 }, 138 139 /** 140 * Middleware called before a model is saved. 141 * </br> 142 * <b> This is called in the scope of the model</b> 143 * @param {Function} next function to pass control up the middleware stack. 144 * @param {_Association} self reference to the Association that is being called. 145 */ 146 _preSave : function(next, self) { 147 next(); 148 }, 149 150 /** 151 * Middleware called after a model is saved. 152 * </br> 153 * <b> This is called in the scope of the model</b> 154 * @param {Function} next function to pass control up the middleware stack. 155 * @param {_Association} self reference to the Association that is being called. 156 */ 157 _postSave : function(next, self) { 158 next(); 159 }, 160 161 /** 162 * Middleware called before a model is updated. 163 * </br> 164 * <b> This is called in the scope of the model</b> 165 * @param {Function} next function to pass control up the middleware stack. 166 * @param {_Association} self reference to the Association that is being called. 167 */ 168 _preUpdate : function(next, self) { 169 next(); 170 }, 171 172 /** 173 * Middleware called before a model is updated. 174 * </br> 175 * <b> This is called in the scope of the model</b> 176 * @param {Function} next function to pass control up the middleware stack. 177 * @param {_Association} self reference to the Association that is being called. 178 */ 179 _postUpdate : function(next, self) { 180 next(); 181 }, 182 183 /** 184 * Middleware called before a model is loaded. 185 * </br> 186 * <b> This is called in the scope of the model</b> 187 * @param {Function} next function to pass control up the middleware stack. 188 * @param {_Association} self reference to the Association that is being called. 189 */ 190 _preLoad : function(next, self) { 191 next(); 192 }, 193 194 /** 195 * Middleware called after a model is loaded. 196 * </br> 197 * <b> This is called in the scope of the model</b> 198 * @param {Function} next function to pass control up the middleware stack. 199 * @param {_Association} self reference to the Association that is being called. 200 */ 201 _postLoad : function(next, self) { 202 next(); 203 }, 204 205 /** 206 * Alias used to explicitly set an association on a model. 207 * @param {*} val the value to set the association to 208 * @param {_Association} self reference to the Association that is being called. 209 */ 210 _setter : function(val, self) { 211 this["_" + self.name] = val; 212 }, 213 214 /** 215 * Alias used to explicitly get an association on a model. 216 * @param {_Association} self reference to the Association that is being called. 217 */ 218 _getter : function(self) { 219 return this["_" + self.name]; 220 }, 221 222 /** 223 * Method to inject functionality into a model. This method alters the model 224 * to prepare it for associations, and initializes all required middleware calls 225 * to fulfill requirements needed to loaded the associations. 226 * 227 * @param {Model} parent the model that is having an associtaion set on it. 228 * @param {String} name the name of the association. 229 */ 230 inject : function(parent, name) { 231 this.loadedKey = "__" + name + "_loaded"; 232 this.name = name; 233 var self = this; 234 235 parent.prototype["_" + name] = []; 236 parent.prototype[this.loadedKey] = false; 237 238 parent.prototype.__defineGetter__(name, function() { 239 return self._getter.call(this, self); 240 }); 241 parent.prototype.__defineGetter__(name + "Dataset", function() { 242 return this._filter(); 243 }); 244 if (!this.filter) { 245 //define a setter because we arent read only 246 parent.prototype.__defineSetter__(name, function(vals) { 247 self._setter.call(this, vals, self); 248 }); 249 } 250 251 //set up all callbacks 252 ["pre", "post"].forEach(function(op) { 253 ["save", "update", "remove", "load"].forEach(function(type) { 254 parent[op](type, function(next) { 255 return self["_" + op + type.charAt(0).toUpperCase() + type.slice(1)].call(this, next, self); 256 }); 257 }, this); 258 }, this); 259 }, 260 261 getters : { 262 263 //Returns our model 264 model : function() { 265 return this.moose.getModel(this._model); 266 } 267 } 268 }, 269 270 static : { 271 /**@lends _Association*/ 272 273 /** 274 * Fetch types 275 */ 276 fetch : { 277 278 LAZY : "lazy", 279 280 EAGER : "eager" 281 } 282 } 283 });