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