vitamin
Version:
Data Mapper library for Node.js applications
898 lines (703 loc) • 24.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _invalidRelationObject = require('./errors/invalid-relation-object');
var _invalidRelationObject2 = _interopRequireDefault(_invalidRelationObject);
var _vitaminEvents = require('vitamin-events');
var _vitaminEvents2 = _interopRequireDefault(_vitaminEvents);
var _base = require('./relations/base');
var _base2 = _interopRequireDefault(_base);
var _collection = require('./collection');
var _collection2 = _interopRequireDefault(_collection);
var _registry = require('./registry');
var _registry2 = _interopRequireDefault(_registry);
var _bluebird = require('bluebird');
var _bluebird2 = _interopRequireDefault(_bluebird);
var _model = require('./model');
var _model2 = _interopRequireDefault(_model);
var _query = require('./query');
var _query2 = _interopRequireDefault(_query);
var _underscore = require('underscore');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* @class Mapper
*/
var _class = function () {
/**
* Mapper class constructor
*
* @param {Object} options
* @constructor
*/
function _class() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, _class);
this.events = {};
this.methods = {};
this.statics = {};
this.relations = {};
this.attributes = {};
this.defaults = null;
this.tableName = null;
this.primaryKey = 'id';
this.timestamps = false;
this.connection = 'default';
this.createdAtColumn = 'created_at';
this.updatedAtColumn = 'updated_at';
(0, _underscore.extend)(this, options);
// set up the model class for this mapper
this._setupModel(options.modelClass || _model2.default);
// set up the event emitter
this.emitter = new _vitaminEvents2.default();
this._registerEvents(this.events);
}
/**
* Inheritance helper
*
* @param {Object} props
* @param {Object} statics
* @return constructor function
* @deprecated
*/
_createClass(_class, [{
key: 'mapper',
/**
* Get the model mapper from the registry
*
* @param {String} name
* @return mapper
*/
value: function mapper(name) {
return _registry2.default.get(name);
}
/**
* Get the model default attributes
*
* @return function or plain object
*/
}, {
key: 'getDefaults',
value: function getDefaults() {
var _this2 = this;
return this.defaults ? this.defaults : function () {
return (0, _underscore.reduce)(_this2.attributes, function (memo, config, attr) {
if ((0, _underscore.has)(config, 'defaultValue')) memo[attr] = (0, _underscore.result)(config, 'defaultValue');
return memo;
}, {});
};
}
/**
* Load the given relationships of the given models
*
* @param {Model|Array} model
* @param {Array} relations
* @return promise
*/
}, {
key: 'load',
value: function load(model) {
var _newQuery;
// TODO deprecate the use of single model, always call it with an array
var models = (0, _underscore.isArray)(model) ? model : [model];
for (var _len = arguments.length, relations = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
relations[_key - 1] = arguments[_key];
}
return (_newQuery = this.newQuery()).withRelated.apply(_newQuery, relations).loadRelated(models).return(model);
}
/**
* Create a new record into the database
*
* @param {Object} attrs
* @param {Array} returning
* @return promise
*/
}, {
key: 'create',
value: function create(attrs) {
var returning = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['*'];
return this.save(this.newInstance(attrs), returning);
}
/**
* Create many instances of the related model
*
* @param {Array} records
* @parma {Array} returning
* @return promise
*/
}, {
key: 'createMany',
value: function createMany(records) {
var _this3 = this;
var returning = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['*'];
return _bluebird2.default.map(records, function (attrs) {
return _this3.create(attrs, returning);
});
}
/**
* Save the model in the database
*
* @param {Model} model
* @param {Array} returning
* @return promise
*/
}, {
key: 'save',
value: function save(model) {
var _this4 = this,
_arguments = arguments;
var returning = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['*'];
return _bluebird2.default.resolve(model).tap(function () {
return _this4.emitter.emit('saving', model);
}).tap(function () {
return model.exists ? _this4._update.apply(_this4, _arguments) : _this4._insert.apply(_this4, _arguments);
}).tap(function () {
return _this4.emitter.emit('saved', model);
});
}
/**
* Save the given models
*
* @param {Array} models
* @parma {Array} returning
* @return promise
*/
}, {
key: 'saveMany',
value: function saveMany(models) {
var _this5 = this;
var returning = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['*'];
return _bluebird2.default.map(models, function (model) {
return _this5.save(model, returning);
});
}
/**
* Delete the model from the database
*
* @param {Model} model
* @return promise
*/
}, {
key: 'destroy',
value: function destroy(model) {
var _this6 = this;
return _bluebird2.default.resolve(model).tap(function () {
return _this6.emitter.emit('deleting', model);
}).tap(function () {
return _this6.newQuery().where(_this6.primaryKey, model.getId()).destroy();
}).tap(function () {
return _this6.emitter.emit('deleted', model);
});
}
/**
* Delete the given models from the database
*
* @param {Array} models
* @return promise
*/
}, {
key: 'destroyMany',
value: function destroyMany(models) {
var _this7 = this;
return _bluebird2.default.map(models, function (model) {
return _this7.destroy(model);
});
}
/**
* Get a fresh timestamp for the model
*
* @return string ISO time
*/
}, {
key: 'freshTimestamp',
value: function freshTimestamp() {
return new Date().toISOString();
}
/**
* Update the model's update timestamp
*
* @param {Model} model
* @return promise
*/
}, {
key: 'touch',
value: function touch(model) {
if (this.timestamps && this.updatedAtColumn) {
return this.save(model.set(this.updatedAtColumn, this.freshTimestamp()));
}
return _bluebird2.default.resolve(model);
}
/**
* Touch the given models
*
* @param {Array} models
* @return promise
*/
}, {
key: 'touchMany',
value: function touchMany(models) {
var _this8 = this;
return _bluebird2.default.map(models, function (model) {
return _this8.touch(model);
});
}
/**
* Create a new model instance
*
* @param {Object} data
* @param {Boolean} exists
* @return model instance
*/
}, {
key: 'newInstance',
value: function newInstance() {
var _modelClass;
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var exists = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
return (_modelClass = this.modelClass).make.apply(_modelClass, arguments);
}
/**
* Get the model query builder
*
* @return Query instance
*/
}, {
key: 'newQuery',
value: function newQuery() {
var client = _registry2.default.connection(this.connection);
var query = new _query2.default(client.queryBuilder());
return query.from(this.tableName).setModel(this);
}
/**
* Create a new collection of models
*
* @param {Array} models
* @return Collection instance
*/
}, {
key: 'newCollection',
value: function newCollection() {
var models = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
return new _collection2.default(models).setMapper(this);
}
/**
* Helper to create a collection of models
*
* @param {Array} records
* @return collection
*/
}, {
key: 'createModels',
value: function createModels(records) {
var _this9 = this;
return this.newCollection((0, _underscore.map)(records, function (data) {
return _this9.newInstance(data, true);
}));
}
/**
* Create the relationship mapper
*
* @param {String} name
* return relation instance
*/
}, {
key: 'getRelation',
value: function getRelation(name) {
if (this.relations[name]) {
var relation = this.relations[name].call(this);
if (relation instanceof _base2.default) return relation.setName(name);
}
throw new _invalidRelationObject2.default(name);
}
/**
* Define a has-one relationship
*
* @param {Model} related
* @param {String} fk target model foreign key
* @param {String} pk parent model primary key
* @return relation
*/
}, {
key: 'hasOne',
value: function hasOne(related) {
var fk = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var pk = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var HasOne = require('./relations/has-one').default;
if (!pk) pk = this.primaryKey;
if (!fk) fk = this.name + '_id';
if ((0, _underscore.isString)(related)) related = this.mapper(related);
return new HasOne(this, related, fk, pk);
}
/**
* Define a morph-one relationship
*
* @param {Model} related
* @param {String} name of the morph
* @param {String} type target model type column
* @param {String} fk target model foreign key
* @param {String} pk parent model primary key
* @return relation
*/
}, {
key: 'morphOne',
value: function morphOne(related, name) {
var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var fk = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
var pk = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
var MorphOne = require('./relations/morph-one').default;
if (!pk) pk = this.primaryKey;
if (!type) type = name + '_type';
if (!fk) fk = name + '_id';
if ((0, _underscore.isString)(related)) related = this.mapper(related);
return new MorphOne(this, related, type, fk, pk);
}
/**
* Define a has-many relationship
*
* @param {Model} related
* @param {String} fk target model foreign key
* @param {String} pk parent model primary key
* @return relation
*/
}, {
key: 'hasMany',
value: function hasMany(related) {
var fk = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var pk = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var HasMany = require('./relations/has-many').default;
if (!pk) pk = this.primaryKey;
if (!fk) fk = this.name + '_id';
if ((0, _underscore.isString)(related)) related = this.mapper(related);
return new HasMany(this, related, fk, pk);
}
/**
* Define a morph-many relationship
*
* @param {Model} related
* @param {String} name of the morph
* @param {String} type target model type column
* @param {String} fk target model foreign key
* @param {String} pk parent model primary key
* @return relation
*/
}, {
key: 'morphMany',
value: function morphMany(related, name) {
var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var fk = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
var pk = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
var MorphMany = require('./relations/morph-many').default;
if (!pk) pk = this.primaryKey;
if (!type) type = name + '_type';
if (!fk) fk = name + '_id';
if ((0, _underscore.isString)(related)) related = this.mapper(related);
return new MorphMany(this, related, type, fk, pk);
}
/**
* Define a belongs-to relationship
*
* @param {Model} related
* @param {String} fk parent model foreign key
* @param {String} pk target model primary key
* @return relation
*/
}, {
key: 'belongsTo',
value: function belongsTo(related) {
var fk = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var pk = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var BelongsTo = require('./relations/belongs-to').default;
if ((0, _underscore.isString)(related)) related = this.mapper(related);
if (!pk) pk = related.primaryKey;
if (!fk) fk = related.name + '_id';
return new BelongsTo(this, related, fk, pk);
}
/**
* Define a morph-to relationship
*
* @param {String} name of the morph
* @param {String} type column name
* @param {String} fk
* @param {String} pk
* @return relation
*/
}, {
key: 'morphTo',
value: function morphTo(name) {
var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var fk = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var pk = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
var MorphTo = require('./relations/morph-to').default;
if (!fk) fk = name + '_id';
if (!pk) pk = this.primaryKey;
if (!type) type = name + '_type';
return new MorphTo(this, type, fk, pk);
}
/**
* Define a belongs-to-many relationship
*
* @param {Model} related
* @param {String} pivot table name
* @param {String} pfk parent model foreign key
* @param {String} tfk target model foreign key
* @return relation
*/
}, {
key: 'belongsToMany',
value: function belongsToMany(related, pivot) {
var pfk = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var tfk = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
var BelongsToMany = require('./relations/belongs-to-many').default;
if ((0, _underscore.isString)(related)) related = this.mapper(related);
if (!pfk) pfk = this.name + '_id';
if (!tfk) tfk = related.name + '_id';
return new BelongsToMany(this, related, pivot, pfk, tfk);
}
/**
* Define a morph-to-many relationship
*
* @param {Model} related
* @param {String} pivot table name
* @param {String} name of the morph
* @param {String} type column name
* @param {String} pfk parent model foreign key
* @param {String} tfk target model foreign key
* @return relation
*/
}, {
key: 'morphToMany',
value: function morphToMany(related, pivot, name) {
var type = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
var pfk = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
var tfk = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
var MorphToMany = require('./relations/morph-to-many').default;
if ((0, _underscore.isString)(related)) related = this.mapper(related);
if (!pfk) pfk = name + '_id';
if (!type) type = name + '_type';
if (!tfk) tfk = related.name + '_id';
return new MorphToMany(this, related, pivot, type, pfk, tfk);
}
/**
* Define the inverse of morph-to-many relationship
*
* @param {Model} related
* @param {String} pivot table name
* @param {String} name of the morph
* @param {String} type column name
* @param {String} pfk parent model foreign key
* @param {String} tfk target model foreign key
* @return relation
*/
}, {
key: 'morphedByMany',
value: function morphedByMany(related, pivot, name) {
var type = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
var pfk = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
var tfk = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
if (!tfk) tfk = name + '_id';
if (!pfk) pfk = this.name + '_id';
return this.morphToMany(related, pivot, name, type, pfk, tfk);
}
/**
* Add a listener for the given event
*
* @param {String} event
* @param {Function} fn
* @return this model
*/
}, {
key: 'on',
value: function on(event, fn) {
var _emitter;
(_emitter = this.emitter).on.apply(_emitter, arguments);
return this;
}
/**
* Remove an event listener
*
* @param {String} event
* @param {Function} fn
* @return this model
*/
}, {
key: 'off',
value: function off(event, fn) {
var _emitter2;
(_emitter2 = this.emitter).off.apply(_emitter2, arguments);
return this;
}
/**
* Trigger an event with arguments
*
* @param {String} event
* @param {Array} args
* @return promise
*/
}, {
key: 'emit',
value: function emit(event) {
var _emitter3;
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
return (_emitter3 = this.emitter).emit.apply(_emitter3, arguments);
}
/**
* Override it to register shared events between mappers
*
* @param {Object} events
* @private
*/
}, {
key: '_registerEvents',
value: function _registerEvents() {
var _this10 = this;
var events = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
(0, _underscore.each)(events, function (listener, name) {
((0, _underscore.isArray)(listener) ? listener : [listener]).forEach(function (fn) {
return _this10.emitter.on(name, fn);
});
});
}
/**
* Perform a model insert operation
*
* @param {Model} model
* @param {String|Array} returning
* @return promise
* @private
*/
}, {
key: '_insert',
value: function _insert(model, returning) {
var _this11 = this;
return _bluebird2.default.resolve(model).tap(function () {
return _this11.emitter.emit('creating', model);
}).tap(function () {
return _this11.timestamps && _this11._updateTimestamps(model);
}).tap(function () {
return _this11.newQuery().insert(model.getData(), returning).then(function (res) {
return _this11._emulateReturning(model, res, returning);
}).then(function (res) {
return model.setData(res, true);
});
}).tap(function () {
return _this11.emitter.emit('created', model);
});
}
/**
* Perform a model update operation
*
* @param {Model} model
* @param {String|Array} returning
* @return promise
* @private
*/
}, {
key: '_update',
value: function _update(model, returning) {
var _this12 = this;
return _bluebird2.default.resolve(model).tap(function () {
return _this12.emitter.emit('updating', model);
}).tap(function () {
return _this12.timestamps && _this12._updateTimestamps(model);
}).tap(function () {
return _this12.newQuery().where(_this12.primaryKey, model.getId()).update(model.getDirty(), returning).then(function (res) {
return _this12._emulateReturning(model, res, returning);
}).then(function (res) {
return model.setData(res, true);
});
}).tap(function () {
return _this12.emitter.emit('updated', model);
});
}
/**
* Emulate the `returning` SQL clause
*
* @param {Model} model
* @param {Array} result
* @param {Array} columns
* @private
*/
}, {
key: '_emulateReturning',
value: function _emulateReturning(model, result) {
var columns = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ['*'];
var id = result[0];
if ((0, _underscore.isObject)(id)) return id;else {
var qb = this.newQuery().toBase();
if (model.exists) id = model.getId();
// resolve with a plain object to populate the model data
return qb.where(this.primaryKey, id).first(columns);
}
}
/**
* Update the creation and update timestamps
*
* @param {Model} model
* @private
*/
}, {
key: '_updateTimestamps',
value: function _updateTimestamps(model) {
var time = this.freshTimestamp();
var useCreatedAt = !!this.createdAtColumn;
var useUpdatedAt = !!this.updatedAtColumn;
if (useUpdatedAt && !model.isDirty(this.updatedAtColumn)) {
model.set(this.updatedAtColumn, time);
}
if (useCreatedAt && !model.exists && !model.isDirty(this.createdAtColumn)) {
model.set(this.createdAtColumn, time);
}
}
/**
* Set up the model class
*
* @param {Model} model constructor
* @private
*/
}, {
key: '_setupModel',
value: function _setupModel(model) {
var _this = this;
var proto = (0, _underscore.clone)(this.methods);
// add prototype properties
proto.mapper = this;
proto.defaults = this.getDefaults();
proto.idAttribute = this.primaryKey;
// add relationship accessors
(0, _underscore.each)(this.relations, function (_, name) {
proto[name] = function () {
return _this.getRelation(name).addConstraints(this);
};
});
this.modelClass = model.extend(proto, this.statics);
}
}], [{
key: 'extend',
value: function extend() {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var statics = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var parent = this;
var child = function child() {
parent.apply(this, arguments);
};
// use custom constructor
if ((0, _underscore.has)(props, 'constructor')) child = props.constructor;
// set the prototype chain to inherit from `parent`
child.prototype = Object.create(parent.prototype, {
constructor: { value: child, writable: true, configurable: true }
});
// add static and instance properties
(0, _underscore.extend)(child, statics);
(0, _underscore.extend)(child.prototype, props);
// fix extending static properties
Object.setPrototypeOf ? Object.setPrototypeOf(child, parent) : child.__proto__ = parent;
return child;
}
}]);
return _class;
}();
exports.default = _class;