Socket
Socket
Sign inDemoInstall

redux-orm

Package Overview
Dependencies
Maintainers
1
Versions
70
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

redux-orm - npm Package Compare versions

Comparing version 0.1.24 to 0.2.0

lib/test/testIntegrations.js

5

lib/Backend.js

@@ -213,2 +213,4 @@ 'use strict';

value: function update(branch, idArr, patcher) {
var _this3 = this;
var returnBranch = this.withMutations ? branch : {};

@@ -225,5 +227,6 @@

mapFunction = function (entity) {
var assignTo = _this3.withMutations ? entity : {};
var diff = (0, _utils.objectDiff)(entity, patcher);
if (diff) {
return Object.assign({}, entity, patcher);
return Object.assign(assignTo, entity, patcher);
}

@@ -230,0 +233,0 @@ return entity;

4

lib/descriptors.js

@@ -98,3 +98,3 @@ 'use strict';

var throughQs = throughModel.filter(lookupObj);
var toIds = throughQs.plain.map(function (obj) {
var toIds = throughQs.withRefs.map(function (obj) {
return obj[reverse ? fromFieldName : toFieldName];

@@ -132,3 +132,3 @@ });

var attrInIdsToRemove = reverse ? fromFieldName : toFieldName;
var entitiesToDelete = throughQs.plain.filter(function (through) {
var entitiesToDelete = throughQs.withRefs.filter(function (through) {
return idsToRemove.includes(through[attrInIdsToRemove]);

@@ -135,0 +135,0 @@ });

@@ -77,3 +77,2 @@ 'use strict';

this._fields = props;
var idAttribute = ModelClass.idAttribute;

@@ -83,3 +82,2 @@ (0, _lodashObjectForOwn2['default'])(props, function (fieldValue, fieldName) {

_this._fieldNames.push(fieldName);
// If the field has not already been defined on the

@@ -94,3 +92,4 @@ // prototype for a relation.

return _this.set(fieldName, value);
}
},
configurable: true
});

@@ -130,7 +129,14 @@ }

/**
* Returns a reference to the plain JS object in the store.
* Make sure to not mutate this.
*
* @return {Object} a reference to the plain JS object in the store
*/
}, {
key: 'toString',
/**
* Returns a string representation of the {@link Model} instance.
* @return {string} A string representation of this {@link Model} instance.
*/
}, {
key: 'toString',
value: function toString() {

@@ -153,19 +159,2 @@ var _this2 = this;

/**
* Returns a plain JavaScript object representation
* of the {@link Model} instance.
* @return {Object} a plain JavaScript object representing the {@link Model}
*/
}, {
key: 'toPlain',
value: function toPlain() {
var _this3 = this;
var obj = {};
this._fieldNames.forEach(function (fieldName) {
obj[fieldName] = _this3._fields[fieldName];
});
return obj;
}
/**
* Records a update to the {@link Model} instance for a single

@@ -184,3 +173,4 @@ * field value assignment.

/**
* Records a update to the {@link Model} instance for multiple field value assignments.
* Records an update to the {@link Model} instance for multiple field value assignments.
* If the session is with mutations, updates the instance to reflect the new values.
* @param {Object} userMergeObj - an object that will be merged with this instance.

@@ -229,2 +219,7 @@ * @return {undefined}

var session = this.getClass().session;
if (session && session.withMutations) {
this._initFields(Object.assign({}, this._fields, mergeObj));
}
this.getClass().addUpdate({

@@ -276,2 +271,7 @@ type: _constants.UPDATE,

}
}, {
key: 'ref',
get: function get() {
return this.getClass().accessId(this.getId());
}
}], [{

@@ -320,6 +320,3 @@ key: 'toString',

}, {
key: 'clearSessionCache',
value: function clearSessionCache() {
this.__sessionCache = {};
}
key: 'getBackend',

@@ -330,16 +327,19 @@ /**

*/
}, {
key: 'getBackend',
value: function getBackend() {
if (!this._sessionCache.backend) {
if (!this._sessionData.backend) {
var BackendClass = this.getBackendClass();
var opts = this._getBackendOpts();
var opts = this._getBackendOpts();
if (this._session && this._session.withMutations) {
if (this.session && this.session.withMutations) {
opts.withMutations = true;
}
this._sessionCache.backend = new BackendClass(opts);
var backend = new BackendClass(opts);
if (!this.session) {
return backend;
}
this._sessionData.backend = backend;
}
return this._sessionCache.backend;
return this._sessionData.backend;
}

@@ -363,2 +363,12 @@

}
/**
* A reducer that takes the Model's state and an internal redux-orm
* action object and applies the update specified by the `action` object
* by delegating to this model's Backend instance.
*
* @param {Object} state - the Model's state
* @param {Object} action - the internal redux-orm update action to apply
* @return {Object} the state after applying the action
*/
}, {

@@ -391,2 +401,3 @@ key: 'updateReducer',

* @param {Session} session - the current {@link Session} instance
* @return {Object} the next state for the Model
*/

@@ -396,2 +407,3 @@ }, {

value: function reducer(state, action, model, session) {
// eslint-disable-line
return model.getNextState();

@@ -461,4 +473,3 @@ }

/**
* Connect the model class to a {@link Session}. Invalidates
* the session-specific cache.
* Connect the model class to a {@link Session}.
*

@@ -474,3 +485,2 @@ * @param {Session} session - The session to connect to.

this._session = session;
this.clearSessionCache();
}

@@ -504,11 +514,11 @@

value: function nextId() {
if (typeof this._sessionCache.nextId === 'undefined') {
if (typeof this._sessionData.nextId === 'undefined') {
var idArr = this.accessIds();
if (idArr.length === 0) {
this._sessionCache.nextId = 0;
this._sessionData.nextId = 0;
} else {
this._sessionCache.nextId = Math.max.apply(Math, _toConsumableArray(idArr)) + 1;
this._sessionData.nextId = Math.max.apply(Math, _toConsumableArray(idArr)) + 1;
}
}
return this._sessionCache.nextId;
return this._sessionData.nextId;
}

@@ -553,3 +563,3 @@ }, {

value: function create(userProps) {
var _this4 = this;
var _this3 = this;

@@ -562,7 +572,7 @@ var idAttribute = this.idAttribute;

props[idAttribute] = nextId;
this._sessionCache.nextId++;
this._sessionData.nextId++;
} else {
var id = props[idAttribute];
if (id > this.nextId()) {
this._sessionCache.nextId = id + 1;
this._sessionData.nextId = id + 1;
}

@@ -579,3 +589,3 @@ }

if ((0, _lodashLangIsArray2['default'])(value)) {
if (_this4.fields.hasOwnProperty(key) && _this4.fields[key] instanceof _fields.ManyToMany) {
if (_this3.fields.hasOwnProperty(key) && _this3.fields[key] instanceof _fields.ManyToMany) {
m2mVals[key] = value;

@@ -674,8 +684,6 @@ delete props[key];

}, {
key: '_sessionCache',
key: '_sessionData',
get: function get() {
if (!this.hasOwnProperty('__sessionCache')) {
this.__sessionCache = {};
}
return this.__sessionCache;
if (!this.session) return {};
return this.session.getDataForModel(this.modelName);
}

@@ -695,6 +703,6 @@ }, {

get: function get() {
if (!this._sessionCache.queryset) {
this._sessionCache.queryset = this.getQuerySet();
if (!this._sessionData.queryset) {
this._sessionData.queryset = this.getQuerySet();
}
return this._sessionCache.queryset;
return this._sessionData.queryset;
}

@@ -701,0 +709,0 @@ }]);

@@ -11,4 +11,2 @@ 'use strict';

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

@@ -59,6 +57,6 @@

// Results are plain objects by default.
if (opts && opts.hasOwnProperty('plain')) {
this._plain = opts.plain;
if (opts && opts.hasOwnProperty('withRefs')) {
this._withRefs = opts.withRefs;
} else {
this._plain = false;
this._withRefs = false;
}

@@ -69,7 +67,13 @@ }

key: '_new',
value: function _new(ids) {
var plain = this._plain;
var opts = Object.assign({}, this._opts, { plain: plain });
value: function _new(ids, userOpts) {
var opts = Object.assign({}, this._opts, userOpts);
return new this.constructor(this.modelClass, ids, opts);
}
/**
* Returns a new QuerySet representing the same entities
* with the `withRefs` flag on.
*
* @return {QuerySet}
*/
}, {

@@ -87,7 +91,10 @@ key: 'toString',

* Returns an array of the plain objects represented by the QuerySet.
* @return {Object[]}
* The plain objects are direct references to the store.
*
* @return {Object[]} references to the plain JS objects represented by
* the QuerySet
*/
}, {
key: 'toPlain',
value: function toPlain() {
key: 'toRefArray',
value: function toRefArray() {
var _this2 = this;

@@ -101,2 +108,16 @@

/**
* Returns an array of the Model instances represented by the QuerySet.
* @return {Model[]} model instances represented by the QuerySet
*/
}, {
key: 'toModelArray',
value: function toModelArray() {
var _this3 = this;
return this.idArr.map(function (_, idx) {
return _this3.at(idx);
});
}
/**
* Returns the number of model instances represented by the QuerySet.

@@ -122,5 +143,8 @@ * @return {number} length of the QuerySet

/**
* Returns the {@link Model} instance at index `index` in the QuerySet.
* Returns the {@link Model} instance at index `index` in the QuerySet if
* `withRefs` flag is set to `false`, or a reference to the plain JavaScript
* object in the model state if `true`.
* @param {number} index - index of the model instance to get
* @return {Model} an {@link Model} instance at index `index` in the QuerySet
* @return {Model|Object} a {@link Model} instance or a plain JavaScript
* object at index `index` in the QuerySet
*/

@@ -130,6 +154,6 @@ }, {

value: function at(index) {
if (this._plain) {
if (this._withRefs) {
return this.modelClass.accessId(this.idArr[index]);
}
return this.modelClass.get(_defineProperty({}, this.modelClass.idAttribute, this.idArr[index]));
return this.modelClass.withId(this.idArr[index]);
}

@@ -168,19 +192,2 @@

/**
* Returns all objects in this QuerySet. If the plain flag
* is on (default), this will be a list of ordinary JavaScript objects.
* If it is off, these will be Model instances.
*
* @return {Array} An array of either JavaScript objects or Model instances.
*/
}, {
key: 'objects',
value: function objects() {
var _this3 = this;
return this.idArr.map(function (_, idx) {
return _this3.at(idx);
});
}
/**
* Returns a new {@link QuerySet} with objects that match properties in `lookupObj`.

@@ -213,4 +220,4 @@ *

var startPlainFlag = this._plain;
var func = exclude ? _lodashCollectionReject2['default'] : _lodashCollectionFilter2['default'];
var operationWithRefs = true;
var entities = undefined;

@@ -221,11 +228,15 @@ if (typeof lookupObj === 'function') {

// is flagged.
entities = this.objects();
if (this._withRefs) {
entities = this.toRefArray();
} else {
entities = this.toModelArray();
operationWithRefs = false;
}
} else {
// Lodash filtering doesn't work with
// Model instances.
entities = this.plain.objects();
entities = this.toRefArray();
}
var filteredEntities = func(entities, lookupObj);
var getIdFunc = this._plain ? function (obj) {
var getIdFunc = operationWithRefs ? function (obj) {
return obj[_this4.modelClass.idAttribute];

@@ -238,5 +249,3 @@ } : function (obj) {

// Return flag to original value.
this._plain = startPlainFlag;
return this._new(newIdArr);
return this._new(newIdArr, { withRefs: false });
}

@@ -256,3 +265,5 @@

value: function forEach(func) {
this.objects().forEach(func);
var arr = this._withRefs ? this.toRefArray() : this.toModelArray();
arr.forEach(func);
}

@@ -263,3 +274,5 @@

* @param {Function} func - the mapping function that takes one argument, a
* {@link Model} instance.
* {@link Model} instance or a reference to the plain
* JavaScript object in the store, depending on the
* QuerySet's `withRefs` flag.
* @return {Array} the mapped array

@@ -285,9 +298,29 @@ */

key: 'orderBy',
value: function orderBy(args) {
value: function orderBy(iteratees, orders) {
var _this6 = this;
var entities = _lodashCollectionSortByOrder2['default'].apply([this.objects()].concat(args));
return this._new(entities.map(function (entity) {
var entities = this.toRefArray();
var iterateeArgs = iteratees;
// Lodash only works on plain javascript objects.
// If the argument is a function, and the `withRefs`
// flag is false, the argument function is wrapped
// to get the model instance and pass that as the argument
// to the user-supplied function.
if (!this._withRefs) {
iterateeArgs = iteratees.map(function (arg) {
if (typeof arg === 'function') {
return function (entity) {
var id = entity[_this6.modelClass.idAttribute];
var instance = _this6.modelClass.withId(id);
return arg(instance);
};
}
return arg;
});
}
var sortedEntities = _lodashCollectionSortByOrder2['default'].call(null, entities, iterateeArgs, orders);
return this._new(sortedEntities.map(function (entity) {
return entity[_this6.modelClass.idAttribute];
}));
}), { withRefs: false });
}

@@ -327,18 +360,37 @@

var originalFlag = this._plain;
this.models.forEach(function (model) {
this.withModels.forEach(function (model) {
return model._onDelete();
});
this._plain = originalFlag;
}
}, {
key: 'plain',
key: 'withRefs',
get: function get() {
this._plain = true;
if (!this._withRefs) {
return this._new(this.idArr, { withRefs: true });
}
return this;
}
/**
* Alias for withRefs
* @return {QuerySet}
*/
}, {
key: 'models',
key: 'ref',
get: function get() {
this._plain = false;
return this.withRefs;
}
/**
* Returns a new QuerySet representing the same entities
* with the `withRefs` flag off.
*
* @return {QuerySet}
*/
}, {
key: 'withModels',
get: function get() {
if (this._withRefs) {
return this._new(this.idArr, { withRefs: false });
}
return this;

@@ -356,5 +408,5 @@ }

QuerySet.sharedMethods = ['toPlain', 'count', 'at', 'all', 'last', 'first', 'forEach', 'exists', 'filter', 'map', 'exclude', 'orderBy', 'update', 'delete'];
QuerySet.sharedMethods = ['count', 'at', 'all', 'last', 'first', 'forEach', 'exists', 'filter', 'map', 'exclude', 'orderBy', 'update', 'delete', 'ref', 'withRefs', 'withModels'];
exports['default'] = QuerySet;
module.exports = exports['default'];

@@ -108,14 +108,2 @@ 'use strict';

/**
* Sets a reducer function to the model with `modelName`.
* @param {string} modelName - The name of the model you want to set a reducer to
* @param {Function} reducer - The reducer function.
*/
}, {
key: 'setReducer',
value: function setReducer(modelName, reducer) {
var model = this.get(modelName);
model.reducer = reducer;
}
/**
* Registers a model class to the schema.

@@ -205,5 +193,5 @@ *

}, {
key: 'getModelClasses',
value: function getModelClasses() {
this.setupModelPrototypes();
key: '_getModelClasses',
value: function _getModelClasses() {
this._setupModelPrototypes();
return this.registry.concat(this.implicitThroughModels);

@@ -219,4 +207,4 @@ }

}, {
key: 'setupModelPrototypes',
value: function setupModelPrototypes() {
key: '_setupModelPrototypes',
value: function _setupModelPrototypes() {
var _this3 = this;

@@ -228,42 +216,45 @@

(0, _lodashObjectForOwn2['default'])(fields, function (fieldInstance, fieldName) {
var toModelName = fieldInstance.toModelName;
var toModel = toModelName === 'this' ? model : _this3.get(toModelName);
var descriptor = Object.getOwnPropertyDescriptor(model.prototype, fieldName);
if (typeof descriptor === 'undefined') {
var toModelName = fieldInstance.toModelName;
var toModel = toModelName === 'this' ? model : _this3.get(toModelName);
if (fieldInstance instanceof _fields.ForeignKey) {
// Forwards.
Object.defineProperty(model.prototype, fieldName, (0, _descriptors.forwardManyToOneDescriptor)(fieldName, toModel));
model.definedProperties[fieldName] = true;
if (fieldInstance instanceof _fields.ForeignKey) {
// Forwards.
Object.defineProperty(model.prototype, fieldName, (0, _descriptors.forwardManyToOneDescriptor)(fieldName, toModel));
model.definedProperties[fieldName] = true;
// Backwards.
var backwardsFieldName = fieldInstance.relatedName ? fieldInstance.relatedName : (0, _utils.reverseFieldName)(model.modelName);
// Backwards.
var backwardsFieldName = fieldInstance.relatedName ? fieldInstance.relatedName : (0, _utils.reverseFieldName)(model.modelName);
Object.defineProperty(toModel.prototype, backwardsFieldName, (0, _descriptors.backwardManyToOneDescriptor)(fieldName, model));
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new _fields.ForeignKey(model.modelName, fieldName);
} else if (fieldInstance instanceof _fields.ManyToMany) {
// Forwards.
var throughModelName = (0, _utils.m2mName)(model.modelName, fieldName);
var throughModel = _this3.get(throughModelName);
Object.defineProperty(toModel.prototype, backwardsFieldName, (0, _descriptors.backwardManyToOneDescriptor)(fieldName, model));
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new _fields.ForeignKey(model.modelName, fieldName);
} else if (fieldInstance instanceof _fields.ManyToMany) {
// Forwards.
var throughModelName = (0, _utils.m2mName)(model.modelName, fieldName);
var throughModel = _this3.get(throughModelName);
Object.defineProperty(model.prototype, fieldName, (0, _descriptors.manyToManyDescriptor)(model, toModel, throughModel, false));
model.definedProperties[fieldName] = true;
model.virtualFields[fieldName] = new _fields.ManyToMany(toModel.modelName, fieldName);
Object.defineProperty(model.prototype, fieldName, (0, _descriptors.manyToManyDescriptor)(model, toModel, throughModel, false));
model.definedProperties[fieldName] = true;
model.virtualFields[fieldName] = new _fields.ManyToMany(toModel.modelName, fieldName);
// Backwards.
var backwardsFieldName = fieldInstance.relatedName ? fieldInstance.relatedName : (0, _utils.reverseFieldName)(model.modelName);
// Backwards.
var backwardsFieldName = fieldInstance.relatedName ? fieldInstance.relatedName : (0, _utils.reverseFieldName)(model.modelName);
Object.defineProperty(toModel.prototype, backwardsFieldName, (0, _descriptors.manyToManyDescriptor)(model, toModel, throughModel, true));
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new _fields.ManyToMany(model.modelName, fieldName);
} else if (fieldInstance instanceof _fields.OneToOne) {
// Forwards.
Object.defineProperty(model.prototype, fieldName, (0, _descriptors.forwardOneToOneDescriptor)(fieldName, toModel));
model.definedProperties[fieldName] = true;
Object.defineProperty(toModel.prototype, backwardsFieldName, (0, _descriptors.manyToManyDescriptor)(model, toModel, throughModel, true));
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new _fields.ManyToMany(model.modelName, fieldName);
} else if (fieldInstance instanceof _fields.OneToOne) {
// Forwards.
Object.defineProperty(model.prototype, fieldName, (0, _descriptors.forwardOneToOneDescriptor)(fieldName, toModel));
model.definedProperties[fieldName] = true;
// Backwards.
var backwardsFieldName = fieldInstance.relatedName ? fieldInstance.relatedName : model.modelName.toLowerCase();
// Backwards.
var backwardsFieldName = fieldInstance.relatedName ? fieldInstance.relatedName : model.modelName.toLowerCase();
Object.defineProperty(toModel.prototype, backwardsFieldName, (0, _descriptors.backwardOneToOneDescriptor)(fieldName, model));
model.definedProperties[backwardsFieldName] = true;
model.virtualFields[backwardsFieldName] = new _fields.OneToOne(model.modelName, fieldName);
Object.defineProperty(toModel.prototype, backwardsFieldName, (0, _descriptors.backwardOneToOneDescriptor)(fieldName, model));
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new _fields.OneToOne(model.modelName, fieldName);
}
}

@@ -290,6 +281,11 @@ });

}
/**
* Returns the default state
* @return {Object} the default state
*/
}, {
key: 'getDefaultState',
value: function getDefaultState() {
var models = this.getModelClasses();
var models = this._getModelClasses();
var state = {};

@@ -301,7 +297,2 @@ models.forEach(function (modelClass) {

}
}, {
key: 'fromEmpty',
value: function fromEmpty(action) {
return new _Session2['default'](this.getModelClasses(), this.getDefaultState(), action);
}

@@ -312,3 +303,3 @@ /**

* @param {Object} state - the state the database manages
* @param {Object} action - the dispatched action object
* @param {Object} [action] - the dispatched action object
* @return {Session} a new session instance

@@ -319,3 +310,3 @@ */

value: function from(state, action) {
return new _Session2['default'](this.getModelClasses(), state, action);
return new _Session2['default'](this._getModelClasses(), state, action);
}

@@ -325,3 +316,3 @@ }, {

value: function withMutations(state) {
return new _Session2['default'](this.getModelClasses(), state, undefined, true);
return new _Session2['default'](this._getModelClasses(), state, undefined, true);
}

@@ -328,0 +319,0 @@

@@ -47,2 +47,3 @@ 'use strict';

this._accessedModels = {};
this.modelData = {};

@@ -63,14 +64,23 @@ models.forEach(function (modelClass) {

value: function markAccessed(model) {
this._accessedModels[model.modelName] = true;
this.getDataForModel(model.modelName).accessed = true;
}
}, {
key: 'addUpdate',
key: 'getDataForModel',
value: function getDataForModel(modelName) {
if (!this.modelData[modelName]) {
this.modelData[modelName] = {};
}
return this.modelData[modelName];
}
/**
* Records an update to the session.
* @param {Object} update - the update object. Must have keys
* `type`, `payload` and `meta`. `meta`
* must also include a `name` attribute
* that contains the model name.
* `type`, `payload` and `meta`. `meta`
* must also include a `name` attribute
* that contains the model name.
*/
}, {
key: 'addUpdate',
value: function addUpdate(update) {

@@ -80,4 +90,6 @@ if (this.withMutations) {

var modelState = this.getState(modelName);
var state = typeof modelState === 'undefined' ? this[modelName].getDefaultState() : modelState;
var state = modelState || this[modelName].getDefaultState();
// The backend used in the updateReducer
// will mutate the model state.
this[modelName].updateReducer(state, update);

@@ -121,2 +133,4 @@ } else {

* the next state.
* If the session uses mutations, just returns the state.
*
* @return {Object} The next state

@@ -129,2 +143,4 @@ */

if (this.withMutations) return this.state;
var nextState = {};

@@ -149,3 +165,9 @@ var currentAction = this.action;

get: function get() {
return Object.keys(this._accessedModels);
var _this3 = this;
return this.models.filter(function (model) {
return !!_this3.getDataForModel(model.modelName).accessed;
}).map(function (model) {
return model.modelName;
});
}

@@ -152,0 +174,0 @@ }]);

@@ -84,5 +84,2 @@ 'use strict';

expect(instance).to.be.an.instanceOf(BackendMockClass);
// Make sure the previous instance is cached
expect(Model.getBackend()).to.equal(instance);
});

@@ -153,13 +150,16 @@ });

var instance = undefined;
var sessionMock = {};
var stateMock = {};
var actionMock = {};
var sessionMock = undefined;
var stateMock = undefined;
var actionMock = undefined;
sessionMock.action = actionMock;
beforeEach(function () {
sessionMock = {};
stateMock = {};
actionMock = {};
sessionMock.getState = function () {
return stateMock;
};
sessionMock.action = actionMock;
beforeEach(function () {
sessionMock.getState = function () {
return stateMock;
};
// Get a fresh copy

@@ -180,2 +180,5 @@ // of Model, so our manipulations

Model.modelName = 'Model';
Model.markAccessed = function () {
return undefined;
};

@@ -231,7 +234,15 @@ instance = new Model({ id: 0, name: 'Tommi' });

it('toPlain works correctly', function () {
expect(instance.toPlain()).to.deep.equal({
id: 0,
name: 'Tommi'
});
it('ref works correctly', function () {
var backendMock = {};
Model.getBackend = function () {
return backendMock;
};
var dbObj = {};
backendMock.accessId = function () {
return dbObj;
};
Model._session = sessionMock;
sessionMock.backend = backendMock;
expect(instance.ref).to.equal(dbObj);
});

@@ -238,0 +249,0 @@

@@ -25,5 +25,5 @@ 'use strict';

var _Model2 = require('../Model');
var _Model = require('../Model');
var _Model3 = _interopRequireDefault(_Model2);
var _Model2 = _interopRequireDefault(_Model);

@@ -40,110 +40,81 @@ var _Schema = require('../Schema');

var _utils = require('./utils');
_chai2['default'].use(_sinonChai2['default']);
var expect = _chai2['default'].expect;
describe('QuerySet', function () {
var modelClassMock = undefined;
var PersonClass = undefined;
var state = {
Person: {
items: [0, 1, 2],
itemsById: {
0: {
id: 0,
name: 'Tommi',
age: 25
},
1: {
id: 1,
name: 'John',
age: 50
},
2: {
id: 2,
name: 'Mary',
age: 60
}
}
}
};
var schema = undefined;
describe('QuerySet tests', function () {
var session = undefined;
var qs = undefined;
var bookQs = undefined;
var genreQs = undefined;
beforeEach(function () {
schema = new _Schema2['default']();
// Start off with a fresh Model class for each
// test.
PersonClass = (function (_Model) {
_inherits(Person, _Model);
var _createTestSessionWithData = (0, _utils.createTestSessionWithData)();
function Person() {
_classCallCheck(this, Person);
session = _createTestSessionWithData.session;
_get(Object.getPrototypeOf(Person.prototype), 'constructor', this).apply(this, arguments);
}
return Person;
})(_Model3['default']);
PersonClass.modelName = 'Person';
schema.register(PersonClass);
session = schema.from(state);
qs = session.Person.query;
bookQs = session.Book.getQuerySet();
genreQs = session.Genre.getQuerySet();
});
it('count works correctly', function () {
expect(qs.count()).to.equal(3);
var emptyQs = new _QuerySet3['default'](session.Person, []);
expect(emptyQs.count()).to.equal(0);
expect(bookQs.count()).to.equal(3);
expect(genreQs.count()).to.equal(4);
});
it('exists works correctly', function () {
expect(qs.exists()).to.equal(true);
expect(bookQs.exists()).to.be['true'];
var empty = new _QuerySet3['default'](session.Person, []);
expect(empty.exists()).to.equal(false);
var emptyQs = new _QuerySet3['default'](session.Book, []);
expect(emptyQs.exists()).to.be['false'];
});
it('at works correctly', function () {
expect(qs.plain.at(0)).to.equal(state.Person.itemsById[0]);
expect(qs.plain.at(2)).to.equal(state.Person.itemsById[2]);
expect(qs.at(0)).to.deep.equal({ id: 0, name: 'Tommi', age: 25 });
expect(bookQs.at(0)).to.be.an.instanceOf(_Model2['default']);
expect(bookQs.ref.at(0)).to.equal(session.Book.state.itemsById[0]);
});
it('first works correctly', function () {
expect(qs.first()).to.deep.equal(qs.at(0));
expect(qs.plain.first()).to.deep.equal(qs.at(0));
expect(bookQs.first()).to.deep.equal(bookQs.at(0));
});
it('last works correctly', function () {
expect(qs.last()).to.deep.equal(qs.at(2));
expect(qs.plain.last()).to.equal(state.Person.itemsById[2]);
var lastIndex = bookQs.count() - 1;
expect(bookQs.last()).to.deep.equal(bookQs.at(lastIndex));
});
it('all works correctly', function () {
var all = qs.all();
var all = bookQs.all();
expect(all).not.to.equal(qs);
expect(all.idArr).to.deep.equal(qs.idArr);
expect(all).not.to.equal(bookQs);
expect(all.idArr).to.deep.equal(bookQs.idArr);
});
it('filter works correctly with object argument', function () {
var filtered = qs.plain.filter({ name: 'Tommi' });
var filtered = bookQs.withRefs.filter({ name: 'Clean Code' });
expect(filtered.count()).to.equal(1);
expect(filtered.first()).to.equal(state.Person.itemsById[0]);
expect(filtered.ref.first()).to.equal(session.Book.state.itemsById[1]);
});
it('orderBy works correctly with prop argument', function () {
var ordered = bookQs.orderBy(['releaseYear']);
expect(ordered.idArr).to.deep.equal([1, 2, 0]);
});
it('orderBy works correctly with function argument', function () {
var ordered = bookQs.orderBy([function (book) {
return book.releaseYear;
}]);
expect(ordered.idArr).to.deep.equal([1, 2, 0]);
});
it('exclude works correctly with object argument', function () {
var excluded = qs.exclude({ name: 'Tommi' });
var excluded = bookQs.exclude({ name: 'Clean Code' });
expect(excluded.count()).to.equal(2);
expect(excluded.idArr).to.deep.equal([1, 2]);
expect(excluded.idArr).to.deep.equal([0, 2]);
});
it('update records a update', function () {
var updater = { name: 'Mark' };
var updater = { name: 'Updated Book Name' };
expect(session.updates).to.have.length(0);
qs.update(updater);
bookQs.update(updater);
expect(session.updates).to.have.length(1);

@@ -154,7 +125,7 @@

payload: {
idArr: qs.idArr,
idArr: bookQs.idArr,
updater: updater
},
meta: {
name: 'Person'
name: 'Book'
}

@@ -166,10 +137,10 @@ });

expect(session.updates).to.have.length(0);
qs['delete']();
expect(session.updates).to.have.length(1);
bookQs['delete']();
expect(session.updates).to.have.length.of.at.least(1);
expect(session.updates[0]).to.deep.equal({
type: _constants.DELETE,
payload: qs.idArr,
payload: bookQs.idArr,
meta: {
name: 'Person'
name: 'Book'
}

@@ -180,2 +151,11 @@ });

it('custom methods works', function () {
var _createTestModels = (0, _utils.createTestModels)();
var Book = _createTestModels.Book;
var Genre = _createTestModels.Genre;
var Cover = _createTestModels.Cover;
var Author = _createTestModels.Author;
var currentYear = 2015;
var CustomQuerySet = (function (_QuerySet) {

@@ -191,9 +171,7 @@ _inherits(CustomQuerySet, _QuerySet);

_createClass(CustomQuerySet, [{
key: 'overMiddleAge',
value: function overMiddleAge() {
var origPlain = this._plain;
var filtered = this.plain.filter(function (person) {
return person.age > 50;
key: 'unreleased',
value: function unreleased() {
return this.withRefs.filter(function (book) {
return book.releaseYear > currentYear;
});
return origPlain ? filtered : filtered.models;
}

@@ -205,31 +183,30 @@ }]);

CustomQuerySet.addSharedMethod('overMiddleAge');
CustomQuerySet.addSharedMethod('unreleased');
var PersonSub = (function (_PersonClass) {
_inherits(PersonSub, _PersonClass);
Book.querySetClass = CustomQuerySet;
function PersonSub() {
_classCallCheck(this, PersonSub);
var schema = new _Schema2['default']();
schema.register(Book, Genre, Cover, Author);
_get(Object.getPrototypeOf(PersonSub.prototype), 'constructor', this).apply(this, arguments);
}
var _createTestSessionWithData2 = (0, _utils.createTestSessionWithData)(schema);
return PersonSub;
})(PersonClass);
var sess = _createTestSessionWithData2.session;
PersonSub.modelName = 'Person';
var customQs = sess.Book.getQuerySet();
PersonSub.querySetClass = CustomQuerySet;
var aSchema = new _Schema2['default']();
aSchema.register(PersonSub);
var sess = aSchema.from(state);
var customQs = sess.Person.query;
expect(customQs).to.be.an.instanceOf(CustomQuerySet);
var overMiddleAged = customQs.overMiddleAge();
expect(overMiddleAged.count()).to.equal(1);
expect(overMiddleAged.first().toPlain()).to.deep.equal({ id: 2, name: 'Mary', age: 60 });
var unreleased = customQs.unreleased();
expect(unreleased.count()).to.equal(1);
expect(PersonSub.overMiddleAge().count()).to.equal(1);
expect(PersonSub.plain.filter({ name: 'Tommi' }).count()).to.equal(1);
expect(unreleased.first().ref).to.deep.equal({
id: 0,
name: 'Tommi Kaikkonen - an Autobiography',
author: 0,
cover: 0,
releaseYear: 2050
});
expect(sess.Book.unreleased().count()).to.equal(1);
expect(sess.Book.withRefs.filter({ name: 'Clean Code' }).count()).to.equal(1);
});
});
'use strict';
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(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; 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 { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
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; }
var _chai = require('chai');

@@ -19,50 +11,29 @@

var _Model13 = require('../Model');
var _Session = require('../Session');
var _Model14 = _interopRequireDefault(_Model13);
var _Session2 = _interopRequireDefault(_Session);
var _fields = require('../fields');
var _utils = require('./utils');
describe('Schema', function () {
it('constructor works', function () {
var schema = new _Schema2['default']();
(0, _chai.expect)(schema.selectorCreator).to.be.a('function');
});
describe('simple schema', function () {
var schema = undefined;
var Person = undefined;
var Location = undefined;
var Book = undefined;
var Author = undefined;
var Cover = undefined;
var Genre = undefined;
beforeEach(function () {
schema = new _Schema2['default']();
Person = (function (_Model) {
_inherits(PersonModel, _Model);
var _createTestModels = (0, _utils.createTestModels)();
function PersonModel() {
_classCallCheck(this, PersonModel);
Book = _createTestModels.Book;
Author = _createTestModels.Author;
Cover = _createTestModels.Cover;
Genre = _createTestModels.Genre;
_get(Object.getPrototypeOf(PersonModel.prototype), 'constructor', this).apply(this, arguments);
}
_createClass(PersonModel, null, [{
key: 'fields',
get: function get() {
return {
location: new _fields.ForeignKey('Location')
};
}
}]);
return PersonModel;
})(_Model14['default']);
Person.modelName = 'Person';
Location = (function (_Model2) {
_inherits(LocationModel, _Model2);
function LocationModel() {
_classCallCheck(this, LocationModel);
_get(Object.getPrototypeOf(LocationModel.prototype), 'constructor', this).apply(this, arguments);
}
return LocationModel;
})(_Model14['default']);
Location.modelName = 'Location';
schema = new _Schema2['default']();
});

@@ -72,5 +43,5 @@

(0, _chai.expect)(schema.registry).to.have.length(0);
schema.register(Person);
schema.register(Book);
(0, _chai.expect)(schema.registry).to.have.length(1);
schema.register(Location);
schema.register(Author);
(0, _chai.expect)(schema.registry).to.have.length(2);

@@ -81,595 +52,98 @@ });

(0, _chai.expect)(schema.registry).to.have.length(0);
schema.register(Person, Location);
schema.register(Book, Author);
(0, _chai.expect)(schema.registry).to.have.length(2);
});
it('correcly resolves model instance values on create', function () {
schema.register(Person, Location);
schema.from(schema.getDefaultState());
var tommi = Person.create({ id: 0, name: 'Tommi', friend: null });
var friend = Person.create({ id: 1, name: 'Matt', friend: tommi });
(0, _chai.expect)(friend._fields.friend).to.equal(0);
it('correctly starts session', function () {
var initialState = {};
var session = schema.from(initialState);
(0, _chai.expect)(session).to.be.instanceOf(_Session2['default']);
});
it('correctly sets field value on assignment', function () {
schema.register(Person, Location);
var session = schema.from(schema.getDefaultState());
Person.create({ id: 0, name: 'Tommi', friend: null });
var nextState = session.reduce();
var nextSession = schema.from(nextState);
var newName = 'NewName';
nextSession.Person.withId(0).name = newName;
var nextNextState = session.reduce();
var nextNextSession = schema.from(nextNextState);
(0, _chai.expect)(nextNextSession.Person.withId(0).name).to.equal(newName);
it('correctly gets models from registry', function () {
schema.register(Book);
(0, _chai.expect)(schema.get('Book')).to.equal(Book);
});
});
it('correctly works with mutations', function () {
var schema = new _Schema2['default']();
it('correctly sets model prototypes', function () {
schema.register(Book, Author, Cover, Genre);
(0, _chai.expect)(Book.isSetUp).to.not.be.ok;
var PersonModel = (function (_Model3) {
_inherits(PersonModel, _Model3);
var coverDescriptor = Object.getOwnPropertyDescriptor(Book.prototype, 'cover');
(0, _chai.expect)(coverDescriptor).to.be.undefined;
var authorDescriptor = Object.getOwnPropertyDescriptor(Book.prototype, 'author');
(0, _chai.expect)(authorDescriptor).to.be.undefined;
var genresDescriptor = Object.getOwnPropertyDescriptor(Book.prototype, 'genres');
(0, _chai.expect)(genresDescriptor).to.be.undefined;
function PersonModel() {
_classCallCheck(this, PersonModel);
schema._setupModelPrototypes();
_get(Object.getPrototypeOf(PersonModel.prototype), 'constructor', this).apply(this, arguments);
}
(0, _chai.expect)(Book.isSetUp).to.be.ok;
_createClass(PersonModel, null, [{
key: 'fields',
get: function get() {
return {
location: new _fields.ForeignKey('Location')
};
}
}]);
coverDescriptor = Object.getOwnPropertyDescriptor(Book.prototype, 'cover');
(0, _chai.expect)(coverDescriptor.get).to.be.a('function');
(0, _chai.expect)(coverDescriptor.set).to.be.a('function');
return PersonModel;
})(_Model14['default']);
authorDescriptor = Object.getOwnPropertyDescriptor(Book.prototype, 'author');
(0, _chai.expect)(authorDescriptor.get).to.be.a('function');
(0, _chai.expect)(authorDescriptor.set).to.be.a('function');
PersonModel.modelName = 'Person';
var LocationModel = (function (_Model4) {
_inherits(LocationModel, _Model4);
function LocationModel() {
_classCallCheck(this, LocationModel);
_get(Object.getPrototypeOf(LocationModel.prototype), 'constructor', this).apply(this, arguments);
}
return LocationModel;
})(_Model14['default']);
LocationModel.modelName = 'Location';
schema.register(PersonModel);
schema.register(LocationModel);
var state = schema.getDefaultState();
var _schema$withMutations = schema.withMutations(state);
var Person = _schema$withMutations.Person;
var Location = _schema$withMutations.Location;
(0, _chai.expect)(state.Person.items).to.have.length(0);
(0, _chai.expect)(state.Person.itemsById).to.deep.equal({});
Person.create({ name: 'Tommi', age: 25 });
(0, _chai.expect)(state.Person.items).to.have.length(1);
(0, _chai.expect)(state.Person.itemsById).to.deep.equal({
0: {
id: 0,
name: 'Tommi',
age: 25
}
genresDescriptor = Object.getOwnPropertyDescriptor(Book.prototype, 'genres');
(0, _chai.expect)(genresDescriptor.get).to.be.a('function');
(0, _chai.expect)(genresDescriptor.set).to.be.a('function');
});
Person.create({ name: 'Matt', age: 35 });
(0, _chai.expect)(state.Person.items).to.have.length(2);
(0, _chai.expect)(state.Person.itemsById).to.deep.equal({
0: {
id: 0,
name: 'Tommi',
age: 25
},
1: {
id: 1,
name: 'Matt',
age: 35
}
});
(0, _chai.expect)(state.Person.items).to.deep.equal([0, 1]);
it('correctly gets the default state', function () {
schema.register(Book, Author, Cover, Genre);
var defaultState = schema.getDefaultState();
Person.setOrder('name');
(0, _chai.expect)(state.Person.items).to.deep.equal([1, 0]);
Person.withId(1)['delete']();
(0, _chai.expect)(state.Person.items).to.have.length(1);
(0, _chai.expect)(state.Person.itemsById).to.deep.equal({
0: {
id: 0,
name: 'Tommi',
age: 25
}
});
Person.withId(0).update({ name: 'Michael' });
(0, _chai.expect)(state.Person.itemsById[0].name).to.equal('Michael');
});
it('correctly defines models', function () {
var schema = new _Schema2['default']();
var PersonModel = (function (_Model5) {
_inherits(PersonModel, _Model5);
function PersonModel() {
_classCallCheck(this, PersonModel);
_get(Object.getPrototypeOf(PersonModel.prototype), 'constructor', this).apply(this, arguments);
}
_createClass(PersonModel, null, [{
key: 'fields',
get: function get() {
return {
location: new _fields.ForeignKey('Location')
};
(0, _chai.expect)(defaultState).to.deep.equal({
Book: {
items: [],
itemsById: {}
},
BookGenres: {
items: [],
itemsById: {}
},
Author: {
items: [],
itemsById: {}
},
Cover: {
items: [],
itemsById: {}
},
Genre: {
items: [],
itemsById: {}
}
}]);
return PersonModel;
})(_Model14['default']);
PersonModel.modelName = 'Person';
var LocationModel = (function (_Model6) {
_inherits(LocationModel, _Model6);
function LocationModel() {
_classCallCheck(this, LocationModel);
_get(Object.getPrototypeOf(LocationModel.prototype), 'constructor', this).apply(this, arguments);
}
return LocationModel;
})(_Model14['default']);
LocationModel.modelName = 'Location';
schema.register(PersonModel);
schema.register(LocationModel);
var reducer = schema.reducer();
var initReduce = reducer(undefined, { type: 'INIT' });
(0, _chai.expect)(initReduce).to.have.property('Person');
(0, _chai.expect)(initReduce.Person).to.have.property('items');
(0, _chai.expect)(initReduce.Person.items).to.have.length(0);
(0, _chai.expect)(initReduce).to.have.property('Location');
var orm = schema.from({
Person: {
items: [0],
itemsById: {
0: {
id: 0,
name: 'Tommi',
age: 25,
location: 0
}
}
},
Location: {
items: [0],
itemsById: {
0: {
id: 0,
name: 'San Francisco',
country: 'United States'
}
}
}
});
});
(0, _chai.expect)(orm.Person).to.exist;
(0, _chai.expect)(orm.Location).to.exist;
it('correctly creates a selector', function () {
schema.register(Book, Author, Cover, Genre);
var selectorTimesRun = 0;
var selector = schema.createSelector(function () {
return selectorTimesRun++;
});
(0, _chai.expect)(selector).to.be.a('function');
var Person = orm.Person;
var Location = orm.Location;
var aPerson = Person.first();
var aLocation = Location.first();
(0, _chai.expect)(aPerson.toPlain()).to.deep.equal({ id: 0, name: 'Tommi', age: 25, location: 0 });
(0, _chai.expect)(aPerson).to.be.an.instanceOf(_Model14['default']);
var relatedLocation = aPerson.location;
(0, _chai.expect)(relatedLocation.equals(aLocation)).to.be.ok;
});
it('correctly defines ManyToMany', function () {
var schema = new _Schema2['default']();
var PersonModel = (function (_Model7) {
_inherits(PersonModel, _Model7);
function PersonModel() {
_classCallCheck(this, PersonModel);
_get(Object.getPrototypeOf(PersonModel.prototype), 'constructor', this).apply(this, arguments);
}
_createClass(PersonModel, null, [{
key: 'reducer',
value: function reducer(state, action, Person) {
Person.create({ id: 5, name: 'Mike', age: 30 });
var me = Person.get({ name: 'Tommi' });
me.update({ age: 20 });
var firstLoc = me.locations.first();
me.locations.remove(firstLoc);
var result = Person.getNextState();
return result;
}
}, {
key: 'fields',
get: function get() {
return {
locations: new _fields.ManyToMany('Location'),
currentLocation: new _fields.OneToOne('Location')
};
}
}]);
return PersonModel;
})(_Model14['default']);
PersonModel.modelName = 'Person';
var LocationModel = (function (_Model8) {
_inherits(LocationModel, _Model8);
function LocationModel() {
_classCallCheck(this, LocationModel);
_get(Object.getPrototypeOf(LocationModel.prototype), 'constructor', this).apply(this, arguments);
}
_createClass(LocationModel, [{
key: 'toString',
value: function toString() {
return this.name + ', ' + this.country;
}
}]);
return LocationModel;
})(_Model14['default']);
LocationModel.modelName = 'Location';
schema.register(PersonModel);
schema.register(LocationModel);
var orm = schema.from({
Person: {
items: [0, 1],
itemsById: {
0: {
id: 0,
name: 'Tommi',
age: 25
},
1: {
id: 1,
name: 'Ats',
age: 28
}
}
},
PersonLocations: {
items: [0, 1, 2],
itemsById: {
0: {
id: 0,
fromPersonId: 0,
toLocationId: 0
},
1: {
id: 1,
fromPersonId: 0,
toLocationId: 1
},
2: {
id: 2,
fromPersonId: 1,
toLocationId: 1
}
}
},
Location: {
items: [0, 1],
itemsById: {
0: {
id: 0,
name: 'San Francisco',
country: 'United States'
},
1: {
id: 1,
name: 'Helsinki',
country: 'Finland'
}
}
}
var state = schema.getDefaultState();
selector(state);
(0, _chai.expect)(selectorTimesRun).to.equal(1);
selector(state);
(0, _chai.expect)(selectorTimesRun).to.equal(1);
selector(schema.getDefaultState());
(0, _chai.expect)(selectorTimesRun).to.equal(1);
});
(0, _chai.expect)(orm.Person).to.exist;
(0, _chai.expect)(orm.Location).to.exist;
(0, _chai.expect)(orm.PersonLocations).to.exist;
var SF = orm.Location.get({ name: 'San Francisco' });
(0, _chai.expect)(orm.accessedModels).to.deep.equal(['Location']);
(0, _chai.expect)(SF.personSet.count()).to.equal(1);
var hki = orm.Location.get({ name: 'Helsinki' });
(0, _chai.expect)(orm.accessedModels).to.deep.equal(['Location', 'PersonLocations']);
(0, _chai.expect)(hki.personSet.count()).to.equal(2);
hki.personSet.map(function (x) {
return x;
it('correctly starts a mutating session', function () {
schema.register(Book, Author, Cover, Genre);
var initialState = schema.getDefaultState();
var session = schema.withMutations(initialState);
(0, _chai.expect)(session).to.be.an.instanceOf(_Session2['default']);
(0, _chai.expect)(session.withMutations).to.be['true'];
});
(0, _chai.expect)(orm.accessedModels).to.deep.equal(['Location', 'PersonLocations', 'Person']);
var tommi = orm.Person.first();
(0, _chai.expect)(tommi.name).to.equal('Tommi');
(0, _chai.expect)(tommi.locations.count()).to.equal(2);
var ats = orm.Person.get({ name: 'Ats' });
(0, _chai.expect)(ats.locations.count()).to.equal(1);
(0, _chai.expect)(ats.locations.first().equals(hki)).to.be.ok;
});
it('correctly handles ManyToMany relations', function () {
var BookModel = (function (_Model9) {
_inherits(BookModel, _Model9);
function BookModel() {
_classCallCheck(this, BookModel);
_get(Object.getPrototypeOf(BookModel.prototype), 'constructor', this).apply(this, arguments);
}
return BookModel;
})(_Model14['default']);
BookModel.modelName = 'Book';
BookModel.fields = {
genres: new _fields.ManyToMany('Genre')
};
var GenreModel = (function (_Model10) {
_inherits(GenreModel, _Model10);
function GenreModel() {
_classCallCheck(this, GenreModel);
_get(Object.getPrototypeOf(GenreModel.prototype), 'constructor', this).apply(this, arguments);
}
return GenreModel;
})(_Model14['default']);
GenreModel.modelName = 'Genre';
var schema = new _Schema2['default']();
schema.register(BookModel, GenreModel);
var initialState = schema.getDefaultState();
var _schema$withMutations2 = schema.withMutations(initialState);
var Book = _schema$withMutations2.Book;
var Genre = _schema$withMutations2.Genre;
var g1 = Genre.create({ name: 'Fiction' });
var g2 = Genre.create({ name: 'Non-Fiction' });
var g3 = Genre.create({ name: 'Business' });
var book = Book.create({ name: 'A Phenomenal Novel', genres: [0, 1] });
Book.create({ name: 'Not so good Novel', genres: [] });
(0, _chai.expect)(initialState.BookGenres.items).to.have.length(2);
(0, _chai.expect)(book._fields.genres).to.be.undefined;
var session = schema.from(initialState);
Book = session.Book;
Genre = session.Genre;
Book.withId(0)['delete']();
Book.withId(1).update({ genres: [1, g3] });
var nextState = session.reduce();
(0, _chai.expect)(nextState.BookGenres.items).to.have.length(2);
(0, _chai.expect)(nextState.BookGenres.items).to.deep.equal([2, 3]);
});
it('Correctly defined OneToOne', function () {
var schema = new _Schema2['default']();
var UserModel = (function (_Model11) {
_inherits(UserModel, _Model11);
function UserModel() {
_classCallCheck(this, UserModel);
_get(Object.getPrototypeOf(UserModel.prototype), 'constructor', this).apply(this, arguments);
}
_createClass(UserModel, null, [{
key: 'reducer',
value: function reducer(state, action, User) {
switch (action.type) {
case 'CREATE_USER':
User.create(action.payload.user);
break;
case 'REMOVE_USER':
User.get({ id: action.payload })['delete']();
break;
default:
return state;
}
return User.getNextState();
}
}, {
key: 'fields',
get: function get() {
return {
profile: new _fields.OneToOne('Profile'),
pair: new _fields.OneToOne('this')
};
}
}]);
return UserModel;
})(_Model14['default']);
UserModel.modelName = 'User';
var ProfileModel = (function (_Model12) {
_inherits(ProfileModel, _Model12);
function ProfileModel() {
_classCallCheck(this, ProfileModel);
_get(Object.getPrototypeOf(ProfileModel.prototype), 'constructor', this).apply(this, arguments);
}
_createClass(ProfileModel, null, [{
key: 'reducer',
value: function reducer(state, action, Profile) {
switch (action.type) {
case 'CREATE_USER':
Profile.create(action.payload.profile);
break;
case 'REMOVE_USER':
Profile.modelFilter(function (profile) {
return profile.user.getId() === action.payload;
})['delete']();
break;
default:
return state;
}
return Profile.getNextState();
}
}]);
return ProfileModel;
})(_Model14['default']);
ProfileModel.modelName = 'Profile';
schema.register(UserModel);
schema.register(ProfileModel);
var initialState = {
User: {
items: [0, 1, 2, 3],
itemsById: {
0: {
id: 0,
name: 'Tommi',
profile: 0,
pair: 1
},
1: {
id: 1,
name: 'Matt',
profile: 1,
pair: 0
},
2: {
id: 2,
name: 'Mary',
profile: 2,
pair: 3
},
3: {
id: 3,
name: 'Heinz',
profile: 3,
pair: 2
}
}
},
Profile: {
items: [0, 1, 2, 3],
itemsById: {
0: {
id: 0,
greeting: 'Hi, this is Tommi\'s profile!'
},
1: {
id: 1,
greeting: 'Hi, this is Matt\'s profile!'
},
2: {
id: 2,
greeting: 'Hi, this is Mary\'s profile!'
},
3: {
id: 3,
greeting: 'Hi, this is Heinz\'s profile!'
}
}
}
};
var session = schema.from(initialState);
var User = session.User;
var Profile = session.Profile;
(0, _chai.expect)(User.count()).to.equal(4);
(0, _chai.expect)(Profile.count()).to.equal(4);
var aUser = User.first();
var aProfile = Profile.last();
(0, _chai.expect)(aUser.user.pair).to.deep.equal(aUser);
var aUsersProfile = aUser.profile;
(0, _chai.expect)(aUsersProfile.user).to.deep.equal(aUser);
var reducer = schema.reducer();
var nextState = reducer(initialState, {
type: 'CREATE_USER',
payload: {
user: {
id: 4,
name: 'Jonathan'
},
profile: {
id: 4,
greeting: 'This is Jonathan!'
}
}
});
var _schema$from = schema.from(nextState);
var nextUser = _schema$from.User;
var nextProfile = _schema$from.Profile;
(0, _chai.expect)(nextUser.count()).to.equal(5);
(0, _chai.expect)(nextProfile.count()).to.equal(5);
(0, _chai.expect)(nextUser.last().profile).to.be.undefined;
nextUser.last()['delete']();
});
});

@@ -176,27 +176,40 @@ 'use strict';

function querySetGetterDelegatorFactory(getterName) {
return function querySetGetterDelegator() {
var qs = this.getQuerySet();
return qs[getterName];
};
}
function forEachSuperClass(subClass, func) {
var currClass = subClass;
while (currClass !== Function.prototype) {
func(currClass);
currClass = Object.getPrototypeOf(currClass);
}
}
function attachQuerySetMethods(modelClass, querySetClass) {
var querySetSharedMethods = querySetClass.sharedMethods;
querySetSharedMethods.forEach(function (methodName) {
// Check for descriptor.
var descriptor = Object.getOwnPropertyDescriptor(querySetClass.prototype, methodName);
if (typeof descriptor !== 'undefined' && typeof descriptor.get !== 'undefined') {
Object.defineProperty(modelClass, methodName, descriptor);
} else {
modelClass[methodName] = querySetDelegatorFactory(methodName);
}
});
var leftToDefine = querySetClass.sharedMethods.slice();
// `plain` and `models` are specially handled cases
// as they're static getters.
Object.defineProperties(modelClass, {
plain: {
get: function get() {
this._plain = true;
return this;
// There is no way to get a property descriptor for the whole prototype chain;
// only from an objects own properties. Therefore we traverse the whole prototype
// chain for querySet.
forEachSuperClass(querySetClass, function (cls) {
for (var i = 0; i < leftToDefine.length; i++) {
var defined = false;
var methodName = leftToDefine[i];
var descriptor = Object.getOwnPropertyDescriptor(cls.prototype, methodName);
if (typeof descriptor !== 'undefined') {
if (typeof descriptor.get !== 'undefined') {
descriptor.get = querySetGetterDelegatorFactory(methodName);
Object.defineProperty(modelClass, methodName, descriptor);
defined = true;
} else if (typeof descriptor.value === 'function') {
modelClass[methodName] = querySetDelegatorFactory(methodName);
defined = true;
}
}
},
models: {
get: function get() {
this._plain = false;
return this;
if (defined) {
leftToDefine.splice(i--, 1);
}

@@ -203,0 +216,0 @@ }

{
"name": "redux-orm",
"version": "0.1.24",
"version": "0.2.0",
"description": "Simple ORM to manage and query your state trees",

@@ -5,0 +5,0 @@ "main": "lib/index.js",

@@ -271,5 +271,7 @@ redux-orm

**Instance Attributes**:
- `ref`: returns a direct reference to the plain JavaScript object representing the Model instance in the store.
**Instance methods**:
- `toPlain`: returns a plain JavaScript object presentation of the Model.
- `set`: marks a supplied `propertyName` to be updated to `value` at `Model.getNextState`. Returns `undefined`. Is equivalent to normal assignment.

@@ -329,7 +331,8 @@ - `update`: marks a supplied object of property names and values to be merged with the Model instance at `Model.getNextState()`. Returns `undefined`.

- `toPlain()`: returns the `Model` instances in `QuerySet` as an array of plain JavaScript objects.
- `toRefArray()`: returns the objects represented by the `QuerySet` as an array of plain JavaScript objects. The objects are direct references to the store.
- `toModelArray()`: returns the objects represented by the `QuerySet` as an array of `Model` instances objects.
- `count()`: returns the number of `Model` instances in the `QuerySet`.
- `exists()`: return `true` if number of entities is more than 0, else `false`.
- `filter(filterArg)`: returns a new `QuerySet` with the entities that pass the filter. For `filterArg`, you can either pass an object that `redux-orm` tries to match to the entities, or a function that returns `true` if you want to have it in the new `QuerySet`, `false` if not.
- `exclude` returns a new `QuerySet` with the entities that do not pass the filter. Similarly to `filter`, you may pass an object for matching (all entities that match will not be in the new `QuerySet`) or a function.
- `filter(filterArg)`: returns a new `QuerySet` with the entities that pass the filter. For `filterArg`, you can either pass an object that `redux-orm` tries to match to the entities, or a function that returns `true` if you want to have it in the new `QuerySet`, `false` if not. The function receives a model instance as its sole argument.
- `exclude` returns a new `QuerySet` with the entities that do not pass the filter. Similarly to `filter`, you may pass an object for matching (all entities that match will not be in the new `QuerySet`) or a function. The function receives a model instance as its sole argument.
- `map(func)` map the entities in `QuerySet`, returning a JavaScript array.

@@ -343,8 +346,8 @@ - `all()` returns a new `QuerySet` with the same entities.

**Plain/models flagging**
**withRefs/withModels flagging**
When you want to iterate through all entities with `filter`, `exclude`, `forEach`, `map`, or get an item with `first`, `last` or `at`, you don't always need access to the full Model instance - a plain JavaScript object could do. QuerySets maintain a flag indicating whether these methods operate on plain JavaScript objects (a straight reference from the store) or a Model instances that are instantiated during the operations.
When you want to iterate through all entities with `filter`, `exclude`, `forEach`, `map`, or get an item with `first`, `last` or `at`, you don't always need access to the full Model instance - a reference to the plain JavaScript object in the database could do. QuerySets maintain a flag indicating whether these methods operate on plain JavaScript objects (a straight reference from the store) or a Model instances that are instantiated during the operations.
```javascript
const clean = Book.plain.filter(book => book.author === 'Tommi Kaikkonen')
const clean = Book.withRefs.filter(book => book.author === 'Tommi Kaikkonen')
// `book` is a plain javascript object, `clean` is a QuerySet

@@ -356,10 +359,12 @@ //

The flag persists after settings the flag. The default is to operate on Model instances. You can invert the flag by chaining `plain`. You can flip it back with `models`.
The flag persists after setting the flag. If you use `filter`, `exclude` or `orderBy`, the returned `QuerySet` will have the flag set to operate on Model instances either way. The default is to operate on Model instances. You can get a copy of the current `QuerySet` with the flag set to operate on references from the `withRefs` attribute. Likewise a `QuerySet` copy with the flag set to operate on model instances can be gotten by accessing the `withModels` attribute.
```javascript
clean.filter(book => book.release_year > 2014)
// Since the plain flag was used in `clean`, `book` is a plain instance.
// The `withRefs` flag was reverted back to using models after the `filter` operation,
// so `book` here is a model instance.
clean.models.filter(book => book.isReleasedAfterYear(2014))
// `models` inverts the flag, `book` is a Model instance.
clean.withRefs.filter(book => book.isReleasedAfterYear(2014))
// `book` is once again a model instance.
// You rarely need to use `withModels`, unless you're unsure which way the flag is.
```

@@ -402,4 +407,20 @@

## Changelog
### 0.2.0
Includes various bugfixes and improvements.
**Breaking changes**:
- Replaced `plain` and `models` instance attributes in `QuerySet` with `withRefs` and `withModels` respectively. The attributes return a new `QuerySet` instead of modifying the existing one. A `ref` alias is also added for `withRefs`, so you can do `Book.ref.at(2)`.
- After calling `filter`, `exclude` or `orderBy` method on a `QuerySet` instance, the `withRefs` flag is always flipped off so that calling the same methods on the returned `QuerySet` would use model instances in the operations. Previously the flag value remained after calling those methods.
- `.toPlain()` from `QuerySet` is renamed to `.toRefArray()` for clarity.
- Added `.toModelArray()` method to `QuerySet`.
- Removed `.objects()` method from `QuerySet`. Use `.toRefArray()` or `.toModelArray()` instead.
- Removed `.toPlain()` method from `Model`, which returned a copy of the Model instance's property values. To replace that, `ref` instance getter was added. It returns a reference to the plain JavaScript object in the database. So you can do `Book.withId(0).ref`. If you need a copy, you can do `Object.assign({}, Book.withId(0).ref)`.
## License
MIT. See `LICENSE`

@@ -182,5 +182,6 @@ import find from 'lodash/collection/find';

mapFunction = (entity) => {
const assignTo = this.withMutations ? entity : {};
const diff = objectDiff(entity, patcher);
if (diff) {
return Object.assign({}, entity, patcher);
return Object.assign(assignTo, entity, patcher);
}

@@ -187,0 +188,0 @@ return entity;

@@ -91,3 +91,3 @@ import UPDATE from './constants';

const throughQs = throughModel.filter(lookupObj);
const toIds = throughQs.plain.map(obj => obj[reverse ? fromFieldName : toFieldName]);
const toIds = throughQs.withRefs.map(obj => obj[reverse ? fromFieldName : toFieldName]);

@@ -116,3 +116,3 @@ const qsFromModel = reverse ? declaredFromModel : declaredToModel;

const attrInIdsToRemove = reverse ? fromFieldName : toFieldName;
const entitiesToDelete = throughQs.plain.filter(through => {
const entitiesToDelete = throughQs.withRefs.filter(through => {
return idsToRemove.includes(through[attrInIdsToRemove]);

@@ -119,0 +119,0 @@ });

@@ -46,3 +46,2 @@ import forOwn from 'lodash/object/forOwn';

this._fields = props;
const idAttribute = ModelClass.idAttribute;

@@ -52,3 +51,2 @@ forOwn(props, (fieldValue, fieldName) => {

this._fieldNames.push(fieldName);
// If the field has not already been defined on the

@@ -60,2 +58,3 @@ // prototype for a relation.

set: (value) => this.set(fieldName, value),
configurable: true,
});

@@ -111,13 +110,7 @@ }

static get _sessionCache() {
if (!this.hasOwnProperty('__sessionCache')) {
this.__sessionCache = {};
}
return this.__sessionCache;
static get _sessionData() {
if (!this.session) return {};
return this.session.getDataForModel(this.modelName);
}
static clearSessionCache() {
this.__sessionCache = {};
}
/**

@@ -128,13 +121,18 @@ * Gets the {@link Backend} instance linked to this {@link Model}.

static getBackend() {
if (!this._sessionCache.backend) {
if (!this._sessionData.backend) {
const BackendClass = this.getBackendClass();
const opts = this._getBackendOpts();
const opts = this._getBackendOpts();
if (this._session && this._session.withMutations) {
if (this.session && this.session.withMutations) {
opts.withMutations = true;
}
this._sessionCache.backend = new BackendClass(opts);
const backend = new BackendClass(opts);
if (!this.session) {
return backend;
}
this._sessionData.backend = backend;
}
return this._sessionCache.backend;
return this._sessionData.backend;
}

@@ -157,2 +155,11 @@

/**
* A reducer that takes the Model's state and an internal redux-orm
* action object and applies the update specified by the `action` object
* by delegating to this model's Backend instance.
*
* @param {Object} state - the Model's state
* @param {Object} action - the internal redux-orm update action to apply
* @return {Object} the state after applying the action
*/
static updateReducer(state, action) {

@@ -183,4 +190,5 @@ const backend = this.getBackend();

* @param {Session} session - the current {@link Session} instance
* @return {Object} the next state for the Model
*/
static reducer(state, action, model, session) {
static reducer(state, action, model, session) { // eslint-disable-line
return model.getNextState();

@@ -244,4 +252,3 @@ }

/**
* Connect the model class to a {@link Session}. Invalidates
* the session-specific cache.
* Connect the model class to a {@link Session}.
*

@@ -255,3 +262,2 @@ * @param {Session} session - The session to connect to.

this._session = session;
this.clearSessionCache();
}

@@ -284,11 +290,11 @@

static nextId() {
if (typeof this._sessionCache.nextId === 'undefined') {
if (typeof this._sessionData.nextId === 'undefined') {
const idArr = this.accessIds();
if (idArr.length === 0) {
this._sessionCache.nextId = 0;
this._sessionData.nextId = 0;
} else {
this._sessionCache.nextId = Math.max(...idArr) + 1;
this._sessionData.nextId = Math.max(...idArr) + 1;
}
}
return this._sessionCache.nextId;
return this._sessionData.nextId;
}

@@ -312,6 +318,6 @@

static get query() {
if (!this._sessionCache.queryset) {
this._sessionCache.queryset = this.getQuerySet();
if (!this._sessionData.queryset) {
this._sessionData.queryset = this.getQuerySet();
}
return this._sessionCache.queryset;
return this._sessionData.queryset;
}

@@ -340,7 +346,7 @@

props[idAttribute] = nextId;
this._sessionCache.nextId++;
this._sessionData.nextId++;
} else {
const id = props[idAttribute];
if (id > this.nextId()) {
this._sessionCache.nextId = id + 1;
this._sessionData.nextId = id + 1;
}

@@ -459,2 +465,12 @@ }

/**
* Returns a reference to the plain JS object in the store.
* Make sure to not mutate this.
*
* @return {Object} a reference to the plain JS object in the store
*/
get ref() {
return this.getClass().accessId(this.getId());
}
/**
* Returns a string representation of the {@link Model} instance.

@@ -477,15 +493,2 @@ * @return {string} A string representation of this {@link Model} instance.

/**
* Returns a plain JavaScript object representation
* of the {@link Model} instance.
* @return {Object} a plain JavaScript object representing the {@link Model}
*/
toPlain() {
const obj = {};
this._fieldNames.forEach((fieldName) => {
obj[fieldName] = this._fields[fieldName];
});
return obj;
}
/**
* Records a update to the {@link Model} instance for a single

@@ -502,3 +505,4 @@ * field value assignment.

/**
* Records a update to the {@link Model} instance for multiple field value assignments.
* Records an update to the {@link Model} instance for multiple field value assignments.
* If the session is with mutations, updates the instance to reflect the new values.
* @param {Object} userMergeObj - an object that will be merged with this instance.

@@ -541,2 +545,7 @@ * @return {undefined}

const session = this.getClass().session;
if (session && session.withMutations) {
this._initFields(Object.assign({}, this._fields, mergeObj));
}
this.getClass().addUpdate({

@@ -578,3 +587,3 @@ type: UPDATE,

// this instance.
if (this[key] !== null ) {
if (this[key] !== null) {
this[key][field.relatedName] = null;

@@ -581,0 +590,0 @@ }

@@ -36,6 +36,6 @@ import reject from 'lodash/collection/reject';

// Results are plain objects by default.
if (opts && opts.hasOwnProperty('plain')) {
this._plain = opts.plain;
if (opts && opts.hasOwnProperty('withRefs')) {
this._withRefs = opts.withRefs;
} else {
this._plain = false;
this._withRefs = false;
}

@@ -48,15 +48,38 @@ }

_new(ids) {
const plain = this._plain;
const opts = Object.assign({}, this._opts, {plain});
_new(ids, userOpts) {
const opts = Object.assign({}, this._opts, userOpts);
return new this.constructor(this.modelClass, ids, opts);
}
get plain() {
this._plain = true;
/**
* Returns a new QuerySet representing the same entities
* with the `withRefs` flag on.
*
* @return {QuerySet}
*/
get withRefs() {
if (!this._withRefs) {
return this._new(this.idArr, {withRefs: true});
}
return this;
}
get models() {
this._plain = false;
/**
* Alias for withRefs
* @return {QuerySet}
*/
get ref() {
return this.withRefs;
}
/**
* Returns a new QuerySet representing the same entities
* with the `withRefs` flag off.
*
* @return {QuerySet}
*/
get withModels() {
if (this._withRefs) {
return this._new(this.idArr, {withRefs: false});
}
return this;

@@ -73,5 +96,8 @@ }

* Returns an array of the plain objects represented by the QuerySet.
* @return {Object[]}
* The plain objects are direct references to the store.
*
* @return {Object[]} references to the plain JS objects represented by
* the QuerySet
*/
toPlain() {
toRefArray() {
return this.idArr.map(id => {

@@ -83,2 +109,12 @@ return this.modelClass.accessId(id);

/**
* Returns an array of the Model instances represented by the QuerySet.
* @return {Model[]} model instances represented by the QuerySet
*/
toModelArray() {
return this.idArr.map((_, idx) => {
return this.at(idx);
});
}
/**
* Returns the number of model instances represented by the QuerySet.

@@ -100,11 +136,14 @@ * @return {number} length of the QuerySet

/**
* Returns the {@link Model} instance at index `index` in the QuerySet.
* Returns the {@link Model} instance at index `index` in the QuerySet if
* `withRefs` flag is set to `false`, or a reference to the plain JavaScript
* object in the model state if `true`.
* @param {number} index - index of the model instance to get
* @return {Model} an {@link Model} instance at index `index` in the QuerySet
* @return {Model|Object} a {@link Model} instance or a plain JavaScript
* object at index `index` in the QuerySet
*/
at(index) {
if (this._plain) {
if (this._withRefs) {
return this.modelClass.accessId(this.idArr[index]);
}
return this.modelClass.get({[this.modelClass.idAttribute]: this.idArr[index]});
return this.modelClass.withId(this.idArr[index]);
}

@@ -137,13 +176,2 @@

/**
* Returns all objects in this QuerySet. If the plain flag
* is on (default), this will be a list of ordinary JavaScript objects.
* If it is off, these will be Model instances.
*
* @return {Array} An array of either JavaScript objects or Model instances.
*/
objects() {
return this.idArr.map((_, idx) => this.at(idx));
}
/**
* Returns a new {@link QuerySet} with objects that match properties in `lookupObj`.

@@ -169,4 +197,4 @@ *

_filterOrExclude(lookupObj, exclude) {
const startPlainFlag = this._plain;
const func = exclude ? reject : filter;
let operationWithRefs = true;
let entities;

@@ -177,11 +205,15 @@ if (typeof lookupObj === 'function') {

// is flagged.
entities = this.objects();
if (this._withRefs) {
entities = this.toRefArray();
} else {
entities = this.toModelArray();
operationWithRefs = false;
}
} else {
// Lodash filtering doesn't work with
// Model instances.
entities = this.plain.objects();
entities = this.toRefArray();
}
const filteredEntities = func(entities, lookupObj);
const getIdFunc = this._plain
const getIdFunc = operationWithRefs
? (obj) => obj[this.modelClass.idAttribute]

@@ -192,5 +224,3 @@ : (obj) => obj.getId();

// Return flag to original value.
this._plain = startPlainFlag;
return this._new(newIdArr);
return this._new(newIdArr, {withRefs: false});
}

@@ -208,3 +238,7 @@

forEach(func) {
this.objects().forEach(func);
const arr = this._withRefs
? this.toRefArray()
: this.toModelArray();
arr.forEach(func);
}

@@ -215,3 +249,5 @@

* @param {Function} func - the mapping function that takes one argument, a
* {@link Model} instance.
* {@link Model} instance or a reference to the plain
* JavaScript object in the store, depending on the
* QuerySet's `withRefs` flag.
* @return {Array} the mapped array

@@ -231,5 +267,27 @@ */

*/
orderBy(args) {
const entities = sortByOrder.apply([this.objects()].concat(args));
return this._new(entities.map(entity => entity[this.modelClass.idAttribute]));
orderBy(iteratees, orders) {
const entities = this.toRefArray();
let iterateeArgs = iteratees;
// Lodash only works on plain javascript objects.
// If the argument is a function, and the `withRefs`
// flag is false, the argument function is wrapped
// to get the model instance and pass that as the argument
// to the user-supplied function.
if (!this._withRefs) {
iterateeArgs = iteratees.map(arg => {
if (typeof arg === 'function') {
return entity => {
const id = entity[this.modelClass.idAttribute];
const instance = this.modelClass.withId(id);
return arg(instance);
};
}
return arg;
});
}
const sortedEntities = sortByOrder.call(null, entities, iterateeArgs, orders);
return this._new(
sortedEntities.map(entity => entity[this.modelClass.idAttribute]),
{withRefs: false});
}

@@ -265,5 +323,3 @@

const originalFlag = this._plain;
this.models.forEach(model => model._onDelete());
this._plain = originalFlag;
this.withModels.forEach(model => model._onDelete());
}

@@ -273,3 +329,2 @@ };

QuerySet.sharedMethods = [
'toPlain',
'count',

@@ -288,4 +343,7 @@ 'at',

'delete',
'ref',
'withRefs',
'withModels',
];
export default QuerySet;

@@ -75,12 +75,2 @@ import {createSelectorCreator} from 'reselect';

/**
* Sets a reducer function to the model with `modelName`.
* @param {string} modelName - The name of the model you want to set a reducer to
* @param {Function} reducer - The reducer function.
*/
setReducer(modelName, reducer) {
const model = this.get(modelName);
model.reducer = reducer;
}
/**
* Registers a model class to the schema.

@@ -150,4 +140,4 @@ *

getModelClasses() {
this.setupModelPrototypes();
_getModelClasses() {
this._setupModelPrototypes();
return this.registry.concat(this.implicitThroughModels);

@@ -161,3 +151,3 @@ }

setupModelPrototypes() {
_setupModelPrototypes() {
this.registry.forEach(model => {

@@ -167,72 +157,75 @@ if (!model.isSetUp) {

forOwn(fields, (fieldInstance, fieldName) => {
const toModelName = fieldInstance.toModelName;
const toModel = toModelName === 'this' ? model : this.get(toModelName);
const descriptor = Object.getOwnPropertyDescriptor(model.prototype, fieldName);
if (typeof descriptor === 'undefined') {
const toModelName = fieldInstance.toModelName;
const toModel = toModelName === 'this' ? model : this.get(toModelName);
if (fieldInstance instanceof ForeignKey) {
// Forwards.
Object.defineProperty(
model.prototype,
fieldName,
forwardManyToOneDescriptor(fieldName, toModel)
);
model.definedProperties[fieldName] = true;
if (fieldInstance instanceof ForeignKey) {
// Forwards.
Object.defineProperty(
model.prototype,
fieldName,
forwardManyToOneDescriptor(fieldName, toModel)
);
model.definedProperties[fieldName] = true;
// Backwards.
const backwardsFieldName = fieldInstance.relatedName
? fieldInstance.relatedName
: reverseFieldName(model.modelName);
// Backwards.
const backwardsFieldName = fieldInstance.relatedName
? fieldInstance.relatedName
: reverseFieldName(model.modelName);
Object.defineProperty(
toModel.prototype,
backwardsFieldName,
backwardManyToOneDescriptor(fieldName, model)
);
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new ForeignKey(model.modelName, fieldName);
} else if (fieldInstance instanceof ManyToMany) {
// Forwards.
const throughModelName = m2mName(model.modelName, fieldName);
const throughModel = this.get(throughModelName);
Object.defineProperty(
toModel.prototype,
backwardsFieldName,
backwardManyToOneDescriptor(fieldName, model)
);
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new ForeignKey(model.modelName, fieldName);
} else if (fieldInstance instanceof ManyToMany) {
// Forwards.
const throughModelName = m2mName(model.modelName, fieldName);
const throughModel = this.get(throughModelName);
Object.defineProperty(
model.prototype,
fieldName,
manyToManyDescriptor(model, toModel, throughModel, false)
);
model.definedProperties[fieldName] = true;
model.virtualFields[fieldName] = new ManyToMany(toModel.modelName, fieldName);
Object.defineProperty(
model.prototype,
fieldName,
manyToManyDescriptor(model, toModel, throughModel, false)
);
model.definedProperties[fieldName] = true;
model.virtualFields[fieldName] = new ManyToMany(toModel.modelName, fieldName);
// Backwards.
const backwardsFieldName = fieldInstance.relatedName
? fieldInstance.relatedName
: reverseFieldName(model.modelName);
// Backwards.
const backwardsFieldName = fieldInstance.relatedName
? fieldInstance.relatedName
: reverseFieldName(model.modelName);
Object.defineProperty(
toModel.prototype,
backwardsFieldName,
manyToManyDescriptor(model, toModel, throughModel, true)
);
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new ManyToMany(model.modelName, fieldName);
} else if (fieldInstance instanceof OneToOne) {
// Forwards.
Object.defineProperty(
model.prototype,
fieldName,
forwardOneToOneDescriptor(fieldName, toModel)
);
model.definedProperties[fieldName] = true;
Object.defineProperty(
toModel.prototype,
backwardsFieldName,
manyToManyDescriptor(model, toModel, throughModel, true)
);
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new ManyToMany(model.modelName, fieldName);
} else if (fieldInstance instanceof OneToOne) {
// Forwards.
Object.defineProperty(
model.prototype,
fieldName,
forwardOneToOneDescriptor(fieldName, toModel)
);
model.definedProperties[fieldName] = true;
// Backwards.
const backwardsFieldName = fieldInstance.relatedName
? fieldInstance.relatedName
: model.modelName.toLowerCase();
// Backwards.
const backwardsFieldName = fieldInstance.relatedName
? fieldInstance.relatedName
: model.modelName.toLowerCase();
Object.defineProperty(
toModel.prototype,
backwardsFieldName,
backwardOneToOneDescriptor(fieldName, model)
);
model.definedProperties[backwardsFieldName] = true;
model.virtualFields[backwardsFieldName] = new OneToOne(model.modelName, fieldName);
Object.defineProperty(
toModel.prototype,
backwardsFieldName,
backwardOneToOneDescriptor(fieldName, model)
);
toModel.definedProperties[backwardsFieldName] = true;
toModel.virtualFields[backwardsFieldName] = new OneToOne(model.modelName, fieldName);
}
}

@@ -264,4 +257,8 @@ });

/**
* Returns the default state
* @return {Object} the default state
*/
getDefaultState() {
const models = this.getModelClasses();
const models = this._getModelClasses();
const state = {};

@@ -274,6 +271,2 @@ models.forEach(modelClass => {

fromEmpty(action) {
return new Session(this.getModelClasses(), this.getDefaultState(), action);
}
/**

@@ -283,11 +276,11 @@ * Begins a database {@link Session}.

* @param {Object} state - the state the database manages
* @param {Object} action - the dispatched action object
* @param {Object} [action] - the dispatched action object
* @return {Session} a new session instance
*/
from(state, action) {
return new Session(this.getModelClasses(), state, action);
return new Session(this._getModelClasses(), state, action);
}
withMutations(state) {
return new Session(this.getModelClasses(), state, undefined, true);
return new Session(this._getModelClasses(), state, undefined, true);
}

@@ -294,0 +287,0 @@

@@ -26,2 +26,3 @@ import partition from 'lodash/collection/partition';

this._accessedModels = {};
this.modelData = {};

@@ -38,15 +39,25 @@ models.forEach(modelClass => {

markAccessed(model) {
this._accessedModels[model.modelName] = true;
this.getDataForModel(model.modelName).accessed = true;
}
get accessedModels() {
return Object.keys(this._accessedModels);
return this.models.filter(model => {
return !!this.getDataForModel(model.modelName).accessed;
}).map(model => model.modelName);
}
getDataForModel(modelName) {
if (!this.modelData[modelName]) {
this.modelData[modelName] = {};
}
return this.modelData[modelName];
}
/**
* Records an update to the session.
* @param {Object} update - the update object. Must have keys
* `type`, `payload` and `meta`. `meta`
* must also include a `name` attribute
* that contains the model name.
* `type`, `payload` and `meta`. `meta`
* must also include a `name` attribute
* that contains the model name.
*/

@@ -57,6 +68,6 @@ addUpdate(update) {

const modelState = this.getState(modelName);
const state = typeof modelState === 'undefined'
? this[modelName].getDefaultState()
: modelState;
const state = modelState || this[modelName].getDefaultState();
// The backend used in the updateReducer
// will mutate the model state.
this[modelName].updateReducer(state, update);

@@ -95,5 +106,9 @@ } else {

* the next state.
* If the session uses mutations, just returns the state.
*
* @return {Object} The next state
*/
reduce() {
if (this.withMutations) return this.state;
const nextState = {};

@@ -100,0 +115,0 @@ const currentAction = this.action;

@@ -47,5 +47,2 @@ import chai from 'chai';

expect(instance).to.be.an.instanceOf(BackendMockClass);
// Make sure the previous instance is cached
expect(Model.getBackend()).to.equal(instance);
});

@@ -112,11 +109,14 @@ });

let instance;
const sessionMock = {};
const stateMock = {};
const actionMock = {};
let sessionMock;
let stateMock;
let actionMock;
sessionMock.action = actionMock;
beforeEach(() => {
sessionMock = {};
stateMock = {};
actionMock = {};
sessionMock.getState = () => stateMock;
sessionMock.action = actionMock;
beforeEach(() => {
sessionMock.getState = () => stateMock;
// Get a fresh copy

@@ -127,2 +127,3 @@ // of Model, so our manipulations

Model.modelName = 'Model';
Model.markAccessed = () => undefined;

@@ -176,7 +177,11 @@ instance = new Model({id: 0, name: 'Tommi'});

it('toPlain works correctly', () => {
expect(instance.toPlain()).to.deep.equal({
id: 0,
name: 'Tommi',
});
it('ref works correctly', () => {
const backendMock = {};
Model.getBackend = () => backendMock;
const dbObj = {};
backendMock.accessId = () => dbObj;
Model._session = sessionMock;
sessionMock.backend = backendMock;
expect(instance.ref).to.equal(dbObj);
});

@@ -183,0 +188,0 @@

@@ -14,98 +14,77 @@ import chai from 'chai';

} from '../constants';
import {
createTestModels,
createTestSchema,
createTestSessionWithData,
} from './utils';
describe('QuerySet', () => {
let modelClassMock;
let PersonClass;
const state = {
Person: {
items: [0, 1, 2],
itemsById: {
0: {
id: 0,
name: 'Tommi',
age: 25,
},
1: {
id: 1,
name: 'John',
age: 50,
},
2: {
id: 2,
name: 'Mary',
age: 60,
},
},
},
};
let schema;
describe('QuerySet tests', () => {
let session;
let qs;
let bookQs;
let genreQs;
beforeEach(() => {
schema = new Schema();
// Start off with a fresh Model class for each
// test.
PersonClass = class Person extends Model {};
PersonClass.modelName = 'Person';
schema.register(PersonClass);
session = schema.from(state);
qs = session.Person.query;
({session} = createTestSessionWithData());
bookQs = session.Book.getQuerySet();
genreQs = session.Genre.getQuerySet();
});
it('count works correctly', () => {
expect(qs.count()).to.equal(3);
const emptyQs = new QuerySet(session.Person, []);
expect(emptyQs.count()).to.equal(0);
expect(bookQs.count()).to.equal(3);
expect(genreQs.count()).to.equal(4);
});
it('exists works correctly', () => {
expect(qs.exists()).to.equal(true);
expect(bookQs.exists()).to.be.true;
const empty = new QuerySet(session.Person, []);
expect(empty.exists()).to.equal(false);
const emptyQs = new QuerySet(session.Book, []);
expect(emptyQs.exists()).to.be.false;
});
it('at works correctly', () => {
expect(qs.plain.at(0)).to.equal(state.Person.itemsById[0]);
expect(qs.plain.at(2)).to.equal(state.Person.itemsById[2]);
expect(qs.at(0)).to.deep.equal({id: 0, name: 'Tommi', age: 25});
expect(bookQs.at(0)).to.be.an.instanceOf(Model);
expect(bookQs.ref.at(0)).to.equal(session.Book.state.itemsById[0]);
});
it('first works correctly', () => {
expect(qs.first()).to.deep.equal(qs.at(0));
expect(qs.plain.first()).to.deep.equal(qs.at(0));
expect(bookQs.first()).to.deep.equal(bookQs.at(0));
});
it('last works correctly', () => {
expect(qs.last()).to.deep.equal(qs.at(2));
expect(qs.plain.last()).to.equal(state.Person.itemsById[2]);
const lastIndex = bookQs.count() - 1;
expect(bookQs.last()).to.deep.equal(bookQs.at(lastIndex));
});
it('all works correctly', () => {
const all = qs.all();
const all = bookQs.all();
expect(all).not.to.equal(qs);
expect(all.idArr).to.deep.equal(qs.idArr);
expect(all).not.to.equal(bookQs);
expect(all.idArr).to.deep.equal(bookQs.idArr);
});
it('filter works correctly with object argument', () => {
const filtered = qs.plain.filter({name: 'Tommi'});
const filtered = bookQs.withRefs.filter({name: 'Clean Code'});
expect(filtered.count()).to.equal(1);
expect(filtered.first()).to.equal(state.Person.itemsById[0]);
expect(filtered.ref.first()).to.equal(session.Book.state.itemsById[1]);
});
it('orderBy works correctly with prop argument', () => {
const ordered = bookQs.orderBy(['releaseYear']);
expect(ordered.idArr).to.deep.equal([1, 2, 0]);
});
it('orderBy works correctly with function argument', () => {
const ordered = bookQs.orderBy([(book) => book.releaseYear]);
expect(ordered.idArr).to.deep.equal([1, 2, 0]);
});
it('exclude works correctly with object argument', () => {
const excluded = qs.exclude({name: 'Tommi'});
const excluded = bookQs.exclude({name: 'Clean Code'});
expect(excluded.count()).to.equal(2);
expect(excluded.idArr).to.deep.equal([1, 2]);
expect(excluded.idArr).to.deep.equal([0, 2]);
});
it('update records a update', () => {
const updater = {name: 'Mark'};
const updater = {name: 'Updated Book Name'};
expect(session.updates).to.have.length(0);
qs.update(updater);
bookQs.update(updater);
expect(session.updates).to.have.length(1);

@@ -116,7 +95,7 @@

payload: {
idArr: qs.idArr,
idArr: bookQs.idArr,
updater,
},
meta: {
name: 'Person',
name: 'Book',
},

@@ -128,10 +107,10 @@ });

expect(session.updates).to.have.length(0);
qs.delete();
expect(session.updates).to.have.length(1);
bookQs.delete();
expect(session.updates).to.have.length.of.at.least(1);
expect(session.updates[0]).to.deep.equal({
type: DELETE,
payload: qs.idArr,
payload: bookQs.idArr,
meta: {
name: 'Person',
name: 'Book',
},

@@ -142,29 +121,40 @@ });

it('custom methods works', () => {
const {
Book,
Genre,
Cover,
Author,
} = createTestModels();
const currentYear = 2015;
class CustomQuerySet extends QuerySet {
overMiddleAge() {
const origPlain = this._plain;
const filtered = this.plain.filter(person => person.age > 50);
return origPlain ? filtered : filtered.models;
unreleased() {
return this.withRefs.filter(book => book.releaseYear > currentYear);
}
}
CustomQuerySet.addSharedMethod('overMiddleAge');
CustomQuerySet.addSharedMethod('unreleased');
class PersonSub extends PersonClass {}
Book.querySetClass = CustomQuerySet;
PersonSub.modelName = 'Person';
const schema = new Schema();
schema.register(Book, Genre, Cover, Author);
const {session: sess} = createTestSessionWithData(schema);
PersonSub.querySetClass = CustomQuerySet;
const aSchema = new Schema();
aSchema.register(PersonSub);
const sess = aSchema.from(state);
const customQs = sess.Person.query;
const customQs = sess.Book.getQuerySet();
const overMiddleAged = customQs.overMiddleAge();
expect(overMiddleAged.count()).to.equal(1);
expect(overMiddleAged.first().toPlain()).to.deep.equal({id: 2, name: 'Mary', age: 60});
expect(customQs).to.be.an.instanceOf(CustomQuerySet);
expect(PersonSub.overMiddleAge().count()).to.equal(1);
expect(PersonSub.plain.filter({name: 'Tommi'}).count()).to.equal(1);
const unreleased = customQs.unreleased();
expect(unreleased.count()).to.equal(1);
expect(unreleased.first().ref).to.deep.equal({
id: 0,
name: 'Tommi Kaikkonen - an Autobiography',
author: 0,
cover: 0,
releaseYear: 2050,
});
expect(sess.Book.unreleased().count()).to.equal(1);
expect(sess.Book.withRefs.filter({name: 'Clean Code'}).count()).to.equal(1);
});
});
import {expect} from 'chai';
import Schema from '../Schema';
import Model from '../Model';
import {ForeignKey, ManyToMany, OneToOne} from '../fields';
import Session from '../Session';
import {createTestModels} from './utils';
describe('Schema', () => {
it('constructor works', () => {
const schema = new Schema();
expect(schema.selectorCreator).to.be.a('function');
});
describe('simple schema', () => {
let schema;
let Person;
let Location;
let Book;
let Author;
let Cover;
let Genre;
beforeEach(() => {
({
Book,
Author,
Cover,
Genre,
} = createTestModels());
schema = new Schema();
Person = class PersonModel extends Model {
static get fields() {
return {
location: new ForeignKey('Location'),
};
}
};
Person.modelName = 'Person';
Location = class LocationModel extends Model {};
Location.modelName = 'Location';
});

@@ -29,5 +31,5 @@

expect(schema.registry).to.have.length(0);
schema.register(Person);
schema.register(Book);
expect(schema.registry).to.have.length(1);
schema.register(Location);
schema.register(Author);
expect(schema.registry).to.have.length(2);

@@ -38,450 +40,115 @@ });

expect(schema.registry).to.have.length(0);
schema.register(Person, Location);
schema.register(Book, Author);
expect(schema.registry).to.have.length(2);
});
it('correcly resolves model instance values on create', () => {
schema.register(Person, Location);
schema.from(schema.getDefaultState());
const tommi = Person.create({id: 0, name: 'Tommi', friend: null});
const friend = Person.create({id: 1, name: 'Matt', friend: tommi});
expect(friend._fields.friend).to.equal(0);
it('correctly starts session', () => {
const initialState = {};
const session = schema.from(initialState);
expect(session).to.be.instanceOf(Session);
});
it('correctly sets field value on assignment', () => {
schema.register(Person, Location);
const session = schema.from(schema.getDefaultState());
Person.create({id: 0, name: 'Tommi', friend: null});
const nextState = session.reduce();
const nextSession = schema.from(nextState);
const newName = 'NewName';
nextSession.Person.withId(0).name = newName;
const nextNextState = session.reduce();
const nextNextSession = schema.from(nextNextState);
expect(nextNextSession.Person.withId(0).name).to.equal(newName);
it('correctly gets models from registry', () => {
schema.register(Book);
expect(schema.get('Book')).to.equal(Book);
});
});
it('correctly works with mutations', () => {
const schema = new Schema();
it('correctly sets model prototypes', () => {
schema.register(Book, Author, Cover, Genre);
expect(Book.isSetUp).to.not.be.ok;
class PersonModel extends Model {
static get fields() {
return {
location: new ForeignKey('Location'),
};
}
}
PersonModel.modelName = 'Person';
let coverDescriptor = Object.getOwnPropertyDescriptor(
Book.prototype,
'cover'
);
expect(coverDescriptor).to.be.undefined;
let authorDescriptor = Object.getOwnPropertyDescriptor(
Book.prototype,
'author'
);
expect(authorDescriptor).to.be.undefined;
let genresDescriptor = Object.getOwnPropertyDescriptor(
Book.prototype,
'genres'
);
expect(genresDescriptor).to.be.undefined;
class LocationModel extends Model {}
LocationModel.modelName = 'Location';
schema._setupModelPrototypes();
schema.register(PersonModel);
schema.register(LocationModel);
expect(Book.isSetUp).to.be.ok;
const state = schema.getDefaultState();
const {Person, Location} = schema.withMutations(state);
expect(state.Person.items).to.have.length(0);
expect(state.Person.itemsById).to.deep.equal({});
Person.create({name: 'Tommi', age: 25});
expect(state.Person.items).to.have.length(1);
expect(state.Person.itemsById).to.deep.equal({
0: {
id: 0,
name: 'Tommi',
age: 25,
},
});
Person.create({name: 'Matt', age: 35});
expect(state.Person.items).to.have.length(2);
expect(state.Person.itemsById).to.deep.equal({
0: {
id: 0,
name: 'Tommi',
age: 25,
},
1: {
id: 1,
name: 'Matt',
age: 35,
},
});
coverDescriptor = Object.getOwnPropertyDescriptor(
Book.prototype,
'cover'
);
expect(coverDescriptor.get).to.be.a('function');
expect(coverDescriptor.set).to.be.a('function');
expect(state.Person.items).to.deep.equal([0, 1]);
authorDescriptor = Object.getOwnPropertyDescriptor(
Book.prototype,
'author'
);
expect(authorDescriptor.get).to.be.a('function');
expect(authorDescriptor.set).to.be.a('function');
Person.setOrder('name');
expect(state.Person.items).to.deep.equal([1, 0]);
Person.withId(1).delete();
expect(state.Person.items).to.have.length(1);
expect(state.Person.itemsById).to.deep.equal({
0: {
id: 0,
name: 'Tommi',
age: 25,
},
genresDescriptor = Object.getOwnPropertyDescriptor(
Book.prototype,
'genres'
);
expect(genresDescriptor.get).to.be.a('function');
expect(genresDescriptor.set).to.be.a('function');
});
Person.withId(0).update({name: 'Michael'});
expect(state.Person.itemsById[0].name).to.equal('Michael');
});
it('correctly gets the default state', () => {
schema.register(Book, Author, Cover, Genre);
const defaultState = schema.getDefaultState();
it('correctly defines models', () => {
const schema = new Schema();
class PersonModel extends Model {
static get fields() {
return {
location: new ForeignKey('Location'),
};
}
}
PersonModel.modelName = 'Person';
class LocationModel extends Model {}
LocationModel.modelName = 'Location';
schema.register(PersonModel);
schema.register(LocationModel);
const reducer = schema.reducer();
const initReduce = reducer(undefined, {type: 'INIT'});
expect(initReduce).to.have.property('Person');
expect(initReduce.Person).to.have.property('items');
expect(initReduce.Person.items).to.have.length(0);
expect(initReduce).to.have.property('Location');
const orm = schema.from({
Person: {
items: [0],
itemsById: {
0: {
id: 0,
name: 'Tommi',
age: 25,
location: 0,
},
expect(defaultState).to.deep.equal({
Book: {
items: [],
itemsById: {},
},
},
Location: {
items: [0],
itemsById: {
0: {
id: 0,
name: 'San Francisco',
country: 'United States',
},
BookGenres: {
items: [],
itemsById: {},
},
},
});
expect(orm.Person).to.exist;
expect(orm.Location).to.exist;
const {Person, Location} = orm;
const aPerson = Person.first();
const aLocation = Location.first();
expect(aPerson.toPlain()).to.deep.equal({id: 0, name: 'Tommi', age: 25, location: 0});
expect(aPerson).to.be.an.instanceOf(Model);
const relatedLocation = aPerson.location;
expect(relatedLocation.equals(aLocation)).to.be.ok;
});
it('correctly defines ManyToMany', () => {
const schema = new Schema();
class PersonModel extends Model {
static get fields() {
return {
locations: new ManyToMany('Location'),
currentLocation: new OneToOne('Location'),
};
}
static reducer(state, action, Person) {
Person.create({id: 5, name: 'Mike', age: 30});
const me = Person.get({name: 'Tommi'});
me.update({age: 20});
const firstLoc = me.locations.first();
me.locations.remove(firstLoc);
const result = Person.getNextState();
return result;
}
}
PersonModel.modelName = 'Person';
class LocationModel extends Model {
toString() {
return `${this.name}, ${this.country}`;
}
}
LocationModel.modelName = 'Location';
schema.register(PersonModel);
schema.register(LocationModel);
const orm = schema.from({
Person: {
items: [0, 1],
itemsById: {
0: {
id: 0,
name: 'Tommi',
age: 25,
},
1: {
id: 1,
name: 'Ats',
age: 28,
},
Author: {
items: [],
itemsById: {},
},
},
PersonLocations: {
items: [0, 1, 2],
itemsById: {
0: {
id: 0,
fromPersonId: 0,
toLocationId: 0,
},
1: {
id: 1,
fromPersonId: 0,
toLocationId: 1,
},
2: {
id: 2,
fromPersonId: 1,
toLocationId: 1,
},
Cover: {
items: [],
itemsById: {},
},
},
Location: {
items: [0, 1],
itemsById: {
0: {
id: 0,
name: 'San Francisco',
country: 'United States',
},
1: {
id: 1,
name: 'Helsinki',
country: 'Finland',
},
Genre: {
items: [],
itemsById: {},
},
},
});
});
expect(orm.Person).to.exist;
expect(orm.Location).to.exist;
expect(orm.PersonLocations).to.exist;
it('correctly creates a selector', () => {
schema.register(Book, Author, Cover, Genre);
let selectorTimesRun = 0;
const selector = schema.createSelector(() => selectorTimesRun++);
expect(selector).to.be.a('function');
const SF = orm.Location.get({name: 'San Francisco'});
expect(orm.accessedModels).to.deep.equal(['Location']);
expect(SF.personSet.count()).to.equal(1);
const state = schema.getDefaultState();
selector(state);
expect(selectorTimesRun).to.equal(1);
selector(state);
expect(selectorTimesRun).to.equal(1);
selector(schema.getDefaultState());
expect(selectorTimesRun).to.equal(1);
});
const hki = orm.Location.get({name: 'Helsinki'});
expect(orm.accessedModels).to.deep.equal(['Location', 'PersonLocations']);
expect(hki.personSet.count()).to.equal(2);
hki.personSet.map(x => x);
expect(orm.accessedModels).to.deep.equal(['Location', 'PersonLocations', 'Person']);
const tommi = orm.Person.first();
expect(tommi.name).to.equal('Tommi');
expect(tommi.locations.count()).to.equal(2);
const ats = orm.Person.get({name: 'Ats'});
expect(ats.locations.count()).to.equal(1);
expect(ats.locations.first().equals(hki)).to.be.ok;
it('correctly starts a mutating session', () => {
schema.register(Book, Author, Cover, Genre);
const initialState = schema.getDefaultState();
const session = schema.withMutations(initialState);
expect(session).to.be.an.instanceOf(Session);
expect(session.withMutations).to.be.true;
});
});
it('correctly handles ManyToMany relations', () => {
class BookModel extends Model {}
BookModel.modelName = 'Book';
BookModel.fields = {
genres: new ManyToMany('Genre'),
};
class GenreModel extends Model {}
GenreModel.modelName = 'Genre';
const schema = new Schema();
schema.register(BookModel, GenreModel);
const initialState = schema.getDefaultState();
let {Book, Genre} = schema.withMutations(initialState);
const g1 = Genre.create({name: 'Fiction'});
const g2 = Genre.create({name: 'Non-Fiction'});
const g3 = Genre.create({name: 'Business'});
const book = Book.create({name: 'A Phenomenal Novel', genres: [0, 1]});
Book.create({name: 'Not so good Novel', genres: []});
expect(initialState.BookGenres.items).to.have.length(2);
expect(book._fields.genres).to.be.undefined;
const session = schema.from(initialState);
Book = session.Book;
Genre = session.Genre;
Book.withId(0).delete();
Book.withId(1).update({genres: [1, g3]});
const nextState = session.reduce();
expect(nextState.BookGenres.items).to.have.length(2);
expect(nextState.BookGenres.items).to.deep.equal([2, 3]);
});
it('Correctly defined OneToOne', () => {
const schema = new Schema();
class UserModel extends Model {
static get fields() {
return {
profile: new OneToOne('Profile'),
pair: new OneToOne('this'),
};
}
static reducer(state, action, User) {
switch (action.type) {
case 'CREATE_USER':
User.create(action.payload.user);
break;
case 'REMOVE_USER':
User.get({id: action.payload}).delete();
break;
default:
return state;
}
return User.getNextState();
}
}
UserModel.modelName = 'User';
class ProfileModel extends Model {
static reducer(state, action, Profile) {
switch (action.type) {
case 'CREATE_USER':
Profile.create(action.payload.profile);
break;
case 'REMOVE_USER':
Profile.modelFilter(profile => profile.user.getId() === action.payload)
.delete();
break;
default:
return state;
}
return Profile.getNextState();
}
}
ProfileModel.modelName = 'Profile';
schema.register(UserModel);
schema.register(ProfileModel);
const initialState = {
User: {
items: [0, 1, 2, 3],
itemsById: {
0: {
id: 0,
name: 'Tommi',
profile: 0,
pair: 1,
},
1: {
id: 1,
name: 'Matt',
profile: 1,
pair: 0,
},
2: {
id: 2,
name: 'Mary',
profile: 2,
pair: 3,
},
3: {
id: 3,
name: 'Heinz',
profile: 3,
pair: 2,
},
},
},
Profile: {
items: [0, 1, 2, 3],
itemsById: {
0: {
id: 0,
greeting: 'Hi, this is Tommi\'s profile!',
},
1: {
id: 1,
greeting: 'Hi, this is Matt\'s profile!',
},
2: {
id: 2,
greeting: 'Hi, this is Mary\'s profile!',
},
3: {
id: 3,
greeting: 'Hi, this is Heinz\'s profile!',
},
},
},
};
const session = schema.from(initialState);
const {User, Profile} = session;
expect(User.count()).to.equal(4);
expect(Profile.count()).to.equal(4);
const aUser = User.first();
const aProfile = Profile.last();
expect(aUser.user.pair).to.deep.equal(aUser);
const aUsersProfile = aUser.profile;
expect(aUsersProfile.user).to.deep.equal(aUser);
const reducer = schema.reducer();
const nextState = reducer(
initialState,
{
type: 'CREATE_USER',
payload: {
user: {
id: 4,
name: 'Jonathan',
},
profile: {
id: 4,
greeting: 'This is Jonathan!',
},
},
}
);
const {User: nextUser, Profile: nextProfile} = schema.from(nextState);
expect(nextUser.count()).to.equal(5);
expect(nextProfile.count()).to.equal(5);
expect(nextUser.last().profile).to.be.undefined;
nextUser.last().delete();
});
});

@@ -141,30 +141,43 @@ import forOwn from 'lodash/object/forOwn';

function querySetGetterDelegatorFactory(getterName) {
return function querySetGetterDelegator() {
const qs = this.getQuerySet();
return qs[getterName];
};
}
function forEachSuperClass(subClass, func) {
let currClass = subClass;
while (currClass !== Function.prototype) {
func(currClass);
currClass = Object.getPrototypeOf(currClass);
}
}
function attachQuerySetMethods(modelClass, querySetClass) {
const querySetSharedMethods = querySetClass.sharedMethods;
querySetSharedMethods.forEach(methodName => {
// Check for descriptor.
const descriptor = Object.getOwnPropertyDescriptor(querySetClass.prototype, methodName);
if (typeof descriptor !== 'undefined' && typeof descriptor.get !== 'undefined') {
Object.defineProperty(modelClass, methodName, descriptor);
} else {
modelClass[methodName] = querySetDelegatorFactory(methodName);
const leftToDefine = querySetClass.sharedMethods.slice();
// There is no way to get a property descriptor for the whole prototype chain;
// only from an objects own properties. Therefore we traverse the whole prototype
// chain for querySet.
forEachSuperClass(querySetClass, (cls) => {
for (let i = 0; i < leftToDefine.length; i++) {
let defined = false;
const methodName = leftToDefine[i];
const descriptor = Object.getOwnPropertyDescriptor(cls.prototype, methodName);
if (typeof descriptor !== 'undefined') {
if (typeof descriptor.get !== 'undefined') {
descriptor.get = querySetGetterDelegatorFactory(methodName);
Object.defineProperty(modelClass, methodName, descriptor);
defined = true;
} else if (typeof descriptor.value === 'function') {
modelClass[methodName] = querySetDelegatorFactory(methodName);
defined = true;
}
}
if (defined) {
leftToDefine.splice(i--, 1);
}
}
});
// `plain` and `models` are specially handled cases
// as they're static getters.
Object.defineProperties(modelClass, {
plain: {
get() {
this._plain = true;
return this;
},
},
models: {
get() {
this._plain = false;
return this;
},
},
});
}

@@ -171,0 +184,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc