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