From 0c3d869a048e7db1e594c85332ac12bb08006fe5 Mon Sep 17 00:00:00 2001 From: Will Goto Date: Fri, 29 Sep 2017 15:00:11 -0700 Subject: [PATCH 1/3] Create addEntities method --- src/models/Store.js | 64 +++++++++++++++++++++++++++++++------- tests/models/Store.test.js | 56 +++++++++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 15 deletions(-) diff --git a/src/models/Store.js b/src/models/Store.js index ed55352..8202b20 100644 --- a/src/models/Store.js +++ b/src/models/Store.js @@ -1,4 +1,5 @@ import isPlainObject from 'lodash/isPlainObject'; +import mapValues from 'lodash/mapValues'; import pick from 'lodash/pick'; import Model from './Model'; @@ -25,6 +26,12 @@ export default class Store extends Model { }, {}); } + static defaultState() { + return { + entityDefinitions: {} + }; + } + //===================== // INTERFACING METHODS @@ -49,16 +56,19 @@ export default class Store extends Model { Object.assign(this.state, this._fromSerialState(state, models, newModels)); } + addEntities(entities) { + this.setState(this._mergeEntities(entities)); + } + //================== // INTERNAL METHODS //================== _createModelsHash() { - return Object.keys(Store.modelsHash).reduce((hash, key) => { - hash[key] = {}; - return hash; - }, {}); + return mapValues(Store.modelsHash, () => { + return {}; + }); } _toSerial(data, store) { @@ -78,10 +88,9 @@ export default class Store extends Model { } _toSerialState(state, store) { - return Object.keys(state).reduce((serializableState, key) => { - serializableState[key] = this._toSerial(state[key], store); - return serializableState; - }, {}); + return mapValues(state, (value, key) => + this._toSerial(state[key], store) + ); } _toSerialModel(model, store) { @@ -112,10 +121,9 @@ export default class Store extends Model { } _fromSerialState(state, models, newModels) { - return Object.keys(state).reduce((finalState, key) => { - finalState[key] = this._fromSerial(state[key], models, newModels); - return finalState; - }, {}); + return mapValues(state, (value, key) => + this._fromSerial(value, models, newModels) + ); } _fromSerialModel(model, models, newModels) { @@ -133,4 +141,36 @@ export default class Store extends Model { return newModelHash[_id] = newModel; } + _mergeEntities(entities) { + return mapValues(entities, (entity, key) => + this._mergeEntity(entity, key) + ); + } + + _mergeEntity(entity, key) { + return Object.assign({}, this.state[key], this._mergeInstances(entity, key)); + } + + _mergeInstances(entity, key) { + return mapValues(entity, (instance, id) => + this._instanceExists(key, id) + ? this._updateExistingInstance(key, id, instance) + : this._createNewInstance(key, instance) + ); + } + + _instanceExists(key, id) { + return !!this.state[key][id]; + } + + _updateExistingInstance(key, id, instance) { + this.state[key][id].setState(instance); + return this.state[key][id]; + } + + _createNewInstance(key, instance) { + const Model = this.getEntityDefinitions()[key]; + return new Model(Object.assign({}, instance, { store: this })); + } + } diff --git a/tests/models/Store.test.js b/tests/models/Store.test.js index cf56550..dc5b23c 100644 --- a/tests/models/Store.test.js +++ b/tests/models/Store.test.js @@ -73,7 +73,7 @@ class ListItem extends Model { } const listItem1 = new ListItem({ id: '1' }); -const listItem2 = new ListItem({ id: '2' }); +const listItem2 = new ListItem({ id: '2', name: 'old name' }); const listItem3 = new ListItem({ id: '3' }); const listItem4 = new ListItem({ id: '4' }); @@ -87,6 +87,8 @@ const list2 = new List({ id: '2' }); list1.setListItems([listItem1, listItem2, listItem3, listItem4]); list2.setOtherList(list1); +const entityDefinitions = {}; + //============= // STORE TESTS @@ -105,7 +107,7 @@ describe('Store', () => { describe('parse', () => { describe('with non-Model data', () => { beforeEach(() => { - state = { string, number, float, bool, array, object }; + state = { string, number, float, bool, array, object, entityDefinitions }; store = new Store(state); output = store.stringify(); store = new Store(); @@ -224,7 +226,7 @@ describe('Store', () => { describe('parseMerge', () => { beforeEach(() => { subState = { number, float, bool, array, object }; - state = { string, number, float, bool, array, object }; + state = { string, number, float, bool, array, object, entityDefinitions }; store = new Store(subState); output = store.stringify(); store = new Store({ string, number: {} }); @@ -249,4 +251,52 @@ describe('Store', () => { expect(store.state).toEqual({ number }); }); }); + + describe('addEntities', () => { + beforeEach(() => { + state = { + entityDefinitions: { + listItems: ListItem, + lists: List + }, + listItems: { + 1: listItem1, + 2: listItem2 + }, + lists: {} + }; + + store = new Store(state); + + store.addEntities({ + listItems: { + 2: { name: 'updated name' }, + 3: { id: 3, name: 'new name' } + }, + lists: { + 1: { id: 1, name: 'new list' } + } + }); + }); + + it('should not replace the first list item', () => { + expect(store.getListItems()[1]).toBe(listItem1); + }); + + it('should not replace the second list item', () => { + expect(store.getListItems()[2]).toBe(listItem2); + }); + + it('should update the second list item', () => { + expect(store.getListItems()[2].getName()).toBe('updated name'); + }); + + it('should create the third list item', () => { + expect(store.getListItems()[3].getName()).toBe('new name'); + }); + + it('should create the first list', () => { + expect(store.getLists()[1].getName()).toBe('new list'); + }) + }) }); From cf91826b6a1da7e59ca66ad00d4ede8184f3154b Mon Sep 17 00:00:00 2001 From: Will Goto Date: Fri, 29 Sep 2017 15:28:02 -0700 Subject: [PATCH 2/3] Create entity find meta-programmed functions --- src/models/Store.js | 21 +++++++++++++++++++ tests/models/Store.test.js | 43 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/models/Store.js b/src/models/Store.js index 8202b20..49195a5 100644 --- a/src/models/Store.js +++ b/src/models/Store.js @@ -6,6 +6,12 @@ import Model from './Model'; export default class Store extends Model { + constructor(state) { + super(state); + this._createEntityHelpers(); + } + + //================== // CLASS PROPERTIES //================== @@ -65,6 +71,21 @@ export default class Store extends Model { // INTERNAL METHODS //================== + _createEntityHelpers() { + Object.keys(this.state.entityDefinitions).forEach(key => { + const cappedKey = key[0].toUpperCase() + key.substring(1); + const findKey = `find${cappedKey}`; + + if (isPlainObject(this.state[key])) { + this.constructor.prototype[findKey] = this[findKey] || function (id) { + return id instanceof Array + ? id.map(_id => this.state[key][_id]) + : this.state[key][id]; + }; + } + }); + } + _createModelsHash() { return mapValues(Store.modelsHash, () => { return {}; diff --git a/tests/models/Store.test.js b/tests/models/Store.test.js index dc5b23c..aece1c7 100644 --- a/tests/models/Store.test.js +++ b/tests/models/Store.test.js @@ -297,6 +297,45 @@ describe('Store', () => { it('should create the first list', () => { expect(store.getLists()[1].getName()).toBe('new list'); - }) - }) + }); + }); + + describe('createEntityHelpers', () => { + beforeEach(() => { + state = { + entityDefinitions: { + listItems: ListItem, + lists: List, + falseEntity: {} + }, + listItems: { + 1: listItem1, + 2: listItem2 + }, + lists: { + 1: list1, + } + }; + + store = new Store(state); + }); + + describe('when multiple ids are provided', () => { + it('should return an array of items', () => { + expect(store.findListItems([1, 2])).toEqual([listItem1, listItem2]); + }); + }); + + describe('when one id is provided', () => { + it('should return the item', () => { + expect(store.findLists(1)).toBe(list1); + }); + }); + + describe('when a false entity is not correctly stored', () => { + it('should not define a find function', () => { + expect(store.findFalseEntity).not.toBeDefined(); + }); + }); + }); }); From e8d182abf07223461c93a981e01ad4bae9853dbf Mon Sep 17 00:00:00 2001 From: Will Goto Date: Fri, 29 Sep 2017 15:30:11 -0700 Subject: [PATCH 3/3] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9698177..58d717e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-axiom", - "version": "0.2.3", + "version": "0.2.4", "description": "React Axiom is a way to use models with React.", "repository": "https://github.com/wgoto/react-axiom", "main": "lib/react-axiom.js",