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 });