Skip to content

Commit

Permalink
Merge pull request #48 from wgoto/store
Browse files Browse the repository at this point in the history
Improve store functionality
  • Loading branch information
wGoto authored Sep 29, 2017
2 parents f804c00 + e8d182a commit 573b00f
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 16 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
85 changes: 73 additions & 12 deletions src/models/Store.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import isPlainObject from 'lodash/isPlainObject';
import mapValues from 'lodash/mapValues';
import pick from 'lodash/pick';
import Model from './Model';


export default class Store extends Model {

constructor(state) {
super(state);
this._createEntityHelpers();
}


//==================
// CLASS PROPERTIES
//==================
Expand All @@ -25,6 +32,12 @@ export default class Store extends Model {
}, {});
}

static defaultState() {
return {
entityDefinitions: {}
};
}


//=====================
// INTERFACING METHODS
Expand All @@ -49,16 +62,34 @@ export default class Store extends Model {
Object.assign(this.state, this._fromSerialState(state, models, newModels));
}

addEntities(entities) {
this.setState(this._mergeEntities(entities));
}


//==================
// 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 Object.keys(Store.modelsHash).reduce((hash, key) => {
hash[key] = {};
return hash;
}, {});
return mapValues(Store.modelsHash, () => {
return {};
});
}

_toSerial(data, store) {
Expand All @@ -78,10 +109,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) {
Expand Down Expand Up @@ -112,10 +142,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) {
Expand All @@ -133,4 +162,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 }));
}

}
95 changes: 92 additions & 3 deletions tests/models/Store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' });

Expand All @@ -87,6 +87,8 @@ const list2 = new List({ id: '2' });
list1.setListItems([listItem1, listItem2, listItem3, listItem4]);
list2.setOtherList(list1);

const entityDefinitions = {};


//=============
// STORE TESTS
Expand All @@ -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();
Expand Down Expand Up @@ -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: {} });
Expand All @@ -249,4 +251,91 @@ 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');
});
});

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();
});
});
});
});

0 comments on commit 573b00f

Please sign in to comment.