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