import utils from './utils' import { hasManyType } from './decorators' import Collection from './Collection' /** * TODO * * ```javascript * import {LinkedCollection} from 'js-data' * ``` * * @class LinkedCollection * @extends Collection * @param {Array} [records] Initial set of records to insert into the * collection. See {@link Collection}. * @param {Object} [opts] Configuration options. See {@link Collection}. * @return {Mapper} */ export default Collection.extend({ constructor: function LinkedCollection (records, opts) { const self = this utils.classCallCheck(self, LinkedCollection) LinkedCollection.__super__.call(self, records, opts) // Make sure this collection has somewhere to store "added" timestamps self._added = {} // Make sure this collection a reference to a datastore if (!self.datastore) { throw new Error('This collection must have a datastore!') } return self }, _onRecordEvent (...args) { const self = this utils.getSuper(self).prototype._onRecordEvent.apply(self, args) const event = args[0] // This is a very brute force method // Lots of room for optimization if (utils.isString(event) && event.indexOf('change') === 0) { self.updateIndexes(args[1]) } }, add (records, opts) { const self = this const datastore = self.datastore const mapper = self.mapper const relationList = mapper.relationList || [] const timestamp = new Date().getTime() const usesRecordClass = !!mapper.recordClass let singular if (utils.isObject(records) && !utils.isArray(records)) { singular = true records = [records] } if (relationList.length && records.length) { // Check the currently visited record for relations that need to be // inserted into their respective collections. mapper.relationList.forEach(function (def) { const relationName = def.relation // A reference to the Mapper that this Mapper is related to const relatedMapper = datastore.getMapper(relationName) // The field used by the related Mapper as the primary key const relationIdAttribute = relatedMapper.idAttribute // Grab the foreign key in this relationship, if there is one const foreignKey = def.foreignKey // A lot of this is an optimization for being able to insert a lot of // data as quickly as possible const relatedCollection = datastore.getCollection(relationName) const type = def.type const isHasMany = type === hasManyType const shouldAdd = utils.isUndefined(def.add) ? true : !!def.add let relatedData records.forEach(function (record) { // Grab a reference to the related data attached or linked to the // currently visited record relatedData = def.getLocalField(record) if (utils.isFunction(def.add)) { def.add(datastore, def, record) } else if (relatedData) { // Otherwise, if there is something to be added, add it if (isHasMany) { // Handle inserting hasMany relations relatedData = relatedData.map(function (toInsertItem) { // Check that this item isn't the same item that is already in the // store if (toInsertItem !== relatedCollection.get(relatedCollection.recordId(toInsertItem))) { // Make sure this item has its foreignKey if (foreignKey) { // TODO: slow, could be optimized? But user loses hook def.setForeignKey(record, toInsertItem) } // Finally add this related item if (shouldAdd) { toInsertItem = relatedCollection.add(toInsertItem) } } return toInsertItem }) // If it's the parent that has the localKeys if (def.localKeys) { utils.set(record, def.localKeys, relatedData.map(function (inserted) { return utils.get(inserted, relationIdAttribute) })) } } else { const relatedDataId = utils.get(relatedData, relationIdAttribute) // Handle inserting belongsTo and hasOne relations if (relatedData !== relatedCollection.get(relatedDataId)) { // Make sure foreignKey field is set def.setForeignKey(record, relatedData) // Finally insert this related item if (shouldAdd) { relatedData = relatedCollection.add(relatedData) } } } def.setLocalField(record, relatedData) } }) }) } records = utils.getSuper(self).prototype.add.call(self, records, opts) records.forEach(function (record) { // Track when this record was added self._added[self.recordId(record)] = timestamp if (usesRecordClass) { record._set('$', timestamp) } }) return singular ? records[0] : records }, remove (id, opts) { const self = this const mapper = self.mapper const record = utils.getSuper(self).prototype.remove.call(self, id, opts) if (record) { delete self._added[id] if (mapper.recordClass) { record._set('$') // unset } } return record }, removeAll (query, opts) { const self = this const mapper = self.mapper const records = utils.getSuper(self).prototype.removeAll.call(self, query, opts) records.forEach(function (record) { delete self._added[self.recordId(record)] if (mapper.recordClass) { record._set('$') // unset } }) return records } })