UNPKG

openrecord

Version:
255 lines (211 loc) 8.48 kB
exports.definition = { belongsToPolymorphic: function(name, options) { const Store = require('../../store') const self = this const utils = this.store.utils options = options || {} options.type = 'belongs_to_polymorphic' options.preInitialize = function() { options.storeField = options.storeField || name + '_store' options.typeField = options.typeField || name + '_type' options.idField = options.idField || name + '_id' options.from = [] // set to empty array to avoid any conflicts options.to = [] } options.setter = options.setter || function(record) { if (this.relations[name] && record === this.relations[name][0]) return var chain = this.relations[name] // if no relational data exists (e.g. Parent.new()) // create a new collection if (!chain) { chain = this.model.chain({ polymorph: true }) chain.setInternal('relation', options) // add the parent relation of this collection chain.setInternal('relation_to', this) // + the parent record options.setResult(this, chain) } if (Array.isArray(record)) record = record[0] if (record === null) { this[options.typeField] = null this[options.idField] = null } else { chain.remove(0) chain.add(record) } chain._resolved() } // add a single record options.add = function(parent, record) { record.set(options.conditions) // add relation conditions to record. Will only work in `equality` conditions. e.g. {type: 'Foo'} parent[options.typeField] = record.model.modelName parent[options.idField] = record[record.definition.primaryKeys[0]] options.setResult(parent, record.__chainedModel) } options.beforeSave = options.beforeSave || function(parent, saveOptions) { const collection = parent.relations[name] if (collection) { return collection.save(saveOptions).then(function() { collection.forEach(function(record) { // set new ids on parent options.add(parent, record) }) }) } } options.afterDestroy = options.afterDestroy || function(parent, transOptions) { const Model = self.store.Model(parent[options.typeField]) if (!Model) return const conditions = utils.recordsToConditions( [parent], [options.idField], [Model.definition.primaryKeys[0]] ) if (!conditions) return var dependent = options.dependent if (typeof dependent === 'object') dependent = dependent[parent[options.typeField]] // Allow for `dependent: {'ModelA': 'delete', 'ModelB': 'nullify'}` const query = Model.where(conditions) if (options.conditions) query.where(options.conditions) // conditions on the relation if (options.scope) query[options.scope]() // scope on the relation if (dependent === 'delete') { return query.deleteAll(transOptions) } if (dependent === 'destroy') { return query.destroyAll(transOptions) } // ignore `nullify` on belongs to relation -> got deleted anyways } // remove a single record options.remove = function(parent, record) { this._lazyOperation(function(transOptions) { var dependent = options.dependent if (typeof dependent === 'object') dependent = dependent[parent[options.typeField]] // Allow for `dependent: {'ModelA': 'delete', 'ModelB': 'nullify'}` if (dependent === 'delete') { return record.delete(transOptions) } if (dependent === 'destroy') { return record.destroy(transOptions) } // nullify! options.from.forEach(function(key) { parent[key] = null }) // to need to return parent.save() // because lazy operations will be triggered only by parent.save() // so it would be a double save... }) } options.loadFromRecords = function(parentCollection, include) { const relationCache = parentCollection.getInternal('relation_cache') || {} const targetMap = {} const jobs = [] // return cached result, if available! if (relationCache[options.name]) { const result = relationCache[options.name] if (!result.then) return Promise.resolve(relationCache[options.name]) return result } parentCollection.forEach(function(record) { const targetStore = record[options.storeField] || '' const targetModel = record[options.typeField] const targetId = record[options.idField] const key = targetStore + '|' + targetModel if (!targetModel || !targetId) return targetMap[key] = targetMap[key] || [] targetMap[key].push(targetId) }) Object.keys(targetMap).forEach(function(key) { const keyParts = key.split('|') var store = self.store if (keyParts[0]) store = Store.getStoreByName(keyParts[0]) if (!store) throw new Error( "Unknown store '" + keyParts[0] + "' on polymorphic relation '" + name + "'" ) const model = store.Model(keyParts[1]) if (!model) throw new Error( "Unknown model '" + keyParts[1] + "' on polymorphic relation '" + name + "'" ) const primaryKeys = model.definition.primaryKeys if (primaryKeys.length > 1) throw new Error( "Can't load " + model.definition.modelName + ' as a polymorhic relation. Only one primary key is allowed!' ) if (primaryKeys.length === 0) throw new Error( "Can't load " + model.definition.modelName + ' as a polymorhic relation. A primary key is required!' ) const conditions = {} conditions[primaryKeys[0]] = targetMap[key] const chain = model.where(conditions) if (include.children) { utils.toIncludesList(include.children).forEach(function(inc) { // include only if the polymorphic model has a relation with the same name // otherwise ignore it! if (chain.definition.relations[inc.relation]) { chain.addInternal('includes', inc) } }) } jobs.push(chain) }) return utils.parallel(jobs).then(function(result) { // save result in parent collection relationCache[options.name] = result parentCollection.setInternal('relation_cache', relationCache) // add the result to corresponding record parentCollection.forEach(function(record) { record.relations[name] = options.filterCache(record, result) }) return result }) } options.filterCache = function(parentRecord, records) { var chain = [] if (!records) return null records.forEach(function(collection) { if ( collection.definition.modelName === parentRecord[options.typeField] ) { // now we need to filter the collection by `options.idField` only chain = collection.model.chain() const relatedRecords = collection.filter(function(record) { const primaryKey = collection.definition.primaryKeys[0] return record[primaryKey] === parentRecord[options.idField] }) chain.setInternal('relation', options) // add the parent relation of this collection chain.setInternal('relation_to', parentRecord) // + the parent record chain.add(relatedRecords) // add all child records chain._exists() // set this collection as existing (all records fetched from the datastore!) } }) return chain[0] } options.collection = function(parentRecord) { const chain = parentRecord.definition.model.chain({ polymorph: true }) chain.setInternal('relation', options) // add the parent relation of this collection chain.setInternal('relation_to', parentRecord) // + the parent record return chain } return this.relation(name, options) } }