      1 var comb = require("comb"),
      2         plugins = require("./plugins"),
      3         AssociationPlugin = plugins.AssociationPlugin,
      4         QueryPlugin = plugins.QueryPlugin,
      5         Promise = comb.Promise,
      6         PromiseList = comb.PromiseList;
      7
      8
      9 var Model = comb.define([QueryPlugin, AssociationPlugin, comb.plugins.Middleware], {
     10     instance : {
     11     /**
     12      * @lends Model.prototype
     13      */
     14         /**
     15          * The table this model represent
     16          * @type moose.Table
     17          * */
     18         table : null,
     19
     20         /**
     21          * moose  - read only
     22          *
     23          * @type moose
     24          */
     25         moose : null,
     26
     27         /**
     28          * The database type such as mysql
     29          *
     30          * @typre String
     31          *
     32          * */
     33         type : null,
     34
     35         /**
     36          * Whether or not this model is new
     37          * */
     38         __isNew  : true,
     39
     40         /**
     41          * Signifies if the model has changed
     42          * */
     43         __isChanged : false,
     44
     45         /**
     46          * Base class for all models.
     47          * <p>This is used through {@link moose.addModel}, <b>NOT directly.</b></p>
     48          *
     49          * @constructs
     50          * @augments moose.plugins.QueryPlugin
     51          * @augments moose.plugins.AssociationPlugin
     52          * @augments comb.plugins.Middleware
     53          *
     54          * @param {Object} columnValues values of each column to be used by this Model.
     55          *
     56          * @property {*} primaryKeyValue the value of this models primaryKey
     57          * @property {Boolean} isNew true if this model is new and does not exist in the database.
     58          * @property {Boolean} isChanged true if the model has been changed and not saved.
     59          * */
     60         constructor : function(options) {
     61             if (options) {
     62                 for (var i in options) {
     63                     this[i] = options[i];
     64                 }
     65             }
     66         },
     67
     68         /**
     69          *
     70          * Validate values against the model {@link moose.Table#validate}
     71          *
     72          * @returns {Boolean} true if the values are valid
     73          * */
     74         isValid : function(options) {
     75             try {
     76                 return this.table.validate.apply(this.table, arguments);
     77             } catch(e) {
     78                 return false;
     79             }
     80         },
     81
     82         /**
     83          * Convert this model to an object, containing column, value pairs.
     84          *
     85          * @return {Object} the object version of this model.
     86          **/
     87         toObject : function() {
     88             var columns = this.table.columns, ret = {};
     89             for (var i in columns) {
     90                 ret[i] = this[i];
     91             }
     92             return ret;
     93         },
     94
     95         /**
     96          * Convert this model to JSON, containing column, value pairs.
     97          *
     98          * @return {JSON} the JSON version of this model.
     99          **/
    100         toJson : function() {
    101             return JSON.stringify(this.toObject(), null, 4);
    102         },
    103
    104         getters : {
    105             /*Returns my actual primary key value*/
    106             primaryKeyValue : function() {
    107                 return this[this.primaryKey];
    108             },
    109
    110             /*Return if Im a new object*/
    111             isNew : function() {
    112                 return this.__isNew;
    113             },
    114
    115             /*Return if Im changed*/
    116             isChanged : function() {
    117                 return this.__isChanged;
    118             }
    119
    120         }
    121     },
    122
    123     static : {
    124
    125     /**@lends Model*/
    126
    127         /**
    128          * The table that this Model represents.
    129          */
    130         table : null,
    131
    132         /**
    133          * moose  - read only
    134          *
    135          * @type moose
    136          */
    137         moose : null,
    138
    139         /**
    140          *
    141          * Validate values against the Model {@link moose.Table#validate}
    142          *
    143          * @returns {Boolean} true if the values are valid
    144          * */
    145         isValid : function(options) {
    146             try {
    147                 return this.table.validate(options);
    148             } catch(e) {
    149                 //we dont actually want to throw an error just
    150                 //return false
    151                 return false;
    152             }
    153         },
    154
    155         /**
    156          * Create a new model instance from sql values.
    157          *
    158          * @example
    159          *
    160          * var myModel = Model.load({
    161          *    myDate : "1999-01-01",
    162          *    intValue : "1"
    163          * });
    164          *
    165          * //intValue is converted to a number.
    166          * myModel.intValue => 1,
    167          * //mydate is converted to a date
    168          * myModel.myDate => new Date(1999,01,01);
    169          *
    170          * @param {Object} values object containing the values to initialize the model with.
    171          *
    172          * @returns {Model} instantiated model initialized with the values passed in.
    173          * */
    174         load : function(values) {
    175             //load an object from an object
    176             var promise = new Promise();
    177             var m = new this(this.table.fromSql(values));
    178             m._hook("post", "load").then(comb.hitch(promise, "callback", m));
    179             return promise;
    180         },
    181
    182         /**
    183          * Create a new model initialized with the specified values.
    184          *
    185          * @param {Object} values  the values to initialize the model with.
    186          *
    187          * @returns {Model} instantiated model initialized with the values passed in.
    188          */
    189         create : function(values) {
    190             //load an object from an object
    191             return new this(values);
    192         }
    193     }
    194
    195 });
    196
    197 /*Adds a setter to an object*/
    198 var addSetter = function(name, col) {
    199     return function(val) {
    200         col.check(val);
    201         if (!this.isNew) this.__isChanged = true;
    202         this["_" + name] = val;
    203     };
    204 };
    205
    206 /*Adds a getter to an object*/
    207 var addGetter = function(name) {
    208     return function() {
    209         return this["_" + name];
    210     };
    211 };
    212
    213 exports.create = function(table, moose, modelOptions) {
    214     modelOptions = modelOptions || {};
    215     //Create the default proto
    216     var proto = {
    217         instance : {
    218             table : table,
    219             moose : moose,
    220             _hooks : ["save", "update", "remove", "load"],
    221             __hooks : {pre : {}, post : {}},
    222             getters : {
    223                 primaryKey : function() {
    224                     return this.table.pk;
    225                 }
    226             },
    227             setters : {}
    228         },
    229         static : {
    230             getters : {
    231                 table : function() {
    232                     return table;
    233                 },
    234
    235                 tableName : function() {
    236                     return table.tableName;
    237                 },
    238
    239                 moose : function() {
    240                     return moose;
    241                 },
    242
    243                 type : function() {
    244                     return table.type;
    245                 }
    246             },
    247             setters : {}
    248         }
    249     };
    250     var instance = proto.instance,
    251             getters = instance.getters,
    252             setters = instance.setters,
    253             static = proto.static,
    254             staticGetters = proto.getters,
    255             staticSetters = proto.setters,
    256             modelInstance = modelOptions.instance || {},
    257             modelGetters = modelInstance.getters || {},
    258             modelSetters = modelInstance.setters || {},
    259             modelStatic = modelOptions.static || {},
    260             modelStaticGetters = modelStatic.getters || {},
    261             modelStaticSetters = modelStatic.setters || {};
    262     //remove these so they dont override our proto
    263     delete modelInstance.getters;
    264     delete modelInstance.setters;
    265     delete modelStatic.getters;
    266     delete modelStatic.setters;
    267     //Mixin the column setter/getters
    268     var columns = table.columns;
    269     for (var i in columns) {
    270         var col = columns[i];
    271         getters[i] = addGetter(i);
    272         setters[i] = addSetter(i, col);
    273     }
    274
    275     //Define super and mixins
    276     //By Default we include Query and Association plugins
    277     var parents = [Model].concat(modelOptions.plugins || []);
    278
    279     //START MERGE OF PASSED PROTO
    280     var merge = comb.merge;
    281
    282     merge(instance, modelInstance);
    283     merge(getters, modelGetters);
    284     merge(setters, modelSetters);
    285     merge(static, modelStatic);
    286     merge(staticGetters, modelStaticGetters);
    287     merge(staticSetters, modelStaticSetters);
    288     //END MERGE OF PASSED PROTO
    289     //Create return model
    290     var model = comb.define(parents, proto);
    291     //mixin pre and post functions
    292     ["pre","post"].forEach(function(op) {
    293         var optionsOp = modelOptions[op];
    294         if (optionsOp) {
    295             for (var i in optionsOp) {
    296                 model[op](i, optionsOp[i]);
    297             }
    298         }
    299     });
    300     return model;
    301 };
    302
    303
    304