From 29f67bd157c6ab1b617fa680eb647fd8227910f3 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 21 Dec 2016 11:21:09 -0500 Subject: [PATCH] Add store.Cascading * src/store/Cascading.js: Add trait. * test/store/CascadingTest.js: Add test case. --- src/store/Cascading.js | 99 +++++++++++++++++++++++++++++++++++++ test/store/CascadingTest.js | 77 +++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/store/Cascading.js create mode 100644 test/store/CascadingTest.js diff --git a/src/store/Cascading.js b/src/store/Cascading.js new file mode 100644 index 0000000..0bf4dfa --- /dev/null +++ b/src/store/Cascading.js @@ -0,0 +1,99 @@ +/** + * Recurisvely-clearing key/value store + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of the Liza Data Collection Framework + * + * Liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var Trait = require( 'easejs' ).Trait, + Class = require( 'easejs' ).Class, + Store = require( './Store' ); + + +/** + * Store of stores with cascading clear + * + * The store `S` into which this trait is mixed will accept only other + * Store objects which will each have their `#clear` method called + * when `#clear` is invoked on `S`. + * + * @example + * let store_a = Store().add( 'key', value' ), + * store_b = Store().add( 'foo', 'bar' ); + * + * store_a.get( 'key' ); // value + * store_b.get( 'foo' ); // bar + * + * Store.use( Cascading ) + * .add( 'a', store_a ) + * .add( 'b', store_b ) + * .clear(); + * + * store_a.get( 'key' ); // undefined + * store_b.get( 'foo' ); // undefined + * + * Store.use( Cascading ).add( 'invalid', 'value' ); + * // TypeError: Can only add Store to Cascading stores + * + * Although clear cascades to each `Store`, other methods do not (for + * example, `get` will not query all `Store`s); another trait should + * be added for such a thing. This behavior allows for, effectively, + * namespacing. + * + * This trait can be used, for example, to implement a centralized + * caching system that can trigger a reload of objects realtime + * system-wide, allowing for transparent hot code swapping (assuming + * that the caller will re-store). + */ +module.exports = Trait( 'Cascading' ) + .implement( Store ) + .extend( +{ + /** + * Add item to store under `key` with value `value` + * + * Only [`Store`]{@link module:store.Store} objects may be attached. + * + * @param {string} key store key + * @param {Store} value Store to attach + * + * @return {Store} self + */ + 'virtual abstract override public add': function( key, value ) + { + if ( !Class.isA( Store, value ) ) + { + throw TypeError( "Can only add Store to Cascading stores" ); + } + + return this.__super( key, value ); + }, + + + /** + * Clear all stores in the store + * + * @return {Store} self + */ + 'virtual abstract override public clear': function() + { + this.reduce( function( _, store ) + { + store.clear(); + } ); + }, +} ); diff --git a/test/store/CascadingTest.js b/test/store/CascadingTest.js new file mode 100644 index 0000000..51aaca0 --- /dev/null +++ b/test/store/CascadingTest.js @@ -0,0 +1,77 @@ +/** + * Test case for Cascading store + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of the Liza Data Collection Framework + * + * Liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +"use strict"; + +var store = require( '../../' ).store, + expect = require( 'chai' ).expect, + Store = store.MemoryStore, + Sut = store.Cascading; + +describe( 'store.Cascading', () => +{ + describe( '#add', () => + { + it( 'does not allow attaching non-store objects', () => + { + expect( () => Store.use( Sut )().add( 'invalid', {} ) ) + .to.throw( TypeError ); + } ); + + + it( 'allows attaching Store objects', () => + { + expect( () => Store.use( Sut )().add( 'valid', Store() ) ) + .to.not.throw( TypeError ); + } ); + } ); + + + describe( '#clear', () => + { + it( 'invokes #clear on all contained stores', () => + { + const cleared = []; + + const MockStore = Store.extend( + { + 'override clear'() + { + cleared.push( this.__inst ); + } + } ); + + const stores = [ 1, 2, 3 ].map( () => MockStore() ); + const sut = Store.use( Sut )(); + + stores.forEach( ( store, i ) => sut.add( i, store ) ); + + // should trigger clear on all stores + sut.clear(); + + expect( + stores.every( store => + cleared.some( item => item === store ) + ) + ).to.be.true; + } ); + } ); +} ); \ No newline at end of file