vitamin
Version:
Data Mapper library for Node.js applications
488 lines (377 loc) • 14.2 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 _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
var _underscore = require('underscore');
var _underscore2 = _interopRequireDefault(_underscore);
var _model = require('../model');
var _model2 = _interopRequireDefault(_model);
var _base = require('./base');
var _base2 = _interopRequireDefault(_base);
var _bluebird = require('bluebird');
var _bluebird2 = _interopRequireDefault(_bluebird);
var _mapper = require('../mapper');
var _mapper2 = _interopRequireDefault(_mapper);
var _oneToMany = require('./mixins/one-to-many');
var _oneToMany2 = _interopRequireDefault(_oneToMany);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
// exports
var _class = function (_mixin) {
_inherits(_class, _mixin);
/**
* BelongsToManyRelation constructor
*
* @param {Mapper} parent mapper instance
* @param {Mapper} target mapper instance
* @param {String} pivot table name
* @param {String} pfk parent model foreign key
* @param {String} tfk target model foreign key
* @constructor
*/
function _class(parent, target, pivot, pfk, tfk) {
_classCallCheck(this, _class);
var _this = _possibleConstructorReturn(this, (_class.__proto__ || Object.getPrototypeOf(_class)).call(this, parent, target));
_this.pivot = new _mapper2.default({ tableName: pivot });
_this.table = _this.pivot.tableName;
_this.localKey = parent.primaryKey;
_this.pivotColumns = [pfk, tfk];
_this.targetKey = tfk;
_this.otherKey = pfk;
_this._through = _this.newPivotQuery(false).from(_this.table, target.name + '_pivot');
return _this;
}
/**
* Add the pivot table columns to fetch
*
* @param {Array} columns
* @return this relation
*/
_createClass(_class, [{
key: 'withPivot',
value: function withPivot(columns) {
if (!_underscore2.default.isArray(columns)) columns = _underscore2.default.toArray(arguments);
this.pivotColumns = _underscore2.default.uniq(this.pivotColumns.concat(columns));
return this;
}
/**
* Update an existing record on the pivot table
*
* @param {Any} id of the target model
* @param {Object} attrs
* @return promise
*/
}, {
key: 'updatePivot',
value: function updatePivot(id, attrs) {
return this.newPivotQuery().where(this.targetKey, id).update(attrs);
}
/**
* Create and attach a new instance of the related model
*
* @param {Object} attrs
* @param {Array} returning
* @return promise
*/
}, {
key: 'create',
value: function create(attrs) {
var returning = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['*'];
var pivots = {};
if (attrs.pivot) {
pivots = attrs.pivot;
delete attrs.pivot;
}
return this.save(this.target.newInstance(attrs), pivots, returning);
}
/**
* Save a new model and attach it to the parent
*
* @param {Model} related
* @param {Object} pivots
* @param {Array} returning
* @return promise
*/
}, {
key: 'save',
value: function save(related) {
var _this2 = this;
var pivots = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var returning = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ['*'];
return _get(_class.prototype.__proto__ || Object.getPrototypeOf(_class.prototype), 'save', this).call(this, related, returning).then(function (model) {
return _this2.attach(_this2.createPivotRecord(model.getId(), pivots));
});
}
/**
* Save many models and attach them to the parent model
*
* @param {Array} related
* @param {Array} pivots
* @parma {Array} returning
* @return promise
*/
}, {
key: 'saveMany',
value: function saveMany(related) {
var _this3 = this;
var pivots = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var returning = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ['*'];
return _bluebird2.default.map(related, function (model, i) {
return _this3.save(model, pivots[i], returning);
});
}
/**
* Attach a models to the parent model
*
* @param {Array} ids
* @return promise
*/
}, {
key: 'attach',
value: function attach(ids) {
var records = this.createPivotRecords(_underscore2.default.isArray(ids) ? ids : [ids]);
return this.newPivotQuery(false).insert(records);
}
/**
* Detach one or many models from the parent
*
* @param {Array} ids
* @return promise
*/
}, {
key: 'detach',
value: function detach() {
var ids = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var query = this.newPivotQuery();
ids = (_underscore2.default.isArray(ids) ? ids : [ids]).map(function (value) {
return value instanceof _model2.default ? value.getId() : value;
});
if (ids.length > 0) query.whereIn(this.targetKey, ids);
return query.destroy();
}
/**
* Toggle the given IDs in the intermediate table
*
* It will detach the existing given ids, and attach the new ones,
* In another words, it will only sync the given ids
*
* @param {Any} ids
* @return promise
*/
}, {
key: 'toggle',
value: function toggle(ids) {
var _this4 = this;
var toDetach = [],
toAttach = [];
if (!_underscore2.default.isArray(ids)) ids = [ids];
return this.newPivotQuery().pluck(this.targetKey)
// traversing
.then(function (oldIds) {
ids.forEach(function (_id) {
if (_id instanceof _model2.default) _id = _id.getId();
if (_underscore2.default.isArray(_id)) _id = _id[0];
_underscore2.default.contains(oldIds, _id) ? toDetach.push(_id) : toAttach.push(_id);
});
})
// detach
.then(function () {
return _underscore2.default.isEmpty(toDetach) ? false : _this4.detach(toDetach);
})
// attach
.then(function () {
return _underscore2.default.isEmpty(toAttach) ? false : _this4.attach(toAttach);
})
// return
.then(function () {
return {
'detached': toDetach.length,
'attached': toAttach.length
};
});
}
/**
* Sync the intermediate table with a list of IDs
*
* @param {Array} ids
* @return promise
*/
}, {
key: 'sync',
value: function sync(ids) {
var _this5 = this;
var newIds = [],
toAttach = [],
toUpdate = [],
toDetach;
return this.newPivotQuery().pluck(this.targetKey)
// traversing
.then(function (oldIds) {
ids.forEach(function (value) {
var id;
if (value instanceof _model2.default) value = value.getId();
if (!_underscore2.default.isArray(value)) value = [value];
// add the new id
newIds.push(id = value[0]);
// looking for ids that should be attached or updated
if (!_underscore2.default.contains(oldIds, id)) toAttach.push(value);else if (!_underscore2.default.isEmpty(value[1])) toUpdate.push(value);
});
toDetach = _underscore2.default.difference(oldIds, newIds);
})
// detach
.then(function () {
return _underscore2.default.isEmpty(toDetach) ? false : _this5.detach(toDetach);
})
// attach
.then(function () {
return _underscore2.default.isEmpty(toAttach) ? false : _this5.attach(toAttach);
})
// update pivots
.then(function () {
return _bluebird2.default.map(toUpdate, function (args) {
return _this5.updatePivot.apply(_this5, _toConsumableArray(args));
});
})
// return
.then(function () {
return {
'detached': toDetach.length,
'attached': toAttach.length,
'updated': toUpdate.length
};
});
}
/**
* Apply constraints on the relation query
*
* @param {Model} model
* @return this relation
*/
}, {
key: 'addConstraints',
value: function addConstraints(model) {
this.addPivotJoin();
this.addPivotColumns();
return _get(_class.prototype.__proto__ || Object.getPrototypeOf(_class.prototype), 'addConstraints', this).call(this, model);
}
/**
* Create a query for the pivot table
*
* @param {Boolean} constraints
* @return query
* @private
*/
}, {
key: 'newPivotQuery',
value: function newPivotQuery() {
var constraints = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
var query = this.pivot.newQuery();
if (constraints) query.where(this.otherKey, this.model.get(this.localKey));
return query;
}
/**
* Create an array of records to insert into the pivot table
*
* @param {Array} ids
* @return array
* @private
*/
}, {
key: 'createPivotRecords',
value: function createPivotRecords(ids) {
var _this6 = this;
return ids.map(function (args) {
if (args instanceof _model2.default) args = [args.getId()];
if (!_underscore2.default.isArray(args)) args = [args];
return _this6.createPivotRecord.apply(_this6, _toConsumableArray(args));
});
}
/**
* Create a record to insert into the pivot table
*
* @param {Any} id of the target model
* @param {Object} pivots pivot table attributes
* @return plain object
* @private
*/
}, {
key: 'createPivotRecord',
value: function createPivotRecord(id) {
var pivots = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var record = _underscore2.default.extend({}, pivots);
record[this.otherKey] = this.model.get(this.localKey);
record[this.targetKey] = id;
return record;
}
/**
* Get the fully qualified compare key of the relation
*
* @return string
* @private
*/
}, {
key: 'getCompareKey',
value: function getCompareKey() {
return this._through.getQualifiedColumn(this.otherKey);
}
/**
* Apply eager constraints on the relation query
*
* @param {Array} models
* @private
*/
}, {
key: 'addEagerLoadConstraints',
value: function addEagerLoadConstraints(models) {
_get(_class.prototype.__proto__ || Object.getPrototypeOf(_class.prototype), 'addEagerLoadConstraints', this).call(this, models);
this.addPivotColumns();
this.addPivotJoin();
}
/**
* Set the columns of the relation query
*
* @private
*/
}, {
key: 'addPivotColumns',
value: function addPivotColumns() {
var _this7 = this;
var columns = this.pivotColumns.map(function (col) {
return _this7._through.getQualifiedColumn(col) + ' as pivot_' + col;
});
this.query.builder.select(columns);
}
/**
* Build model dictionary keyed by the given key
*
* @param {Collection} related
* @param {String} key
* @return plain object
* @private
*/
}, {
key: 'buildDictionary',
value: function buildDictionary(related, key) {
return related.groupBy(function (model) {
return model.related.pivot.get(key);
});
}
/**
* Add `join`constraints to the intermediate table
*
* @private
*/
}, {
key: 'addPivotJoin',
value: function addPivotJoin() {
this.query.join(this._through.table + ' as ' + this._through.alias, this._through.getQualifiedColumn(this.targetKey), this.query.getQualifiedColumn(this.target.primaryKey));
}
}]);
return _class;
}((0, _oneToMany2.default)(_base2.default));
exports.default = _class;