<commit_before>'use strict';

var util = require('util');
var utilities = require('node-helper-utilities');
var async = require('async');

var OsmosError = require('./util/error');
var Hookable = require('./util/hookable');
var Document = require('./document/');
var drivers = require('./drivers');

var Model = function Model(name, schema, bucket, db, documentClass) {
  this.schema = schema;
  this.bucket = bucket;
  this.db = typeof db == 'string' ? drivers.instance(db) : db;
  this.name = name;

  this.documentClass = null;
  this.documentClassConstructor = documentClass || Document;
  this.hasInitializedDocumentClass = false;

  this.instanceMethods = {};
  this.instanceProperties = {};
  this.transformers = {};

  if(!this.schema.primaryKey) {
    throw new OsmosError('Schema is missing a primary key');
  }

  Hookable.call(this);
};

util.inherits(Model, Hookable);

Model.prototype.hooks = [
  'willCount',
  'didCount',
  'didCreate',
  'willFind',
  'didFind',
  'willFindOne',
  'didFindOne',
  'willGet',
  'didGet',
  'willInitialize',
  'didInitialize',
  'willUpdate',
  'didUpdate',
  'willSave',
  'didSave',
  'willDelete',
  'didDelete'
];

Model.prototype.instanceProperties = {};
Model.prototype.instanceMethods = {};
Model.prototype.transformers = {};

Model.prototype.generateDocumentClass = function() {
  this.documentClass = Document.generateClass(
    this.documentClassConstructor,
    this.instanceProperties,
    this.instanceMethods,
    this.transformers,
    this.schema.transformers
  );

  Object.seal(this.instanceMethods);
  Object.seal(this.instanceProperties);
  Object.seal(this.transformers);
};

Model.prototype._initialize = function(data, cb) {
  if (!this.documentClass)
    this.generateDocumentClass();

  var args = {
    data: data,
    documentClass: this.documentClass
  };

  var self = this;

  async.series(
    [
      function(cb) {
        self.callHook('willInitialize', args, cb);
      },

      function(cb) {
        args.document = new args.documentClass(self, args.data);
        cb(null);
      },

      function(cb) {
        self.callHook('didInitialize', args, cb);
      }
    ],

    function(err) {
      cb(err, args.document);
    }
  );
};

Model.prototype.create = function(cb) {
  var self = this;

  async.waterfall([
    function(cb) {
      self.db.create(self, cb);
    },

    function(data, cb) {
      self._initialize(data, function(err, doc) {
        if (err) return cb(err);

        var props = self.schema.__raw__.properties;

        Object.keys(props).forEach(function(key) {
          var prop = props[key];

          if(prop.hasOwnProperty('default')) {
           doc[key] = utilities.clone(prop.default);
          }

        });

        self.callHook('didCreate', doc, function(err) {
          cb(err, doc);
        });
      });
    }
  ], cb);
};

Model.prototype.get = function(key, cb) {
  var self = this;

  var args = {
    key: key,
    stop: false
  };

  this.performHookCycle(
    'Get',

    args,

    function(cb) {
      if (args.stop) return cb(null);

      self.db.get(self, args.key, function(err, data) {
        if (err) return cb(err);
        if (data) {
          return self._initialize(data, function(err, doc) {
            args.document = doc;
            cb(null);
          });
        }

        cb(null);
      });
    },

    function(err) {
      return cb(err, args.document);
    }
  );
};

Model.prototype.getFromImmediateData = function(data, cb) {
  var self = this;

  var args = {
    key: data[self.schema.primaryKey],
    stop: false
  };

  this.performHookCycle(
    'Get',

    args,

    function(cb) {
      if (args.stop) {
        setImmediate(function() {
          cb(null);
        });
      } else {
        self._initialize(data, function(err, doc) {
          args.document = doc;
          cb(null);
        });
      }
    },

    function(err) {
      return cb(err, args.document);
    }
  );
};

Model.prototype.getOrCreate = function(key, cb) {
  var self = this;

  this.get(key, function(err, doc) {
    if (err || doc) {
      cb(err, doc, false);
      return;
    }

    self.create(function(err, doc) {
      doc.primaryKey = key;

      cb(null, doc, true);
    });
  });
};

Model.prototype.find = function(spec, cb) {
  var self = this;

  var args = {
    spec: spec,
    stop: false
  };

  this.performHookCycle(
    'Find',

    args,

    function(cb) {
      if (args.stop) {
        setImmediate(function() {
          cb(null);
        });
      } else {
        self.db.find(self, args.spec, function(err, docs) {
          if (docs) {
            async.map(
              docs,

              function(doc, cb) {
                self._initialize(doc, cb);
              },

              function(err, docs) {
                args.documents = docs;
                cb(err);
              }
            );
          } else {
            cb(err);
          }
        });
      }
    },

    function(err) {
      return cb(err, args.documents);
    }
  );
};

Model.prototype.findLimit = function(spec, start, limit, cb) {
  var self = this;

  var args = {
    spec: spec,
    stop: false
  };

  this.performHookCycle(
    'Find',

    args,

    function(cb) {
      self.db.findLimit(self, args.spec, start, limit, function(err, result) {
        args.count = result.count;

        if (result.docs) {
          async.map(
            result.docs,

            function(doc, cb) {
              self._initialize(doc, cb);
            },

            function(err, docs) {
              args.documents = docs;
              cb(err);
            }
          );
        } else {
          cb(err);
        }
      });
    },

    function(err) {
      return cb(err, {
        count: args.count,
        start: start,
        limit: limit,
        docs : args.documents
      });
    }
  );
};

Model.prototype.count = function(spec, done) {
  var args = {
    spec: spec,
    stop: false
  };

  var self = this;

  this.performHookCycle(
    'Count',
    args,
    function(next) {
      self.db.count(self, args.spec, function(err, count) {
        args.count = count;
        next(null);
      });
    },
    function(err) {
      done(err, args.count);
    }
  );
};

Model.prototype.findOne = function (spec, cb) {
  var self = this;

  var args = {
    spec: spec,
    stop: false
  };

  this.performHookCycle(
    'FindOne',

    args,

    function(cb) {
      self.db.findOne(self, args.spec, function(err, data) {
        if (data) {
          self._initialize(data, function(err, doc) {
            args.document = doc;
            cb(err);
          });
        } else {
          cb(err);
        }
      });
    },

    function(err) {
      return cb(err, args.document);
    }
  );
};

Model.prototype._update = function(doc, payload, cb) {
  var self = this;

  var args = {
    doc: doc,
    payload: payload,
    stop: false
  };

  self.performHookCycle(
    'Update',

    args,

    function(cb) {
      doc._update(payload, cb);
    },

    cb
  );
};

Model.prototype._save = function(doc, cb) {
  var self = this;

  var args = {
    doc: doc,
    stop: false
  };

  async.series([
    function(cb) {
      self.schema.validateDocument(args.doc, function(err) {
        if (err) {
          return cb(err);
        }

        args.payload = args.doc.toRawJSON ? args.doc.toRawJSON() : args.doc;
        cb(null);
      });
    },

    function(cb) {
      self.performHookCycle(
        'Save',

        args,

        function(cb) {
          if (args.doc.primaryKey) {
            self.db.put(args.doc, args.payload, cb);
          } else {
            self.db.post(args.doc, args.payload, cb);
          }
        },
        cb
      );
    }
  ],
  cb
  );
};

Model.prototype._delete = function(doc, cb) {
  if (!doc.primaryKey) {
    //throw new OsmosError('This document does not have a primary key');
    cb(new OsmosError('This document does not have a primary key'));
  }

  var self = this;

  var args = {
    doc: doc,
    stop: false
  };

  this.performHookCycle(
    'Delete',
    args,
    function(cb) {
      var spec = {};

      spec[args.doc.model.schema.primaryKey] = args.doc.primaryKey;

      self.db.del(self, spec, cb);
    },

    cb
  );
};

Object.defineProperty(
  Model.prototype,
  'updateableProperties',
  {
    enumerable: true,
    get: function getUpdateablePropertiesOfModel() {
      return this._updateableProperties;
    },

    set: function setUpdateablePropertiesOfModel(value) {
      this._updateableProperties = value;

      if (value.forEach) {
        this.updateablePropertiesHash = {};

        value.forEach(function(prop) {
          this.updateablePropertiesHash[prop] = 1;
        }, this);
      } else {
        this.updateablePropertiesHash = value;
      }
    }
  }
);

module.exports = Model;
<commit_msg>code: remove support  for documentClass constructor passthrough<commit_after>'use strict';

var util = require('util');
var utilities = require('node-helper-utilities');
var async = require('async');

var OsmosError = require('./util/error');
var Hookable = require('./util/hookable');
var Document = require('./document/');
var drivers = require('./drivers');

var Model = function Model(name, schema, bucket, db) {
  this.schema = schema;
  this.bucket = bucket;
  this.db = typeof db == 'string' ? drivers.instance(db) : db;
  this.name = name;

  this.documentClass = null;
  this.documentClassConstructor = Document;
  this.hasInitializedDocumentClass = false;

  this.instanceMethods = {};
  this.instanceProperties = {};
  this.transformers = {};

  if(!this.schema.primaryKey) {
    throw new OsmosError('Schema is missing a primary key');
  }

  Hookable.call(this);
};

util.inherits(Model, Hookable);

Model.prototype.hooks = [
  'willCount',
  'didCount',
  'didCreate',
  'willFind',
  'didFind',
  'willFindOne',
  'didFindOne',
  'willGet',
  'didGet',
  'willInitialize',
  'didInitialize',
  'willUpdate',
  'didUpdate',
  'willSave',
  'didSave',
  'willDelete',
  'didDelete'
];

Model.prototype.instanceProperties = {};
Model.prototype.instanceMethods = {};
Model.prototype.transformers = {};

