UNPKG

vitamin

Version:

Data Mapper library for Node.js applications

488 lines (377 loc) 14.2 kB
'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;