diff --git a/Documentation/Books/Users/Foxx/FoxxModel.mdpp b/Documentation/Books/Users/Foxx/FoxxModel.mdpp index aa31ed9dab..af8bf182ba 100644 --- a/Documentation/Books/Users/Foxx/FoxxModel.mdpp +++ b/Documentation/Books/Users/Foxx/FoxxModel.mdpp @@ -55,6 +55,47 @@ person.attributes // => { name: "Pete", admin: true, active: true } person.errors // => {admin: [ValidationError: value is not allowed]} ``` +The following events are emitted by a model: + +- beforeCreate +- afterCreate +- beforeSave +- afterSave +- beforeUpdate +- afterUpdate +- beforeRemove +- afterRemove + +Model lifecycle: + +```js +var person = new PersonModel(); +person.on('beforeCreate', function() { + var model = this; + model.fancyMethod(); // Do something fancy with the model +}); +var people = new Repository(appContext.collection("people"), { model: PersonModel }); + +people.save(person); +// beforeCreate() +// beforeSave() +// The model is created at db +// afterSave() +// afterCreate() + +people.update(person, data); +// beforeUpdate(data) +// beforeSave(data) +// The model is updated at db +// afterSave(data) +// afterUpdate(data) + +people.remove(person); +// beforeRemove() +// The model is deleted at db +// afterRemove() +``` + !SUBSECTION Extend @startDocuBlock JSF_foxx_model_extend diff --git a/js/server/modules/org/arangodb/foxx/model.js b/js/server/modules/org/arangodb/foxx/model.js index 703eba25fd..8ccdf9d3e0 100644 --- a/js/server/modules/org/arangodb/foxx/model.js +++ b/js/server/modules/org/arangodb/foxx/model.js @@ -32,6 +32,8 @@ var Model, joi = require("joi"), is = require("org/arangodb/is"), extend = require('org/arangodb/extend').extend, + EventEmitter = require('events').EventEmitter, + util = require('util'), excludeExtraAttributes, metadataSchema = { _id: joi.string().optional(), @@ -142,8 +144,11 @@ Model = function (attributes) { } else if (attributes) { instance.attributes = _.clone(attributes); } + EventEmitter.call(instance); }; +util.inherits(Model, EventEmitter); + Model.fromClient = function (attributes) { 'use strict'; return new this(excludeExtraAttributes(attributes, this)); diff --git a/js/server/modules/org/arangodb/foxx/repository.js b/js/server/modules/org/arangodb/foxx/repository.js index ae095a9c70..329f40d742 100644 --- a/js/server/modules/org/arangodb/foxx/repository.js +++ b/js/server/modules/org/arangodb/foxx/repository.js @@ -130,8 +130,12 @@ _.extend(Repository.prototype, { //////////////////////////////////////////////////////////////////////////////// save: function (model) { 'use strict'; + model.emit('beforeCreate'); + model.emit('beforeSave'); var id_and_rev = this.collection.save(model.forDB()); model.set(id_and_rev); + model.emit('afterSave'); + model.emit('afterCreate'); return model; }, @@ -262,8 +266,11 @@ _.extend(Repository.prototype, { //////////////////////////////////////////////////////////////////////////////// remove: function (model) { 'use strict'; - var id = model.get('_id'); - return this.collection.remove(id); + model.emit('beforeRemove'); + var id = model.get('_id'), + result = this.collection.remove(id); + model.emit('afterRemove'); + return result; }, //////////////////////////////////////////////////////////////////////////////// @@ -398,10 +405,14 @@ _.extend(Repository.prototype, { //////////////////////////////////////////////////////////////////////////////// update: function (model, data) { 'use strict'; + model.emit('beforeUpdate', data); + model.emit('beforeSave', data); var id = model.get("_id") || model.get("_key"), id_and_rev = this.collection.update(id, data); model.set(data); model.set(id_and_rev); + model.emit('afterSave', data); + model.emit('afterUpdate', data); return model; }, diff --git a/js/server/tests/shell-foxx-model-events-spec.js b/js/server/tests/shell-foxx-model-events-spec.js new file mode 100644 index 0000000000..a9df30490b --- /dev/null +++ b/js/server/tests/shell-foxx-model-events-spec.js @@ -0,0 +1,83 @@ +/*global require, describe, expect, it, beforeEach, createSpyObj */ + +var FoxxRepository = require("org/arangodb/foxx/repository").Repository, + Model = require("org/arangodb/foxx/model").Model; + +describe('Model Events', function () { + 'use strict'; + + var collection, instance, repository; + + beforeEach(function () { + collection = createSpyObj('collection', [ + 'update', + 'save', + 'remove' + ]); + instance = new Model({ random: '', beforeCalled: false, afterCalled: false }); + repository = new FoxxRepository(collection, {model: Model}); + }); + + it('should be possible to subscribe and emit events', function () { + expect(instance.on).toBeDefined(); + expect(instance.emit).toBeDefined(); + }); + + it('should emit beforeCreate and afterCreate events when creating the model', function () { + addHooks(instance, 'Create'); + expect(repository.save(instance)).toEqual(instance); + expect(instance.get('beforeCalled')).toBe(true); + expect(instance.get('afterCalled')).toBe(true); + }); + + it('should emit beforeSave and afterSave events when creating the model', function () { + addHooks(instance, 'Save'); + expect(repository.save(instance)).toEqual(instance); + expect(instance.get('beforeCalled')).toBe(true); + expect(instance.get('afterCalled')).toBe(true); + }); + + it('should emit beforeUpdate and afterUpdate events when updating the model', function () { + var newData = { newAttribute: 'test' }; + addHooks(instance, 'Update', newData); + expect(repository.update(instance, newData)).toEqual(instance); + expect(instance.get('beforeCalled')).toBe(true); + expect(instance.get('afterCalled')).toBe(true); + }); + + it('should emit beforeSave and afterSave events when updating the model', function () { + var newData = { newAttribute: 'test' }; + addHooks(instance, 'Save', newData); + expect(repository.update(instance, newData)).toEqual(instance); + expect(instance.get('beforeCalled')).toBe(true); + expect(instance.get('afterCalled')).toBe(true); + }); + + it('should emit beforeRemove and afterRemove events when removing the model', function () { + addHooks(instance, 'Remove'); + repository.remove(instance); + expect(instance.get('beforeCalled')).toBe(true); + expect(instance.get('afterCalled')).toBe(true); + }); + +}); + +function addHooks(model, ev, dataToReceive) { + 'use strict'; + + var random = String(Math.floor(Math.random() * 1000)); + + model.on('before' + ev, function (data) { + expect(this).toEqual(model); + expect(data).toEqual(dataToReceive); + this.set('random', random); + this.set('beforeCalled', true); + }); + model.on('after' + ev, function (data) { + expect(this).toEqual(model); + expect(data).toEqual(dataToReceive); + this.set('afterCalled', true); + expect(this.get('beforeCalled')).toBe(true); + expect(this.get('random')).toEqual(random); + }); +} \ No newline at end of file