Model.prototype.generateDocumentClass = function() {
  this.documentClass = Document.generateClass(
    this.documentClassConstructor,
    this.instanceProperties,
    this.instanceMethods,
    this.transformers,
    this.schema.transformers
  );

  Object.seal(this.instanceMethods);
  Object.seal(this.instanceProperties);
  Object.seal(this.transformers);
};

Model.prototype._initialize = function(data, cb) {
  if (!this.documentClass)
    this.generateDocumentClass();

  var args = {
    data: data,
    documentClass: this.documentClass
  };

  var self = this;

  async.series([
    function(cb) {
      self.callHook('willInitialize', args, cb);
    },

    function(cb) {
      args.document = new args.documentClass(self, args.data);
      cb(null);
    },

    function(cb) {
      self.callHook('didInitialize', args, cb);
    }
  ],

  function(err) {
    cb(err, args.document);
  });
};

Model.prototype.create = function(cb) {
  var self = this;

  async.waterfall([
    function(cb) {
      self.db.create(self, cb);
    },

    function(data, cb) {
      self._initialize(data, function(err, doc) {
        if (err) return cb(err);

        var props = self.schema.__raw__.properties;

        Object.keys(props).forEach(function(key) {
          var prop = props[key];

          if(prop.hasOwnProperty('default')) {
           doc[key] = utilities.clone(prop.default);
          }

        });

        self.callHook('didCreate', doc, function(err) {
          cb(err, doc);
        });
      });
    }
  ], cb);
};

Model.prototype.get = function(key, cb) {
  var self = this;

  var args = {
    key: key
  };

  this.performHookCycle(
    'Get',

    args,

    function(cb) {
      self.db.get(self, args.key, function(err, data) {
        if (err) return cb(err);
        if (data) {
          return self._initialize(data, function(err, doc) {
            args.document = doc;
            cb(null);
          });
        }

        cb(null);
      });
    },

    function(err) {
      return cb(err, args.document);
    }
  );
};

Model.prototype.getFromImmediateData = function(data, cb) {
  var self = this;

  var args = {
    key: data[self.schema.primaryKey]
  };

  this.performHookCycle(
    'Get',

    args,

    function(cb) {
      self._initialize(data, function(err, doc) {
        args.document = doc;
        cb(null);
      });
    },

    function(err) {
      return cb(err, args.document);
    }
  );
};

Model.prototype.getOrCreate = function(key, cb) {
  var self = this;

  this.get(key, function(err, doc) {
    if (err || doc) {
      cb(err, doc, false);
      return;
    }

    self.create(function(err, doc) {
      doc.primaryKey = key;

      cb(null, doc, true);
    });
  });
};

Model.prototype.find = function(spec, cb) {
  var self = this;

  var args = {
    spec: spec
  };

  this.performHookCycle(
    'Find',

    args,

    function(cb) {
      self.db.find(self, args.spec, function(err, docs) {
        if (docs) {
          async.map(
            docs,

            function(doc, cb) {
              self._initialize(doc, cb);
            },

            function(err, docs) {
              args.documents = docs;
              cb(err);
            });
        } else {
          cb(err);
        }
      });
    },

    function(err) {
      return cb(err, args.documents);
    }
  );
};

Model.prototype.findLimit = function(spec, start, limit, cb) {
  var self = this;

  var args = {
    spec: spec
  };

  this.performHookCycle(
    'Find',

    args,

    function(cb) {
      self.db.findLimit(self, args.spec, start, limit, function(err, result) {
        args.count = result.count;

        if (result.docs) {
          async.map(
            result.docs,

            function(doc, cb) {
              self._initialize(doc, cb);
            },

            function(err, docs) {
              args.documents = docs;
              cb(err);
            }
          );
        } else {
          cb(err);
        }
      });
    },

    function(err) {
      return cb(err, {
        count: args.count,
        start: start,
        limit: limit,
        docs : args.documents
      });
    }
  );
};

Model.prototype.count = function(spec, done) {
  var args = {
    spec: spec
  };

  var self = this;

  this.performHookCycle(
    'Count',
    args,
    function(next) {
      self.db.count(self, args.spec, function(err, count) {
        args.count = count;
        next(null);
      });
    },
    function(err) {
      done(err, args.count);
    }
  );
};

Model.prototype.findOne = function (spec, cb) {
  var self = this;

  var args = {
    spec: spec
  };

  this.performHookCycle(
    'FindOne',

    args,

    function(cb) {
      self.db.findOne(self, args.spec, function(err, data) {
        if (data) {
          self._initialize(data, function(err, doc) {
            args.document = doc;
            cb(err);
          });
        } else {
          cb(err);
        }
      });
    },

    function(err) {
      return cb(err, args.document);
    }
  );
};

Model.prototype._update = function(doc, payload, cb) {
  var self = this;

  var args = {
    doc: doc,
    payload: payload
  };

  self.performHookCycle(
    'Update',

    args,

    function(cb) {
      doc._update(payload, cb);
    },

    cb
  );
};

Model.prototype._save = function(doc, cb) {
  var self = this;

  var args = {
    doc: doc
  };

  async.series([
    function(cb) {
      self.schema.validateDocument(args.doc, function(err) {
        if (err) {
          return cb(err);
        }

        args.payload = args.doc.toRawJSON ? args.doc.toRawJSON() : args.doc;
        cb(null);
      });
    },

    function(cb) {
      self.performHookCycle(
        'Save',

        args,

        function(cb) {
          if (args.doc.primaryKey) {
            self.db.put(args.doc, args.payload, cb);
          } else {
            self.db.post(args.doc, args.payload, cb);
          }
        },
        cb
      );
    }
  ],
  cb
  );
};

Model.prototype._delete = function(doc, cb) {
  if (!doc.primaryKey) {
    //throw new OsmosError('This document does not have a primary key');
    cb(new OsmosError('This document does not have a primary key'));
  }

  var self = this;

  var args = {
    doc: doc
  };

  this.performHookCycle(
    'Delete',
    args,
    function(cb) {
      var spec = {};

      spec[args.doc.model.schema.primaryKey] = args.doc.primaryKey;

      self.db.del(self, spec, cb);
    },

    cb
  );
};

Object.defineProperty(
  Model.prototype,
  'updateableProperties',
  {
    enumerable: true,
    get: function getUpdateablePropertiesOfModel() {
      return this._updateableProperties;
    },

    set: function setUpdateablePropertiesOfModel(value) {
      this._updateableProperties = value;

      if (value.forEach) {
        this.updateablePropertiesHash = {};

        value.forEach(function(prop) {
          this.updateablePropertiesHash[prop] = 1;
        }, this);
      } else {
        this.updateablePropertiesHash = value;
      }
    }
  }
);

module.exports = Model;
<|endoftext|>


<commit_before>"use strict";

var express = require('express');
var utils = require('./utils');

module.exports = Model;

/**
 * Constructor.
 *
 * @param model - The mongoose model that is being restified.
 */
function Model(restifier, model) {
  this.restifier = restifier;
  this.model = model;

  // Special fields
  var id;
  var restricted = this.restricted = [];
  model.schema.eachPath(function(pathName, path) {
    if (path.options.restricted) {
      restricted.push(pathName);
    }
    if (path.options.id) {
      id = pathName;
    }
  });
  this.id = id || '_id';

  this.modifiers = [];
  this.filters = {};
  this.transformers = [];

  this.transform(function(req, doc) {
    restricted.filter(function(path) {
      delete doc[path];
    });
  });
}

/**
 * Modifies a query parameter.
 *
 * @param param - The param to modify
 * @paran fn(req, value) - The modifier function. Should return the new value.
 */
Model.prototype.modifyParam = function(param, fn) {
  this.modifiers.push({
    param: param,
    fn: fn
  });
  return this;
};

/**
 * Modifies the limit parameter.
 *
 * @param num - The maximum number of documents that can be returned.
 */
Model.prototype.limit = function(num) {
  return this.modifyParam('limit', function(req, value) {
    return value && !isNaN(value) ? Math.min(num, value) : num;
  });
};

/**
 * Adds a filter to this model.
 *
 * @param name - The name of the filter.
 * @param filter(req, query, params...) - The filter function that will be run on queries.
 */
Model.prototype.filter = function(name, filter) {
  this.filters[name] = filter;
  return this;
};

/**
 * Adds a transformer to this model.
 *
 * @param transformer(req, doc) - The transformer
 */
Model.prototype.transform = function(transformer) {
  this.transformers.push(transformer);
};

Model.prototype.applyTransforms = function(req, doc) {
  var jsoned = doc.toObject();
  this.transformers.filter(function(tf) {
    tf(req, jsoned);
  });
  return jsoned;
};

/**
 * Serves this model on a RESTful API.
 *
 * @param router - The Express router to serve the model on.
 */
Model.prototype.serve = function(router) {
  this.serveModel(router, '/' + this.model.collection.name, this.model);
};

