diff --git a/src/Lucid/Relations/HasMany.js b/src/Lucid/Relations/HasMany.js index 642d9a18..e1aba5cd 100644 --- a/src/Lucid/Relations/HasMany.js +++ b/src/Lucid/Relations/HasMany.js @@ -11,6 +11,7 @@ const _ = require('lodash') const BaseRelation = require('./BaseRelation') +const CE = require('../../Exceptions') /** * HasMany relationship instance is used to define a @@ -21,6 +22,23 @@ const BaseRelation = require('./BaseRelation') * @constructor */ class HasMany extends BaseRelation { + /** + * Persists the parent model instance if it's not + * persisted already. This is done before saving + * the related instance + * + * @method _persistParentIfRequired + * + * @return {void} + * + * @private + */ + async _persistParentIfRequired () { + if (this.parentInstance.isNew) { + await this.parentInstance.save() + } + } + /** * Load a single relationship from parent to child * model, but only for one row. @@ -104,6 +122,74 @@ class HasMany extends BaseRelation { } return this.relatedQuery.query } + + /** + * Saves the related instance to the database. Foreign + * key is set automatically + * + * @method save + * + * @param {Object} relatedInstance + * + * @return {Promise} + */ + async save (relatedInstance) { + await this._persistParentIfRequired() + relatedInstance[this.foreignKey] = this.$primaryKeyValue + return relatedInstance.save() + } + + /** + * Creates the new related instance model and persist + * it to database. Foreign key is set automatically + * + * @method create + * + * @param {Object} payload + * + * @return {Promise} + */ + async create (payload) { + await this._persistParentIfRequired() + payload[this.foreignKey] = this.$primaryKeyValue + return this.relatedModel.create(payload) + } + + /** + * Creates an array of model instances in parallel + * + * @method createMany + * + * @param {Array} arrayOfPayload + * + * @return {Array} + */ + async createMany (arrayOfPayload) { + if (arrayOfPayload instanceof Array === false) { + throw CE.InvalidArgumentException.invalidParamter('hasMany.createMany expects an array of values') + } + + await this._persistParentIfRequired() + return Promise.all(arrayOfPayload.map((payload) => this.create(payload))) + } + + /** + * Creates an array of model instances in parallel + * + * @method createMany + * + * @param {Array} arrayOfRelatedInstances + * + * @return {Array} + */ + async saveMany (arrayOfRelatedInstances) { + if (arrayOfRelatedInstances instanceof Array === false) { + throw CE.InvalidArgumentException.invalidParamter('hasMany.saveMany expects an array of related model instances') + } + + await this._persistParentIfRequired() + return Promise.all(arrayOfRelatedInstances.map((relatedInstance) => this.save(relatedInstance))) + } } module.exports = HasMany diff --git a/test/unit/lucid-has-many.spec.js b/test/unit/lucid-has-many.spec.js index b55e4d37..30043fb1 100644 --- a/test/unit/lucid-has-many.spec.js +++ b/test/unit/lucid-has-many.spec.js @@ -638,4 +638,174 @@ test.group('Relations | Has Many', (group) => { assert.isArray(json.data[0].cars) assert.isArray(json.data[1].cars) }) + + test('save related model instance', async (assert) => { + class Car extends Model { + } + + class User extends Model { + cars () { + return this.hasMany(Car) + } + } + + Car._bootIfNotBooted() + User._bootIfNotBooted() + + const user = new User() + user.username = 'virk' + await user.save() + + const mercedes = new Car() + mercedes.name = 'mercedes' + mercedes.model = '1992' + + await user.cars().save(mercedes) + assert.equal(mercedes.user_id, user.id) + assert.isTrue(mercedes.$persisted) + assert.isFalse(mercedes.isNew) + }) + + test('create related model instance', async (assert) => { + class Car extends Model { + } + + class User extends Model { + cars () { + return this.hasMany(Car) + } + } + + Car._bootIfNotBooted() + User._bootIfNotBooted() + + const user = new User() + user.username = 'virk' + await user.save() + + const mercedes = await user.cars().create({ name: 'mercedes', model: '1992' }) + assert.equal(mercedes.user_id, 1) + assert.equal(mercedes.user_id, user.id) + assert.isTrue(mercedes.$persisted) + assert.isFalse(mercedes.isNew) + }) + + test('persist parent model when isNew', async (assert) => { + class Car extends Model { + } + + class User extends Model { + cars () { + return this.hasMany(Car) + } + } + + Car._bootIfNotBooted() + User._bootIfNotBooted() + + const user = new User() + user.username = 'virk' + + const mercedes = await user.cars().create({ name: 'mercedes', model: '1992' }) + assert.equal(mercedes.user_id, 1) + assert.equal(mercedes.user_id, user.id) + assert.isTrue(mercedes.$persisted) + assert.isFalse(mercedes.isNew) + assert.isTrue(user.$persisted) + assert.isFalse(user.isNew) + }) + + test('persist parent model when isNew while calling save', async (assert) => { + class Car extends Model { + } + + class User extends Model { + cars () { + return this.hasMany(Car) + } + } + + Car._bootIfNotBooted() + User._bootIfNotBooted() + + const user = new User() + user.username = 'virk' + + const mercedes = new Car() + mercedes.name = 'mercedes' + mercedes.model = '1992' + + await user.cars().save(mercedes) + assert.equal(mercedes.user_id, 1) + assert.equal(mercedes.user_id, user.id) + assert.isTrue(mercedes.$persisted) + assert.isFalse(mercedes.isNew) + assert.isTrue(user.$persisted) + assert.isFalse(user.isNew) + }) + + test('saveMany of related instances', async (assert) => { + class Car extends Model { + } + + class User extends Model { + cars () { + return this.hasMany(Car) + } + } + + Car._bootIfNotBooted() + User._bootIfNotBooted() + + const user = new User() + user.username = 'virk' + + const mercedes = new Car() + mercedes.name = 'mercedes' + mercedes.model = '1992' + + const ferrari = new Car() + ferrari.name = 'ferrari' + ferrari.model = '2002' + + await user.cars().saveMany([mercedes, ferrari]) + assert.equal(mercedes.user_id, 1) + assert.equal(mercedes.user_id, user.id) + assert.equal(ferrari.user_id, user.id) + assert.isTrue(mercedes.$persisted) + assert.isFalse(ferrari.isNew) + assert.isTrue(ferrari.$persisted) + assert.isFalse(mercedes.isNew) + assert.isTrue(user.$persisted) + assert.isFalse(user.isNew) + }) + + test('createMany of related instances', async (assert) => { + class Car extends Model { + } + + class User extends Model { + cars () { + return this.hasMany(Car) + } + } + + Car._bootIfNotBooted() + User._bootIfNotBooted() + + const user = new User() + user.username = 'virk' + + const [mercedes, ferrari] = await user.cars().createMany([{ name: 'mercedes', model: '1992' }, { name: 'ferrari', model: '2002' }]) + + assert.equal(mercedes.user_id, 1) + assert.equal(mercedes.user_id, user.id) + assert.equal(ferrari.user_id, user.id) + assert.isTrue(mercedes.$persisted) + assert.isFalse(ferrari.isNew) + assert.isTrue(ferrari.$persisted) + assert.isFalse(mercedes.isNew) + assert.isTrue(user.$persisted) + assert.isFalse(user.isNew) + }) })