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