Model.prototype.serveModel = function(router, uriBase, Mod, queryAdd) {
  var thiz = this;
  var uriDoc = uriBase + '/:id';

  router.route(uriBase)
  // Query
  .get(function(req, res) {
    var query = Mod.find();
    var reqQuery = req.query;

    // Parse the query
    // Ensure empty params are treated as null
    Object.keys(reqQuery).map(function(param) {
      if (reqQuery[param] === '') {
        reqQuery[param] = null;
      }
    });

    // Parse comma-delimited params and trim them
    if (reqQuery.populate) {
      reqQuery.populate = reqQuery.populate.split(',');
      reqQuery.populate = reqQuery.populate.map(Function.prototype.call, String.prototype.trim);
    }
    if (reqQuery.sort) {
      reqQuery.sort = reqQuery.sort.split(',');
      reqQuery.sort = reqQuery.sort.map(Function.prototype.call, String.prototype.trim);
      var newVal = {};
      reqQuery.sort.map(function(param) {
        if (param.lastIndexOf('-', 0) === 0) {
          newVal[param.substring(1)] = -1;
        } else {
          newVal[param] = 1;
        }
      });
      reqQuery.sort = newVal;
    }

    // Apply modifiers
    // The try/catch allows us to throw errors in modifiers, if we're
    // doing something like auth.
    try {
      thiz.modifiers.filter(function(mod) {
        reqQuery[mod.param] = mod.fn(req, reqQuery[mod.param]);
        if (reqQuery[mod.param] === false) {
          delete reqQuery[mod.param];
        }
      });
    } catch (err) {
      if (!err.code) {
        throw err;
      }
      return;
    }

    // Apply limit parameter
    if (reqQuery.limit) {
      try {
        if (isNaN(reqQuery.limit)) {
          throw res.error(400, 'Limit must be a number.');
        }
        query.limit(reqQuery.limit);
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    // Apply skip parameter
    if (reqQuery.skip) {
      try {
        if (isNaN(reqQuery.skip)) {
          throw res.error(400, 'Skip must be a number.');
        }
        query.skip(reqQuery.skip);
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    if (reqQuery.populate) {
      try {
        reqQuery.populate.filter(function(toPopulate) {
          if (!Mod.schema.paths.hasOwnProperty(toPopulate)) {
            throw res.error(400, 'Field "' + toPopulate + '" does not exist.');
          }
          if (thiz.restricted.indexOf(toPopulate) !== -1) {
            throw res.error(401, 'Cannot populate restricted field "' + toPopulate + '".');
          }
          query.populate(toPopulate);
        });
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    if (reqQuery.sort) {
      try {
        Object.keys(reqQuery.sort).filter(function(toSort) {
          if (!Mod.schema.paths.hasOwnProperty(toSort)) {
            throw res.error(400, 'Field "' + toSort + '" does not exist.');
          }
          if (thiz.restricted.indexOf(toSort) !== -1) {
            throw res.error(401, 'Cannot sort restricted field "' + toSort + '".');
          }
        });
        query.sort(reqQuery.sort);
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    // Filters
    if (reqQuery.filter) {
      try {
        var filters = utils.parseFilterString(reqQuery.filter);
        filters.filter(function(filter) {
          if (!thiz.filters.hasOwnProperty(filter[0])) {
            throw res.error(400, 'The filter "' + filter + '" does not exist.');
          }
          var filterFn = thiz.filters[filter[0]];

          // Construct our filter arguments
          var args = [];
          args.push(req);
          args.push(query);
          args.push.apply(args, filter.slice(1));

          try {
            filterFn.apply(thiz, args);
          } catch (err) {
            throw res.error(400, 'Could not apply filter "' + filter + '" due to error: ' + err.message);
          }
        });
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    // Fields
    try {
      Mod.schema.eachPath(function(name) {
        if (!reqQuery.hasOwnProperty(name)) {
          return;
        }
        var val = reqQuery[name];

        if (thiz.restricted.indexOf(name) !== -1) {
          throw res.error(401, 'Cannot access restricted field "' + name + '".');
        }
        query.where(name).equals(val);
      });
    } catch (err) {
      if (!err.code) {
        throw err;
      }
      return;
    }

    query.exec(function(err, docs) {
      if (err) {
        throw err;
      }
      res.json(docs.map(function(doc) {
        return thiz.applyTransforms(req, doc);
      }));
    });
  })

  // Create
  .post(function(req, res) {
    var doc = new Mod(req.body);
    doc.save(function(err) {
      if (err) {
        if (err.err.match(/duplicate key error/)) {
          return res.error(409, Mod.modelName + ' already exists.');
        }
        return res.error(500, err.message);
      }
      res.json(thiz.applyTransforms(req, doc));
    });
  });

  router.route(uriDoc)
  // Get
  .get(function(req, res) {
    var query = {};
    query[thiz.id] = req.params.id;
    Mod.findOne(query).exec(function(err, doc) {
      if (err) {
        return res.error(500, err.err);
      }
      if (!doc) {
        return res.error(404, Mod.modelName + ' "' + req.params.id + '" not found.');
      }
      res.json(thiz.applyTransforms(req, doc));
    });
  })

  // Update
  .put(function(req, res) {
    var query = {};
    var body = req.body;
    query[thiz.id] = req.params.id;
    Mod.findOne(query).exec(function(err, doc) {
      if (err) {
        return res.error(500, err.err);
      }
      if (!doc) {
        return res.error(404, Mod.modelName + ' "' + req.params.id + '" not found.');
      }
      for (var prop in body) {
        if (!body.hasOwnProperty(prop)) {
          continue;
        }
        doc[prop] = body[prop];
      }

      doc.save(function(err) {
        if (err) {
          return res.error(500, err);
        }
        return res.json(thiz.applyTransforms(req, doc));
      });
    });
  })

  // Delete
  .delete(function(req, res) {
    var query = {};
    query[thiz.id] = req.params.id;
    Mod.findOne(query).exec(function(err, doc) {
      if (err) {
        return res.error(500, err.err);
      }
      if (!doc) {
        return res.error(404, Mod.modelName + ' "' + req.params.id + '" not found.');
      }
      doc.remove(function(err) {
        if (err) {
          return res.error(500, err);
        }
        return res.json({
          success: true
        });
      });
    });
  });

  return this;
};

/**
 * Creates a middleware of this model.
 */
Model.prototype.middleware = function() {
  var router = express.Router();
  this.serve(router);
  return router;
};
<commit_msg>Add query/doc modifier<commit_after>"use strict";

var express = require('express');
var utils = require('./utils');

module.exports = Model;

/**
 * Constructor.
 *
 * @param model - The mongoose model that is being restified.
 */
function Model(restifier, model) {
  this.restifier = restifier;
  this.model = model;

  // Special fields
  var id;
  var restricted = this.restricted = [];
  model.schema.eachPath(function(pathName, path) {
    if (path.options.restricted) {
      restricted.push(pathName);
    }
    if (path.options.id) {
      id = pathName;
    }
  });
  this.id = id || '_id';

  this.modifiers = [];
  this.filters = {};
  this.transformers = [];

  this.transform(function(req, doc) {
    restricted.filter(function(path) {
      delete doc[path];
    });
  });
}

/**
 * Modifies a query parameter.
 *
 * @param param - The param to modify
 * @paran fn(req, value) - The modifier function. Should return the new value.
 */
Model.prototype.modifyParam = function(param, fn) {
  this.modifiers.push({
    param: param,
    fn: fn
  });
  return this;
};

/**
 * Modifies the limit parameter.
 *
 * @param num - The maximum number of documents that can be returned.
 */
Model.prototype.limit = function(num) {
  return this.modifyParam('limit', function(req, value) {
    return value && !isNaN(value) ? Math.min(num, value) : num;
  });
};

/**
 * Adds a filter to this model.
 *
 * @param name - The name of the filter.
 * @param filter(req, query, params...) - The filter function that will be run on queries.
 */
Model.prototype.filter = function(name, filter) {
  this.filters[name] = filter;
  return this;
};

/**
 * Adds a transformer to this model.
 *
 * @param transformer(req, doc) - The transformer
 */
Model.prototype.transform = function(transformer) {
  this.transformers.push(transformer);
};

Model.prototype.applyTransforms = function(req, doc) {
  var jsoned = doc.toObject();
  this.transformers.filter(function(tf) {
    tf(req, jsoned);
  });
  return jsoned;
};

/**
 * Serves this model on a RESTful API.
 *
 * @param router - The Express router to serve the model on.
 */
Model.prototype.serve = function(router) {
  this.serveModel(router, '/' + this.model.collection.name, this.model);
};

Model.prototype.serveModel = function(router, uriBase, Mod, modQuery, modDoc) {
  var thiz = this;
  var uriDoc = uriBase + '/:id';

  modQuery = modQuery || function(query) {
    return query;
  };

  modDoc = modDoc || function(doc) {
    return doc;
  };

  router.route(uriBase)
  // Query
  .get(function(req, res) {
    var query = modQuery(Mod.find());
    var reqQuery = req.query;

    // Parse the query
    // Ensure empty params are treated as null
    Object.keys(reqQuery).map(function(param) {
      if (reqQuery[param] === '') {
        reqQuery[param] = null;
      }
    });

    // Parse comma-delimited params and trim them
    if (reqQuery.populate) {
      reqQuery.populate = reqQuery.populate.split(',');
      reqQuery.populate = reqQuery.populate.map(Function.prototype.call, String.prototype.trim);
    }
    if (reqQuery.sort) {
      reqQuery.sort = reqQuery.sort.split(',');
      reqQuery.sort = reqQuery.sort.map(Function.prototype.call, String.prototype.trim);
      var newVal = {};
      reqQuery.sort.map(function(param) {
        if (param.lastIndexOf('-', 0) === 0) {
          newVal[param.substring(1)] = -1;
        } else {
          newVal[param] = 1;
        }
      });
      reqQuery.sort = newVal;
    }

    // Apply modifiers
    // The try/catch allows us to throw errors in modifiers, if we're
    // doing something like auth.
    try {
      thiz.modifiers.filter(function(mod) {
        reqQuery[mod.param] = mod.fn(req, reqQuery[mod.param]);
        if (reqQuery[mod.param] === false) {
          delete reqQuery[mod.param];
        }
      });
    } catch (err) {
      if (!err.code) {
        throw err;
      }
      return;
    }

    // Apply limit parameter
    if (reqQuery.limit) {
      try {
        if (isNaN(reqQuery.limit)) {
          throw res.error(400, 'Limit must be a number.');
        }
        query.limit(reqQuery.limit);
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    // Apply skip parameter
    if (reqQuery.skip) {
      try {
        if (isNaN(reqQuery.skip)) {
          throw res.error(400, 'Skip must be a number.');
        }
        query.skip(reqQuery.skip);
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    if (reqQuery.populate) {
      try {
        reqQuery.populate.filter(function(toPopulate) {
          if (!Mod.schema.paths.hasOwnProperty(toPopulate)) {
            throw res.error(400, 'Field "' + toPopulate + '" does not exist.');
          }
          if (thiz.restricted.indexOf(toPopulate) !== -1) {
            throw res.error(401, 'Cannot populate restricted field "' + toPopulate + '".');
          }
          query.populate(toPopulate);
        });
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    if (reqQuery.sort) {
      try {
        Object.keys(reqQuery.sort).filter(function(toSort) {
          if (!Mod.schema.paths.hasOwnProperty(toSort)) {
            throw res.error(400, 'Field "' + toSort + '" does not exist.');
          }
          if (thiz.restricted.indexOf(toSort) !== -1) {
            throw res.error(401, 'Cannot sort restricted field "' + toSort + '".');
          }
        });
        query.sort(reqQuery.sort);
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    // Filters
    if (reqQuery.filter) {
      try {
        var filters = utils.parseFilterString(reqQuery.filter);
        filters.filter(function(filter) {
          if (!thiz.filters.hasOwnProperty(filter[0])) {
            throw res.error(400, 'The filter "' + filter + '" does not exist.');
          }
          var filterFn = thiz.filters[filter[0]];

          // Construct our filter arguments
          var args = [];
          args.push(req);
          args.push(query);
          args.push.apply(args, filter.slice(1));

          try {
            filterFn.apply(thiz, args);
          } catch (err) {
            throw res.error(400, 'Could not apply filter "' + filter + '" due to error: ' + err.message);
          }
        });
      } catch (err) {
        if (!err.code) {
          throw err;
        }
        return;
      }
    }

    // Fields
    try {
      Mod.schema.eachPath(function(name) {
        if (!reqQuery.hasOwnProperty(name)) {
          return;
        }
        var val = reqQuery[name];

        if (thiz.restricted.indexOf(name) !== -1) {
          throw res.error(401, 'Cannot access restricted field "' + name + '".');
        }
        query.where(name).equals(val);
      });
    } catch (err) {
      if (!err.code) {
        throw err;
      }
      return;
    }

    query.exec(function(err, docs) {
      if (err) {
        throw err;
      }
      res.json(docs.map(function(doc) {
        return thiz.applyTransforms(req, doc);
      }));
    });
  })

  // Create
  .post(function(req, res) {
    var doc = modDoc(new Mod(req.body));
    doc.save(function(err) {
      if (err) {
        if (err.err.match(/duplicate key error/)) {
          return res.error(409, Mod.modelName + ' already exists.');
        }
        return res.error(500, err.message);
      }
      res.json(thiz.applyTransforms(req, doc));
    });
  });

  router.route(uriDoc)
  // Get
  .get(function(req, res) {
    var query = {};
    query[thiz.id] = req.params.id;
    modQuery(Mod.findOne(query)).exec(function(err, doc) {
      if (err) {
        return res.error(500, err.err);
      }
      if (!doc) {
        return res.error(404, Mod.modelName + ' "' + req.params.id + '" not found.');
      }
      res.json(thiz.applyTransforms(req, doc));
    });
  })

  // Update
  .put(function(req, res) {
    var query = {};
    var body = req.body;
    query[thiz.id] = req.params.id;
    modQuery(Mod.findOne(query)).exec(function(err, doc) {
      if (err) {
        return res.error(500, err.err);
      }
      if (!doc) {
        return res.error(404, Mod.modelName + ' "' + req.params.id + '" not found.');
      }
      for (var prop in body) {
        if (!body.hasOwnProperty(prop)) {
          continue;
        }
        doc[prop] = body[prop];
      }

      doc.save(function(err) {
        if (err) {
          return res.error(500, err);
        }
        return res.json(thiz.applyTransforms(req, doc));
      });
    });
  })

  // Delete
  .delete(function(req, res) {
    var query = {};
    query[thiz.id] = req.params.id;
    modQuery(Mod.findOne(query)).exec(function(err, doc) {
      if (err) {
        return res.error(500, err.err);
      }
      if (!doc) {
        return res.error(404, Mod.modelName + ' "' + req.params.id + '" not found.');
      }
      doc.remove(function(err) {
        if (err) {
          return res.error(500, err);
        }
        return res.json({
          success: true
        });
      });
    });
  });

  return this;
};

/**
 * Creates a middleware of this model.
 */
Model.prototype.middleware = function() {
  var router = express.Router();
  this.serve(router);
  return router;
};
<|endoftext|>


<commit_before>define(['notify'], function (notify) {
    var _models = {},
        Model = function () {};

    Model.prototype = {
        //data: [],
        _callBacks: {},
        create: function (modelName, propsObj) {
            var model = _models[modelName] = Object.create(new Model()),
                prop;

            model.modelName = modelName;

            for (prop in propsObj) {
                if (propsObj.hasOwnProperty(prop)) {
                    model[prop] = propsObj[prop];
                }
            }

            return model;
        },

        getModel: function (modelName) {
            return _models[modelName];
        },

        insert: function (topicName, data, config, modelName) {
            var model = this.getModel(modelName || this.modelName),
                topic = model[topicName] || [],
                dataObj,
                newObj,
                i = 0,
                len,
                prop,
                map;

            if (data instanceof Array) {
                for (i, len = data.length; i < len; ++i) {
                    dataObj = data[i];
                    newObj = {};

                    for (prop in dataObj) {
                        if (dataObj.hasOwnProperty(prop)) {
                            map = (config && config[prop]) ? config[prop] : prop;
                            newObj[map] = dataObj[prop];
                        }
                    }

                    topic.push(newObj);
                }

                model[topicName] = topic;
            }

            return model;
        },

        add: function (topicName, dataObj, customEventName, modelName) {
            var model = this.getModel(modelName || this.modelName),
                topic = model[topicName] || [];

            topic.push(dataObj);

            notify.publish(topicName + (customEventName || ''));

            return model;
        },

        update: function (path, newVal, customEventName, modelName) {
            var model = this.getModel(modelName || this.modelName),
                targetNodes = path.split('.'),
                firstNode = targetNodes.shift(),
                topic = model[firstNode] || [],
                target = targetNodes.shift(),
                targetNode,
                i = 0,
                len = targetNodes.length,
                updateEvent;

            for (i; i < len; ++i) {
                targetNode = targetNodes[i];
                // if the property doesn't exist, add it.
                // this allows update to add without overwriting like insert does
                topic = topic[targetNode] || (function() {
                        topic[targetNode] = {};
                        return topic[targetNode];
                    }());
            }

            if (topic instanceof Array) {
                topic = topic[0];
            }

            topic[target] = newVal;

            updateEvent = name + (customEventName || '') + '?path=' + path;

            notify.publish(updateEvent);
        },

        /**
         * getData returns the value of an object path lookup in the model
         * @param path {String} Path through object to desired data
         * @param modelName [String] Name of model to use (or current model)
         * @returns {*} Returns the found value or null if no value is found
         */
        getData: function (path, modelName) {
            var model = this.getModel(modelName || this.modelName),
                targetNodes = path.split('.'),
                node = targetNodes.shift(),
                topic = model[node] || [],
                returnVal;

            for (; targetNodes.length > 1; node = targetNodes.shift()) {
                topic = topic[node];
            }

            if (topic instanceof Array) {
                topic = topic[0];
            }

            returnVal = topic[node];

            if (returnVal === 'undefined') {
                returnVal = null;
            }

            return returnVal;
        },

        /**
         * select returns an object from a data topic array if it matches the compFunc
         * @param topicName {String} Name of a data topic array
         * @param compFunc {Function} Test object property values against a comparator !Function must return either the passing object or boolean false
         * @param modelName [String] Name of model to use (or current model)
         * @returns {boolean|Object} Returns the found object or boolean false
         */
        select: function (topicName, compFunc, modelName) {
            var model = this.getModel(modelName || this.modelName),
                topic = model[topicName] || [],
                found = false;

            topic.some(function (obj) {
                found = compFunc(obj);

                return found;
            });

            if (found instanceof Object) {
                found.$get = function (key) {
                    return found[key];
                };
            }

            return found;
        },

        /**
         * selectAll returns all objects from a data topic array which match the compFunc
         * @param topicName {String} Name of a data topic array
         * @param compFunc {Function} Test object property values against a comparator !Function must return either the passing object or boolean false
         * @param modelName [String] Name of model to use (or current model)
         * @returns {boolean|Object} Returns the found object or boolean false
         */
        selectAll: function (topicName, compFunc, modelName) {
            var model = this.getModel(modelName || this.modelName),
                topic = model[topicName] || [],
                found = [];

            topic.forEach(function (obj) {
                if (compFunc(obj) !== false) {
                    found.push(obj);
                }
            });

            if (found.length === 0) {
                found = false;
            }

            return found;
        },

        /**
         * register a callback to attach to matching data-event-listeners in html
         * @param name {String} Name of callback (should match data-event-listener value)
         * @param event {String} Event to bind to
         * @param listener {Function} Callback function to execute on event
         * @param modelName [String] Name of model to use or current model
         * @returns {*} Returns the current model
         */
        register: function (name, event, listener, modelName) {
            var model = this.getModel(modelName || this.modelName);

            model._callBacks[name] = {
                "event": event,
                "function": listener
            };

            return model;
        }
    };

    return new Model();
});<commit_msg>Complete rewrite of model<commit_after>define(['notify'], function (notify) {
    var _models = {},
        Model = {};

    Model.__proto__ = {
        _data: {
            // _data can be arranged by topics. This allows a single model to represent one or several groupings of data
            // Each topic is a named object whose properties may be any number of dataArrays, dataObjects, or sub-topics
            // Each subtopic is the same thing as a topic; all topics are indexed for quick lookup using model.getTopic(topicName)
            // Alternatively, dataArrays and dataObjects may be appended directly to the root of the model without topics
            //
            // i.e. (Sample Structure under _data), also note the following assertions based on the data:
            //      _data.reminders.isDataArray === true
            //      _data.user.isTopicNode === true
            //      _data.user.lists.isDataArray === true
            //      _data.user.name.isDataObject === true
            //      _data.user.sessions-today.isDataNode === true
            //      _data.user.uid.isDataObject === undefined (false)
            //
            // reminders: [{title:'Wash Car', done:false}, {title:'Walk Dog', done:true}]
            // user: {
            //     lists: [
            //       {id:'Monday', completed:1, total:2},
            //       {id:'Tuesday', completed:0, total:3}
            //     ],
            //     settings: [
            //       {id:'login', auto:true},
            //       {id:'theme', color:'blue', font:'Verdana', font-size:'18'}
            //     ],
            //     name: {data: 'Scott'},
            //     uid:  'blujagu',
            //     sessions-today: {data: ['1443207183821', '1443209204520']}
            // }
            //
        },

        _callBacks: {},

        _modelName: '',

        _topics: {},

        /**
         * create spawns a new model with the given name and properties
         * @param modelName {String} Unique name for this model (or overwrite existing model with this name)
         * @param propsObj [Object] Optional properties/methods to append to the model
         * @returns {Object} Returns the newly created model instance
         */
        create: function (modelName, propsObj) {
            var model = _models[modelName] = Object.create(Model.__proto__),
                prop;

            model.__proto__._modelName = modelName;

            for (prop in propsObj) {
                if (propsObj.hasOwnProperty(prop)) {
                    model[prop] = propsObj[prop];
                }
            }

            return model;
        },

        /**
         * getModel get the model with a given name
         * @param modelName {String} Name of model to get
         * @returns {Object|null} Returns the requested model or null if it doesn't exist
         */
        getModel: function (modelName) {
            return _models[modelName] || null;
        },

        /**
         * getNodeFromPath returns a node from the model at a given path
         * @param path {String} Path to node to retrieve | '_data' to get back the root data node
         * @param modelName [String] Optional name of model to get | defaults to current model
         * @returns {Object|null} Returns the requested node or null if not found
         */
        getNodeFromPath: function (path, modelName) {
            var model = this.getModel(modelName || this._modelName),
                i, len, nodePath, nodeId, node = {};

            if (path !== undefined && path !== null) {
                nodePath = path.split('.');
                for (i = 0, len = nodePath.length; i < len && node !== undefined; ++i) {
                    nodeId = nodePath[i];
                    if (i === 0) {
                        if (nodeId === '') {
                            node = model._data;
                        } else {
                            node = model._data[nodeId];
                        }
                    } else {
                        node = node[nodeId];
                    }
                }
            } else {
                node = undefined;
            }

            return node !== undefined ? node : null;
        },

        getTopic: function (topicId, modelName) {
            var model = this.getModel(modelName || this._modelName);

            return model._topics[topicId];
        },

        /**
         * newTopic creates a topicNode and returns it. If a path is provided, the topic is added to the model
         * @param lookupName [String] Name to index this topic under (must be unique)
         * @param topicName [String] Name of new topicNode node to create on model
         * @param path [String] Path to parent node where this topic should be created
         * @param modelName [String] Optional name of model to get | defaults to current model
         * @returns {Object|null} Returns the requested node or null if not found
         */
        newTopic: function (lookupName, topicName, path, modelName) {
            var parentNode = this.getNodeFromPath(path, modelName),
                topicObj = Object.create(Object.prototype, {
                    'topicName': {
                        value: '',
                        writable: true,
                        configurable: false
                    },
                    'isTopicNode' : {
                        value: true,
                        enumerable: true,
                        writable: false,
                        configurable: false
                    },
                    'get': {
                        value: function (path) {
                            var parts = path.split('.'),
                                i = 0, len = parts.length,
                                part, obj = {};

                            for (i; i < len; ++i) {
                                part = parts[i];
                                if (i === 0) {
                                    obj = part ? this[part] : this;
                                } else {
                                    obj = obj[part];
                                }
                            }

                            return obj.data || obj;
                        },
                        enumerable: true,
                        writable: false,
                        configurable: false
                    },
                    'setVal':  {
                        value: function (prop, val) {
                            var dataObject = this.newDataObject();

                            dataObject.data.value = val;
                            this[prop] = dataObject;

                            return dataObject;
                        },
                        enumerable: true,
                        writable: false,
                        configurable: false
                    },
                    'setData': {
                        value: function (prop, dArray) {
                            var dataArray = this.newDataArray();

                            return dataArray.cloneArray(dArray);
                        },
                        enumerable: true,
                        writable: false,
                        configurable: false
                    },
                    'setTopic': {
                        value: function (prop) {
                            this[prop] = this.newTopic(prop);

                            return this[prop];
                        },
                        enumerable: true,
                        writable: false,
                        configurable: false
                    }
                });

            lookupName = lookupName || 'topic_' + Date.now();
            this.topicName.value = lookupName;
            this._topics[lookupName] = topicObj;
            if (path !== undefined && path !== null) {
                parentNode[topicName] = topicObj;
            }

            return topicObj;
        },

        newDataObject: function (nodeName, path, modelName) {
            var parentNode = this.getNodeFromPath(path, modelName),
                dataObject = Object.create(Object.prototype, {
                    'data': {
                        value: undefined,
                        enumerable: true,
                        writable: true,
                        configurable: false
                    },
                    'get': {
                        value: function (prop) {
                            return this.data[prop] || this.data;
                        },
                        writable: false,
                        configurable: false
                    },
                    'set': {
                        value: function (val, prop) {
                            if (prop) {
                                this.data[prop] = val;
                            } else {
                                this.data = val;
                            }

                            return val;
                        },
                        writable: false,
                        configurable: false
                    },
                    'isDataObject' : {
                        value: true,
                        writable: false,
                        configurable: false
                    }
                });

            if (path !== undefined && path !== null) {
                parentNode[nodeName] = dataObject;
            }

            return dataObject;
        },

        newDataArray: function (arrayName, path, modelName) {
            var model = this.getModel(modelName || this._modelName),
                parentNode = this.getNodeFromPath(path, modelName),
                compMap = {
                    isEqual: function (config) {
                        var prop = config[0],
                            expected = config[1];

                        return function (o) {
                            var comp,
                                type = typeof o;

                            if (type === 'object') {
                                comp = o.data[prop] === expected ? o : false;
                            } else {
                                comp = o.data === expected ? o : false;
                            }

                            return comp;
                        }
                    },
                    notEqual: function (config) {
                        var prop = config[0],
                            expected = config[1];

                        return function (o) {
                            var comp,
                                type = typeof o;

                            if (type === 'object') {
                                comp = o[prop] !== expected ? o : false;
                            } else {
                                comp = o !== expected ? o : false;
                            }

                            return comp;
                        }
                    }
                },
                dataArray = Object.create(Array.prototype, {
                    'isDataArray': {
                        value: true,
                        enumerable: true,
                        writable: false,
                        configurable: false
                    },
                    'cloneArray': {
                        value: function (inputArr) {
                            var i = 0,
                                len = inputArr.length,
                                dataObj,
                                val;

                            this.length = 0;

                            for (i; i < len; ++i) {
                                val = inputArr[i];

                                dataObj = model.newDataObject();
                                dataObj.set(val);
                                this.push(dataObj);
                            }

                            return this;
                        },
                        enumerable: true,
                        writable: false,
                        configurable: false
                    },
                    'query': {
                        value: function (compFunc, config, multiple) {
                            var found = multiple ? [] : false,
                                f;

                            if (typeof compFunc === 'string' && (f = compMap[compFunc])) {
                                f = f(config);
                            }

                            if (f) {
                                if (multiple) {
                                    this.forEach(function (obj) {
                                        if (f(obj) !== false) {
                                            found.push(obj);
                                        }
                                    });
                                } else {
                                    this.some(function (obj) {
                                        found = f(obj);

                                        return found;
                                    });
                                }
                            }

                            if (!found || (multiple && !found.length)) {
                                found = null;
                            }

                            return found;
                        },
                        enumerable: true,
                        writable: false,
                        configurable: false
                    }
                });

            if (path !== undefined && path !== null) {
                parentNode[arrayName] = dataArray;
            }

            return dataArray;
        },

        /**
         * register a callback to attach to matching data-event-listeners in html
         * @param name {String} Name of callback (should match data-event-listener value)
         * @param event {String} Event to bind to
         * @param listener {Function} Callback function to execute on event
         * @param modelName [String] Name of model to use or current model
         * @returns {*} Returns the current model
         */
        register: function (name, event, listener, modelName) {
            var model = this.getModel(modelName || this._modelName);

            model._callBacks[name] = {
                "event": event,
                "function": listener
            };

            return model;
        }
    };

    return Model;
});<|endoftext|>


<commit_before>'use strict';

//
// Required modules.
//
var ObjectID = require('mongodb').ObjectID
  , Backbone = require('backbone');

//
// Default model definition of fossa.
//
module.exports = Backbone.Model.extend({
    /**
     * @type {String} proxy id attribute to MongoDB internal `_id`
     * @api private
     */
    idAttribute: '_id'

    /**
     * On model instantiation set basics and do some checks.
     *
     * @Constructor
     * @api public
     */
  , initialize: function initialize() {
      var id = this.attributes._id;

      // Set the models pertained storage state.
      this._stored = !!id;

      // Provide an MongoDB ObjectID if not present.
      if (!id) this.set('_id', new ObjectID);
    }
});
<commit_msg>[fix] check instance of id<commit_after>'use strict';

//
// Required modules.
//
var ObjectID = require('mongodb').ObjectID
  , Backbone = require('backbone');

//
// Default model definition of fossa.
//
module.exports = Backbone.Model.extend({
    /**
     * @type {String} proxy id attribute to MongoDB internal `_id`
     * @api private
     */
    idAttribute: '_id'

    /**
     * On model instantiation set basics and do some checks.
     *
     * @Constructor
     * @api public
     */
  , initialize: function initialize() {
      var id = this.attributes._id
        , oid = id instanceof ObjectID;

      // Set the models pertained storage state.
      this._stored = !!id && oid;

      // Provide an MongoDB ObjectID if not present.
      if (!id || !oid) this.set('_id', new ObjectID);
    }
});
<|endoftext|>


<commit_before>var augment = require('./object').augment;
var extend = require('./object').extend;
var validate = require('./validation');

module.exports = function model (schema) {
  var properties = {}, attrs = schema.attributes, methods = schema.methods, props = schema.properties;
  if (! attrs && ! methods && ! props) {
    attrs = extend({}, schema);
    delete attrs.constructor;
  }
  properties.constructor = function (obj) {
    if (! (this instanceof properties.constructor)) return new properties.constructor(obj);
    if (schema.hasOwnProperty('constructor')) schema.constructor.apply(this, arguments);
    for (var key in attrs) {
      if (attrs.hasOwnProperty(key)) {
        if (obj && obj.hasOwnProperty(key) && obj[key]) {
          this[key] = validate(key, obj[key], attrs[key]);
        }
      }
    }
  };
  var model = augment(Model, properties);
  defineMethods(model.prototype, methods);
  defineProperties(model.prototype, props);

  model.methods = function (methods) {
    defineMethods(model.prototype, methods);
  };
  model.properties = function (properties) {
    defineProperties(model.prototype, properties);
  };
  return model;
};

function defineMethods (object, methods) {
  for (var fn in methods) {
    if (methods.hasOwnProperty(fn) && typeof methods[fn] === 'function') {
      object[fn] = methods[fn];
    }
  }
}

function defineProperties (object, properties) {
  for (var property in properties) {
    if (properties.hasOwnProperty(property) && typeof properties[property] === 'object') {
      Object.defineProperty(object, property, properties[property]);
    }
  }
}

var Model = augment(Object, {});
<commit_msg>Bug Fix - False valued Booleans set as undefined<commit_after>var augment = require('./object').augment;
var extend = require('./object').extend;
var validate = require('./validation');

module.exports = function model (schema) {
  var properties = {}, attrs = schema.attributes, methods = schema.methods, props = schema.properties;
  if (! attrs && ! methods && ! props) {
    attrs = extend({}, schema);
    delete attrs.constructor;
  }
  properties.constructor = function (obj) {
    if (! (this instanceof properties.constructor)) return new properties.constructor(obj);
    if (schema.hasOwnProperty('constructor')) schema.constructor.apply(this, arguments);
    for (var key in attrs) {
      if (attrs.hasOwnProperty(key)) {
        if (obj && obj.hasOwnProperty(key) && obj[key] !== void 0) {
          this[key] = validate(key, obj[key], attrs[key]);
        }
      }
    }
  };
  var model = augment(Model, properties);
  defineMethods(model.prototype, methods);
  defineProperties(model.prototype, props);

  model.methods = function (methods) {
    defineMethods(model.prototype, methods);
  };
  model.properties = function (properties) {
    defineProperties(model.prototype, properties);
  };
  return model;
};

function defineMethods (object, methods) {
  for (var fn in methods) {
    if (methods.hasOwnProperty(fn) && typeof methods[fn] === 'function') {
      object[fn] = methods[fn];
    }
  }
}

function defineProperties (object, properties) {
  for (var property in properties) {
    if (properties.hasOwnProperty(property) && typeof properties[property] === 'object') {
      Object.defineProperty(object, property, properties[property]);
    }
  }
}

var Model = augment(Object, {});
<|endoftext|>


<commit_before>var r = require('rethinkdb');
var Document = require('./document.js');

function Model(name, schema, settings, thinky) {
    settings = settings || {};

    this.name = name;
    this.schema = schema;
    this.checkEnforce = thinky.checkEnforce; // TODO fix ugliness

    this.settings = {
        primaryKey: settings.primaryKey || 'id',
        enforce: this.checkEnforce(settings.enforce, thinky.options.enforce)
    };

    this.thinky = thinky;
    this.pool = thinky.pool;
    this.thinkyOptions = thinky.options
}

Model.compile = function(name, schema, settings, thinky) {
    var modelProto = new Model(name, schema, settings, thinky);
    function model(doc, docSettings) {
        doc = doc || {};

        docSettings = docSettings || {};
        var enforce = modelProto.checkEnforce(docSettings.enforce, modelProto.settings.enforce);

        result = {}
        modelProto.createBasedOnSchema(result, doc, doc, enforce);

        result.__proto__ = new Document(modelProto, docSettings);
        return result;
    };
    model.__proto__ = modelProto;
    return model;
};

/*
- name: name of the model
- schema: An object which fields can map to the following value
    - String
    - Number
    - Boolean
    - Array with one type (like [String], [Number], [{name: String, age: Number}]
    - Object that contains a valid schema
    - {\_type: String, enforce: { missing: _boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
    - {\_type: Number, enforce: { missing: _boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
    - {\_type: Boolean, enforce: { missing: <boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
    - {\_type: Array, schema: _schema_, enforce: { missing: _boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
    - {\_type: Object, schama: _schema_, enforce: { missing: _boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
- settings (object): settings for the model
    - enforce: represents if the schemas should be enforced or not. Its value can be:
        - an object with the 3 fields:
            - missing -- throw on missing fields -- default to false
            - extra -- throw if extra fields are provided -- default to false
            - type -- throw if the type is not the one expected -- default to true
        - a boolean that set all 3 parameters to the same value

TODO: Don't force users to provide all fields in enforce
TODO: Let people custom _type // mostly about test
TODO: Implement maxLenght, minLenght

*/
Model.prototype.createBasedOnSchema = function(result, doc, originalDoc, enforce, prefix, schema) {
    var enforce = enforce; // Add another check here?
    var prefix = prefix || '';

    var schema = schema || this.schema;

    var iterator;
    if (Object.prototype.toString.call(schema) === '[object Array]') {
        iterator = doc;
    }
    else {
        iterator = schema;
    }

    for(var key in iterator) {
        key = (doc === iterator)? parseInt(key): key; // Trick or treat
        var schema_key = (doc === iterator)? schema[0] : schema[key];

        // Do not read or your eres are going to bleed
        if (this.checkType(result, doc, originalDoc, schema, key, schema_key, String, 'string', prefix, enforce) === true) { continue; }
        else if (this.checkType(result, doc, originalDoc, schema, key, schema_key, Number, 'number', prefix, enforce) === true) { continue; }
        else if (this.checkType(result, doc, originalDoc, schema, key, schema_key, Boolean, 'boolean',prefix, enforce) === true) { continue; }
        else if (this.checkType(result, doc, originalDoc, schema, key, schema_key, Array, 'array', prefix, enforce) === true) { continue; }
        else if (this.checkType(result, doc, originalDoc, schema, key, schema_key, Object, 'object', prefix, enforce) === true) { continue; }
    }

    if (enforce.extra === true) {
        for(key in doc) {
            if (schema[key] === undefined) {
                throw new Error('An extra field `'+key+'` not defined in the schema was found.')
            }
        }
    }

}

// Helper for createdBasedOnSchema
Model.prototype.checkType = function(result, doc, originalDoc, schema, key, schema_key, type, typeOf, prefix, enforce) {
    if (schema_key === type) {
        if (typeof doc[key] === typeOf) {
            result[key] = doc[key];
        }
        else if ((enforce.missing === true) && (doc[key] == null)) {
            throw new Error("Value for "+prefix+"["+key+"] must be defined")
        }
        else if ((doc[key] != null) && (enforce.type === true)) {
            throw new Error("Value for "+prefix+"["+key+"] must be a "+type.name)
        }
        return true;
    }
    else if ((Object.prototype.toString.call(schema_key) === '[object Object]') && (schema_key._type === type)) {
        if (typeof schema_key.enforce === 'boolean') { enforce = schema_key.enforce; }

        if (schema_key.enforce != null) {
            var enforce = this.checkEnforce(schema_key.enforce, enforce);
        }

        // Deal with the special case of full objects and arrays
        if (type === Object) {
            if (Object.prototype.toString.call(doc[key]) === '[object Object]') {
                result[key] = result[key] || {};
                prefix += '['+key+']';
                Model.prototype.createBasedOnSchema(result[key], doc[key], originalDoc, enforce, prefix, schema_key.schema);
            }
            else if ((doc[key] == null) && (schema_key['default'] != null)) {
                if (typeof schema_key['default'] === 'function') {
                    result[key] = schema_key['default'](originalDoc);
                    if ((enforce.type === true) && (typeof result[key] !== typeOf)) {
                        throw new Error("The default function did not return a "+type.name+" for "+prefix+"["+key+"]");
                    }
                }
                else if (typeof schema_key['default'] === typeOf) {
                    result[key] = schema_key['default']; // We are sure that we get the good type here since we have tested it before
                }
                else if ((schema['default'] == null) && (enforce.missing === true)) {
                    throw new Error("Default value for "+prefix+"["+key+"] must be defined")
                }
                else if (enforce.type === true) {
                    throw new Error("The default value is not a "+type.name);
                }
            }
            else if ((enforce.missing === true) && (doc[key] == null)) {
                throw new Error("Value for "+prefix+"["+key+"] must be defined")
            }
            else if ((doc[key] != null) && (enforce.type === true)) {
                throw new Error("Value for "+prefix+"["+key+"] must be an Object")
            }
            return true;

        }
        else if (type === Array) {
            if (Object.prototype.toString.call(doc[key]) === '[object Array]') {
                result[key] = [];
                prefix += '['+key+']';
                Model.prototype.createBasedOnSchema(result[key], doc[key], originalDoc, enforce, prefix, [schema_key.schema]);
            }
            else if ((doc[key] == null) && (schema_key['default'] != null)) {
                if (typeof schema_key['default'] === 'function') {
                    result[key] = schema_key['default'](originalDoc);
                    if ((enforce.type === true) && (typeof result[key] !== typeOf)) {
                        throw new Error("The default function did not return a "+type.name+" for "+prefix+"["+key+"]");
                    }
                }
                else if (Object.prototype.toString.call(schema_key['default']) === '[object Array]') {
                //else if (typeof schema_key['default'] === typeOf) {
                    result[key] = schema_key['default']; // We are sure that we get the good type here since we have tested it before
                }
                else if ((schema['default'] == null) && (enforce.missing === true)) {
                    throw new Error("Default value for "+prefix+"["+key+"] must be defined")
                }
                else if (enforce.type === true) {
                    throw new Error("The default value is not a "+type.name);
                }
            }
            else if ((enforce.missing === true) && (doc[key] == null)) {
                throw new Error("Value for "+prefix+"["+key+"] must be defined")
            }
            else if ((doc[key] != null) && (enforce.type === true)) {
                throw new Error("Value for "+prefix+"["+key+"] must be an Array")
            }
            return true;

        }
        else {
            if (typeof doc[key] === typeOf) {
                result[key] = doc[key];
            }
            else if ((doc[key] == null) && (schema_key['default'] != null)) {
                if (typeof schema_key['default'] === 'function') {
                    result[key] = schema_key['default'](originalDoc);
                    if ((enforce.type === true) && (typeof result[key] !== typeOf)) {
                        throw new Error("The default function did not return a "+type.name+" for "+prefix+"["+key+"]");
                    }
                }
                else if (typeof schema_key['default'] === typeOf) {
                    result[key] = schema_key['default']; // We are sure that we get the good type here since we have tested it before
                }
            }
            else if ((enforce.missing === true) && (doc[key] == null)) {
                throw new Error("Value for "+prefix+"["+key+"] must be defined")
            }
            else if ((doc[key] != null) && (enforce.type === true)) {
                throw new Error("Value for "+prefix+"["+key+"] must be a "+type.name);
            }
        }
        return true;
    }
    else if ((type === Array) && (Object.prototype.toString.call(schema_key) === '[object Array]')) {
        if (Object.prototype.toString.call(doc[key]) === '[object Array]') {
            result[key] = [];
            prefix += '['+key+']';
            Model.prototype.createBasedOnSchema(result[key], doc[key], originalDoc, enforce, prefix, schema_key);
        }
        else if ((enforce.missing === true) && (doc[key] == null)) {
            throw new Error("Value for "+prefix+"["+key+"] must be defined")
        }
        else if ((doc[key] != null) && (enforce.type === true)) {
            throw new Error("Value for "+prefix+"["+key+"] must be an Array")
        }
        return true;
    }
    else if ((type === Object) && (Object.prototype.toString.call(schema_key) === '[object Object]')) {
        if (Object.prototype.toString.call(doc[key]) === '[object Object]') {
            result[key] = result[key] || {};
            prefix += '['+key+']';
            Model.prototype.createBasedOnSchema(result[key], doc[key], originalDoc, enforce, prefix, schema_key);
        }
        else if ((enforce.missing === true) && (doc[key] == null)) {
            throw new Error("Value for "+prefix+"["+key+"] must be defined")
        }
        else if ((doc[key] != null) && (enforce.type === true)) {
            throw new Error("Value for "+prefix+"["+key+"] must be an Object")
        }
        return true;
    }

    return false;
}

// Define a method for the documents
Model.prototype.define = function(key, fn, force) {
    var model = this.getModel();
    if ((force === true) || (model[key] == null)) {
        model[key] = fn;
    }
    else {
        throw new Error("A property/method named `"+key+"` is already defined. Use Model.define(key, fn, true) to overwrite the function.");
    }
}

Model.prototype.setSchema = function(schema) {
    this.getModel().schema = schema;
}

Model.prototype.getSettings = function() {
    return this.settings;
}

Model.prototype.getPrimaryKey = function() {
    return this.settings.primaryKey;
}

Model.prototype.getModel = function() {
    return this.__proto__;
}

Model.prototype.execute = function(query, callback) {
    var self = this;
    var model = this.getModel();

    model.pool.acquire( function(error, connection) {
        if (error) {
            return callback(error, null);
        }
        query.run(connection, function(error, result) {
            callback(error, result);
            model.pool.release(connection);
        });
    });
}

// TODO: Refactor all queries
Model.prototype.get = function(id, callback) {
    var self = this;
    var model = this.getModel();
    model.pool.acquire( function(error, connection) {
        if (error) {
            return callback(error, null);
        }
        if (Object.prototype.toString.call(id) === '[object Array]') {
            var table = r.db(model.thinkyOptions.db).table(model.name);
            table.getAll.apply(table, id).run( connection, function(error, cursor) {
                if (error) {
                    callback(error, null);
                    model.pool.release(connection);
                }
                else {
                    cursor.toArray( function(error, results) {
                        if (error) {
                            callback(error, null);
                            model.pool.release(connection);
                        }
                        else {
                            var docs = [];
                            for (var index in results) {
                                result = results[index];
                                var doc = new self(result, {saved: true});
                                docs.push(doc);
                            }
                            callback(null, docs);
                            model.pool.release(connection);
                        }
                    });
                }
            });

        }
        else {
            //TODO Handle type error
            r.db(model.thinkyOptions.db).table(model.name).get(id).run( connection, function(error, result) {
                var doc = null;
                if ((error == null) && (result != null)) {
                    doc = new self(result, {saved: true});
                }
                callback(error, doc);
                model.pool.release(connection);
            });
        }
    });
}

Model.prototype.filter = function(filter, callback) {
    var self = this;
    var model = this.__proto__;
    model.pool.acquire( function(error, connection) {
        if (error) {
            return callback(error, null);
        }
        r.db(model.thinkyOptions.db).table(model.name).filter(filter).run( connection, function(error, cursor) {
            if (error) {
                callback(error, null);
                model.pool.release(connection);
            }
            else {
                cursor.toArray( function(error, results) {
                    if (error) {
                        callback(error, null);
                        model.pool.release(connection);
                    }
                    else {
                        var docs = [];
                        for (var index in results) {
                            result = results[index];
                            var doc = new self(result, {saved: true});
                            docs.push(doc);
                        }
                        callback(null, docs);
                        model.pool.release(connection);
                    }
                });
            }
        });
    });
}

Model.prototype.mapReduce = function(mapFunction, callback) {
    //TODO
}
Model.prototype.count = function(callback) {
    var self = this;
    var model = this.__proto__;
    model.pool.acquire( function(error, connection) {
        if (error) {
            return callback(error, null);
        }
        r.db(model.thinkyOptions.db).table(model.name).count().run( connection, function(error, result) {
            if (error) {
                callback(error, null);
                model.pool.release(connection);
            }
            else {
                callback(null, result);
                model.pool.release(connection);
            }
        });
    });
}

var model = module.exports = exports = Model;
<commit_msg>Add comment about moving execute in thinky?<commit_after>var r = require('rethinkdb');
var Document = require('./document.js');

function Model(name, schema, settings, thinky) {
    settings = settings || {};

    this.name = name;
    this.schema = schema;
    this.checkEnforce = thinky.checkEnforce; // TODO fix ugliness

    this.settings = {
        primaryKey: settings.primaryKey || 'id',
        enforce: this.checkEnforce(settings.enforce, thinky.options.enforce)
    };

    this.thinky = thinky;
    this.pool = thinky.pool;
    this.thinkyOptions = thinky.options
}

Model.compile = function(name, schema, settings, thinky) {
    var modelProto = new Model(name, schema, settings, thinky);
    function model(doc, docSettings) {
        doc = doc || {};

        docSettings = docSettings || {};
        var enforce = modelProto.checkEnforce(docSettings.enforce, modelProto.settings.enforce);

        result = {}
        modelProto.createBasedOnSchema(result, doc, doc, enforce);

        result.__proto__ = new Document(modelProto, docSettings);
        return result;
    };
    model.__proto__ = modelProto;
    return model;
};

/*
- name: name of the model
- schema: An object which fields can map to the following value
    - String
    - Number
    - Boolean
    - Array with one type (like [String], [Number], [{name: String, age: Number}]
    - Object that contains a valid schema
    - {\_type: String, enforce: { missing: _boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
    - {\_type: Number, enforce: { missing: _boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
    - {\_type: Boolean, enforce: { missing: <boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
    - {\_type: Array, schema: _schema_, enforce: { missing: _boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
    - {\_type: Object, schama: _schema_, enforce: { missing: _boolean_, type: _boolean_, extra: _boolean_}, default: _value/function_ }
- settings (object): settings for the model
    - enforce: represents if the schemas should be enforced or not. Its value can be:
        - an object with the 3 fields:
            - missing -- throw on missing fields -- default to false
            - extra -- throw if extra fields are provided -- default to false
            - type -- throw if the type is not the one expected -- default to true
        - a boolean that set all 3 parameters to the same value

TODO: Don't force users to provide all fields in enforce
TODO: Let people custom _type // mostly about test
TODO: Implement maxLenght, minLenght

*/
Model.prototype.createBasedOnSchema = function(result, doc, originalDoc, enforce, prefix, schema) {
    var enforce = enforce; // Add another check here?
    var prefix = prefix || '';

    var schema = schema || this.schema;

    var iterator;
    if (Object.prototype.toString.call(schema) === '[object Array]') {
        iterator = doc;
    }
    else {
        iterator = schema;
    }

    for(var key in iterator) {
        key = (doc === iterator)? parseInt(key): key; // Trick or treat
        var schema_key = (doc === iterator)? schema[0] : schema[key];

        // Do not read or your eres are going to bleed
        if (this.checkType(result, doc, originalDoc, schema, key, schema_key, String, 'string', prefix, enforce) === true) { continue; }
        else if (this.checkType(result, doc, originalDoc, schema, key, schema_key, Number, 'number', prefix, enforce) === true) { continue; }
        else if (this.checkType(result, doc, originalDoc, schema, key, schema_key, Boolean, 'boolean',prefix, enforce) === true) { continue; }
        else if (this.checkType(result, doc, originalDoc, schema, key, schema_key, Array, 'array', prefix, enforce) === true) { continue; }
        else if (this.checkType(result, doc, originalDoc, schema, key, schema_key, Object, 'object', prefix, enforce) === true) { continue; }
    }

    if (enforce.extra === true) {
        for(key in doc) {
            if (schema[key] === undefined) {
                throw new Error('An extra field `'+key+'` not defined in the schema was found.')
            }
        }
    }

}

// Helper for createdBasedOnSchema
Model.prototype.checkType = function(result, doc, originalDoc, schema, key, schema_key, type, typeOf, prefix, enforce) {
    if (schema_key === type) {
        if (typeof doc[key] === typeOf) {
            result[key] = doc[key];
        }
        else if ((enforce.missing === true) && (doc[key] == null)) {
            throw new Error("Value for "+prefix+"["+key+"] must be defined")
        }
        else if ((doc[key] != null) && (enforce.type === true)) {
            throw new Error("Value for "+prefix+"["+key+"] must be a "+type.name)
        }
        return true;
    }
    else if ((Object.prototype.toString.call(schema_key) === '[object Object]') && (schema_key._type === type)) {
        if (typeof schema_key.enforce === 'boolean') { enforce = schema_key.enforce; }

        if (schema_key.enforce != null) {
            var enforce = this.checkEnforce(schema_key.enforce, enforce);
        }

        // Deal with the special case of full objects and arrays
        if (type === Object) {
            if (Object.prototype.toString.call(doc[key]) === '[object Object]') {
                result[key] = result[key] || {};
                prefix += '['+key+']';
                Model.prototype.createBasedOnSchema(result[key], doc[key], originalDoc, enforce, prefix, schema_key.schema);
            }
            else if ((doc[key] == null) && (schema_key['default'] != null)) {
                if (typeof schema_key['default'] === 'function') {
                    result[key] = schema_key['default'](originalDoc);
                    if ((enforce.type === true) && (typeof result[key] !== typeOf)) {
                        throw new Error("The default function did not return a "+type.name+" for "+prefix+"["+key+"]");
                    }
                }
                else if (typeof schema_key['default'] === typeOf) {
                    result[key] = schema_key['default']; // We are sure that we get the good type here since we have tested it before
                }
                else if ((schema['default'] == null) && (enforce.missing === true)) {
                    throw new Error("Default value for "+prefix+"["+key+"] must be defined")
                }
                else if (enforce.type === true) {
                    throw new Error("The default value is not a "+type.name);
                }
            }
            else if ((enforce.missing === true) && (doc[key] == null)) {
                throw new Error("Value for "+prefix+"["+key+"] must be defined")
            }
            else if ((doc[key] != null) && (enforce.type === true)) {
                throw new Error("Value for "+prefix+"["+key+"] must be an Object")
            }
            return true;

        }
        else if (type === Array) {
            if (Object.prototype.toString.call(doc[key]) === '[object Array]') {
                result[key] = [];
                prefix += '['+key+']';
                Model.prototype.createBasedOnSchema(result[key], doc[key], originalDoc, enforce, prefix, [schema_key.schema]);
            }
            else if ((doc[key] == null) && (schema_key['default'] != null)) {
                if (typeof schema_key['default'] === 'function') {
                    result[key] = schema_key['default'](originalDoc);
                    if ((enforce.type === true) && (typeof result[key] !== typeOf)) {
                        throw new Error("The default function did not return a "+type.name+" for "+prefix+"["+key+"]");
                    }
                }
                else if (Object.prototype.toString.call(schema_key['default']) === '[object Array]') {
                //else if (typeof schema_key['default'] === typeOf) {
                    result[key] = schema_key['default']; // We are sure that we get the good type here since we have tested it before
                }
                else if ((schema['default'] == null) && (enforce.missing === true)) {
                    throw new Error("Default value for "+prefix+"["+key+"] must be defined")
                }
                else if (enforce.type === true) {
                    throw new Error("The default value is not a "+type.name);
                }
            }
            else if ((enforce.missing === true) && (doc[key] == null)) {
                throw new Error("Value for "+prefix+"["+key+"] must be defined")
            }
            else if ((doc[key] != null) && (enforce.type === true)) {
                throw new Error("Value for "+prefix+"["+key+"] must be an Array")
            }
            return true;

        }
        else {
            if (typeof doc[key] === typeOf) {
                result[key] = doc[key];
            }
            else if ((doc[key] == null) && (schema_key['default'] != null)) {
                if (typeof schema_key['default'] === 'function') {
                    result[key] = schema_key['default'](originalDoc);
                    if ((enforce.type === true) && (typeof result[key] !== typeOf)) {
                        throw new Error("The default function did not return a "+type.name+" for "+prefix+"["+key+"]");
                    }
                }
                else if (typeof schema_key['default'] === typeOf) {
                    result[key] = schema_key['default']; // We are sure that we get the good type here since we have tested it before
                }
            }
            else if ((enforce.missing === true) && (doc[key] == null)) {
                throw new Error("Value for "+prefix+"["+key+"] must be defined")
            }
            else if ((doc[key] != null) && (enforce.type === true)) {
                throw new Error("Value for "+prefix+"["+key+"] must be a "+type.name);
            }
        }
        return true;
    }
    else if ((type === Array) && (Object.prototype.toString.call(schema_key) === '[object Array]')) {
        if (Object.prototype.toString.call(doc[key]) === '[object Array]') {
            result[key] = [];
            prefix += '['+key+']';
            Model.prototype.createBasedOnSchema(result[key], doc[key], originalDoc, enforce, prefix, schema_key);
        }
        else if ((enforce.missing === true) && (doc[key] == null)) {
            throw new Error("Value for "+prefix+"["+key+"] must be defined")
        }
        else if ((doc[key] != null) && (enforce.type === true)) {
            throw new Error("Value for "+prefix+"["+key+"] must be an Array")
        }
        return true;
    }
    else if ((type === Object) && (Object.prototype.toString.call(schema_key) === '[object Object]')) {
        if (Object.prototype.toString.call(doc[key]) === '[object Object]') {
            result[key] = result[key] || {};
            prefix += '['+key+']';
            Model.prototype.createBasedOnSchema(result[key], doc[key], originalDoc, enforce, prefix, schema_key);
        }
        else if ((enforce.missing === true) && (doc[key] == null)) {
            throw new Error("Value for "+prefix+"["+key+"] must be defined")
        }
        else if ((doc[key] != null) && (enforce.type === true)) {
            throw new Error("Value for "+prefix+"["+key+"] must be an Object")
        }
        return true;
    }

    return false;
}

// Define a method for the documents
Model.prototype.define = function(key, fn, force) {
    var model = this.getModel();
    if ((force === true) || (model[key] == null)) {
        model[key] = fn;
    }
    else {
        throw new Error("A property/method named `"+key+"` is already defined. Use Model.define(key, fn, true) to overwrite the function.");
    }
}

Model.prototype.setSchema = function(schema) {
    this.getModel().schema = schema;
}

Model.prototype.getSettings = function() {
    return this.settings;
}

Model.prototype.getPrimaryKey = function() {
    return this.settings.primaryKey;
}

Model.prototype.getModel = function() {
    return this.__proto__;
}

//TODO Move in Thinky?
Model.prototype.execute = function(query, callback) {
    var self = this;
    var model = this.getModel();

    model.pool.acquire( function(error, connection) {
        if (error) {
            return callback(error, null);
        }
        query.run(connection, function(error, result) {
            callback(error, result);
            model.pool.release(connection);
        });
    });
}

// TODO: Refactor all queries
Model.prototype.get = function(id, callback) {
    var self = this;
    var model = this.getModel();
    model.pool.acquire( function(error, connection) {
        if (error) {
            return callback(error, null);
        }
        if (Object.prototype.toString.call(id) === '[object Array]') {
            var table = r.db(model.thinkyOptions.db).table(model.name);
            table.getAll.apply(table, id).run( connection, function(error, cursor) {
                if (error) {
                    callback(error, null);
                    model.pool.release(connection);
                }
                else {
                    cursor.toArray( function(error, results) {
                        if (error) {
                            callback(error, null);
                            model.pool.release(connection);
                        }
                        else {
                            var docs = [];
                            for (var index in results) {
                                result = results[index];
                                var doc = new self(result, {saved: true});
                                docs.push(doc);
                            }
                            callback(null, docs);
                            model.pool.release(connection);
                        }
                    });
                }
            });

        }
        else {
            //TODO Handle type error
            r.db(model.thinkyOptions.db).table(model.name).get(id).run( connection, function(error, result) {
                var doc = null;
                if ((error == null) && (result != null)) {
                    doc = new self(result, {saved: true});
                }
                callback(error, doc);
                model.pool.release(connection);
            });
        }
    });
}

Model.prototype.filter = function(filter, callback) {
    var self = this;
    var model = this.__proto__;
    model.pool.acquire( function(error, connection) {
        if (error) {
            return callback(error, null);
        }
        r.db(model.thinkyOptions.db).table(model.name).filter(filter).run( connection, function(error, cursor) {
            if (error) {
                callback(error, null);
                model.pool.release(connection);
            }
            else {
                cursor.toArray( function(error, results) {
                    if (error) {
                        callback(error, null);
                        model.pool.release(connection);
                    }
                    else {
                        var docs = [];
                        for (var index in results) {
                            result = results[index];
                            var doc = new self(result, {saved: true});
                            docs.push(doc);
                        }
                        callback(null, docs);
                        model.pool.release(connection);
                    }
                });
            }
        });
    });
}

Model.prototype.mapReduce = function(mapFunction, callback) {
    //TODO
}
Model.prototype.count = function(callback) {
    var self = this;
    var model = this.__proto__;
    model.pool.acquire( function(error, connection) {
        if (error) {
            return callback(error, null);
        }
        r.db(model.thinkyOptions.db).table(model.name).count().run( connection, function(error, result) {
            if (error) {
                callback(error, null);
                model.pool.release(connection);
            }
            else {
                callback(null, result);
                model.pool.release(connection);
            }
        });
    });
}

var model = module.exports = exports = Model;
<|endoftext|>