From db85305e557daebe4b84d610c65c2a43a9402ab5 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 5 Sep 2011 16:01:29 -0600 Subject: [PATCH] added json-storage-model --- json-storage-model/lib/index.js | 282 ++++++++++++++++++++++++++++ json-storage-model/lib/package.json | 20 ++ json-storage-model/test/test.js | 247 ++++++++++++++++++++++++ 3 files changed, 549 insertions(+) create mode 100644 json-storage-model/lib/index.js create mode 100644 json-storage-model/lib/package.json create mode 100644 json-storage-model/test/test.js diff --git a/json-storage-model/lib/index.js b/json-storage-model/lib/index.js new file mode 100644 index 0000000..b9a2d65 --- /dev/null +++ b/json-storage-model/lib/index.js @@ -0,0 +1,282 @@ +(function () { + "use strict"; + + var localStorage = require('localStorage') + , JsonStorage = require('json-storage') + , jsonStorage = JsonStorage(localStorage) + , db = jsonStorage + , uuidgen = require('node-uuid') + , schemamap = {} + ; + + + function create(schema) { + var models = {}; + + function createModel(lsmodel, prefix, submodels, uuid) { + // + // New Model + // + function LModel(obj) { + var lmodel = this; + + // Add any already-appended submodels + // TODO DRY this up + submodels.forEach(function (subname) { + var sub_ids = '_' + subname + '_ids' + , subs = obj[subname] + , suuid = schemamap[subname].uuid || 'uuid'; + + // Just in case the object is already created + if ('function' === typeof obj.__lookupGetter__(subname)) { + return; + } + + if (false === Array.isArray(subs)) { + return; + } + + obj[sub_ids] = []; + lmodel[sub_ids] = obj[sub_ids]; + + subs.forEach(function (sub) { + if (!sub[suuid]) { + sub[suuid] = uuidgen(); + } + models[subname].set(sub[suuid], sub); + lmodel[sub_ids].push(sub[suuid]); + }); + + obj[subname] = undefined; + delete obj[subname]; + }); + + // Copy object to this + Object.keys(obj).forEach(function (k) { + lmodel[k] = obj[k]; + }); + + // create uuid if it has none + if (undefined === obj[uuid]) { + obj[uuid] = uuidgen(); + lmodel[uuid] = obj[uuid]; + } + } + + // A simple save method + LModel.prototype.save = function () { + lsmodel.set(this[uuid], this); + }; + + + // getters for submodels + submodels.forEach(function (subname) { + var sub_ids = '_' + subname + '_ids'; + + LModel.prototype.__defineGetter__(subname, function () { + // not all submodels exist at "compile-time" + var submodel = models[subname] + , lmodel = this + , subs + , suuid = schemamap[subname].uuid || 'uuid'; + + lmodel[sub_ids] = lmodel[sub_ids] || []; + + subs = submodel.some(lmodel[sub_ids]); + + // modify collection, but leave as array with forEach + // XXX maybe create prototype with forEach + subs.add = function (obj) { + subs.push(obj); + if (undefined === obj[suuid]) { + submodel.add(obj); + } else { + submodel.set(obj[suuid], obj); + } + lmodel[sub_ids].push(obj[suuid]); + lmodel.save(); + }; + + return subs; + }); + }); + + return LModel; + } + + + // A database abstraction + function Model(model) { + var lsmodel = this + , prefix = model.name + , submodels = model.has_many || [] + , uuid = model.uuid || 'uuid' + , LModel = createModel(lsmodel, prefix, submodels, uuid); + + lsmodel.create = function (a, b, c) { + return new LModel(a, b, c) + }; + + + // clear + lsmodel.clear = function () { + return db.set(prefix + '-lsm', []); + }; + + // all keys + lsmodel.keys = function () { + return db.get(prefix + '-lsm'); + }; + + // all items + lsmodel.all = function () { + var items = []; + lsmodel.keys().forEach(function (uuid) { + var item = lsmodel.get(uuid) + // TODO this should be a useless check + if (null === item) { + lsmodel.remove(uuid); + return; + } + items.push(item); + }); + return items; + }; + + // TODO query accepts objects + lsmodel.query = function (obj) { + // + return null; + }; + + // pass in your filter function + lsmodel.filter = function (test) { + var all = lsmodel.all(), + results = []; + + all.forEach(function (one) { + if (!test(one)) { + return; + } + results.push(one); + }); + return results; + } + + lsmodel.some = function (uuids) { + var results = []; + + uuids.forEach(function (uuid) { + var one = lsmodel.get(uuid); + // push null elements to keep array indices + results.push(one); + }); + + return results; + } + + // TODO query object + lsmodel.get = function (uuid) { + var item = db.get(uuid); + if (null === item) { + return null; + } + return new LModel(item); + }; + + lsmodel.set = function (uuid, val) { + var keys; + + if (null === val || undefined === val) { + return db.remove(uuid); + } + keys = db.get(prefix + '-lsm') || []; + + // add the key if it didn't exist + if (-1 === keys.indexOf(uuid)) { + keys.push(uuid); + db.set(prefix + '-lsm', keys); + } + + submodels.forEach(function (mod) { + var children = val[mod] || []; + + // TODO decouple or create new objects + children.forEach(function (child) { + var e; + if (null === child) { + return; + } + if ('string' === typeof child) { + return; + } + if ('string' !== typeof child.uuid) { + return; + } + // TODO other[mod].set(uuid, child); + }); + }); + /* + console.log('\n\n val'); + console.log(val); + console.log('\n\n'); + */ + db.set(uuid, val) + }; + + // Remove an item from the table list and the db + lsmodel.remove = function (uuid) { + var keys = db.get(prefix + '-lsm') + , i; + + keys.forEach(function (key, j) { + if (key === uuid) { + i = j; + } + }); + + if (undefined === i) { + return; + } + db.remove(uuid); + keys.splice(i,1); + db.set(prefix + '-lsm', keys); + }; + + // Remove all objects from the table list and db + lsmodel.clear = function () { + lsmodel.keys().forEach(function (uuid) { + db.remove(uuid); + }); + db.set(prefix + '-lsm', []); + }; + + lsmodel.add = function (val) { + var obj = lsmodel.create(val); + obj.save(); + return obj; + }; + } + + // TODO compare versions of schema + db.set('schema-lsm', schema); + + schema.forEach(function (scheme) { + var table = scheme.name + , x = db.get(table + '-lsm') + ; + + // Pre-create the "models" tableshould always have + schemamap[scheme.name] = scheme; + if (!Array.isArray(x)) { + db.set(table + '-lsm', []); + } + + models[scheme.name] = new Model(scheme); + }); + + return models; + } + + module.exports = create; +}()); diff --git a/json-storage-model/lib/package.json b/json-storage-model/lib/package.json new file mode 100644 index 0000000..598c42c --- /dev/null +++ b/json-storage-model/lib/package.json @@ -0,0 +1,20 @@ +{ + "author": "AJ ONeal (http://coolaj86.info)", + "name": "json-storage-model", + "description": "An abstraction for models to be stored in json-storage", + "keywords": ["ender", "model", "json-storage", "localStorage", "sessionStorage", "globalStorage", "Storage"], + "version": "0.9.1", + "repository": { + "type": "git", + "url": "git://github.com/coolaj86/json-storage-js.git" + }, + "engines": { + "node": ">= v0.2.0" + }, + "main": "index", + "dependencies": { + "json-storage": ">= 1.0.0" + , "node-uuid": ">= 0.0.0" + }, + "devDependencies": {} +} diff --git a/json-storage-model/test/test.js b/json-storage-model/test/test.js new file mode 100644 index 0000000..6875417 --- /dev/null +++ b/json-storage-model/test/test.js @@ -0,0 +1,247 @@ +(function () { + "use strict"; + + var Model = require('json-storage-model') + , assert = require('assert') + , schema + , lsmAll = {} + , Returns + , Batches; + + schema = [ + { + name: "returns", + has_many: ['batches'], + uuid: "key" + }, + { + name: "batches", + has_many: ['products'] + }, + { + name: "products" + } + ]; + + function setup() { + lsmAll = new Model(schema); + //console.log(lsmAll); + Returns = lsmAll.returns; + Batches = lsmAll.batches; + } + + function empty() { + assert.deepEqual([], Returns.keys()); + assert.deepEqual([], Returns.all()); + assert.deepEqual([], Returns.some([])); + assert.deepEqual([null], Returns.some(['x'])); + assert.deepEqual([null, null], Returns.some(['x','y'])); + assert.deepEqual(null, Returns.get('nada')); + Returns.clear(); + assert.deepEqual([], Returns.keys()); + assert.deepEqual([], Returns.all()); + assert.deepEqual([], Returns.some([])); + assert.deepEqual([null], Returns.some(['x'])); + assert.deepEqual([null, null], Returns.some(['x','y'])); + assert.deepEqual(null, Returns.get('nada')); + } + + function errors() { + var errcount = 0; + // TODO make throw + try { + Returns.get(); + } catch(e) { + errcount += 1; + } + //assert.equal(1, errcount); + console.log('skipped `throw error on get(undefined)`'); + + try { + Returns.some(); + } catch(e) { + errcount += 1; + } + assert.equal(1, errcount); + + Returns.some([]); + } + + function createWithoutKey() { + var ret = Returns.create({ + memo: "031811-IHC", + }) + , ret2; + + assert.ok(ret.key); + assert.equal("031811-IHC", ret.memo); + + ret2 = Returns.get(ret.key); + assert.equal(null, ret2); + + ret.test1 = true; + ret.save(); + + ret2 = Returns.get(ret.key); + assert.equal("031811-IHC", ret2.memo); + } + + function createWithKey() { + var ret = Returns.create({ + key: "ajs-test", + memo: "031811-IHC", + }) + , ret2; + + ret2 = Returns.get(ret.key); + assert.ok(!ret2); + + assert.equal("ajs-test", ret.key); + ret.save(); + + ret2 = Returns.get(ret.key); + assert.ok(ret2.memo); + } + + function insertRemove() { + var keys = [] + , all = [] + , ids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + , k; + + Returns.clear(); + + // add + function populate() { + all = []; + keys = []; + ids.forEach(function (id, i) { + // this is private logic + all.push({ key: id, _batches_ids: [] }); + keys.push(all[i].key); + Returns.create(all[i]).save(); + }); + assert.deepEqual(all, Returns.all()); + assert.deepEqual(keys, Returns.keys()); + } + + // Remove from first + populate(); + for (k = 0; k < ids.length; k += 1) { + Returns.remove(ids[k]); + } + assert.deepEqual([], Returns.all()); + assert.deepEqual([], Returns.keys()); + + + // Remove from last + populate(); + for (k = ids.length - 1; k >= 0; k -= 1) { + Returns.remove(ids[k]); + } + assert.deepEqual([], Returns.all()); + assert.deepEqual([], Returns.keys()); + + // Remove from middle + populate(); + ids.sort(function () { return 0.5 - Math.random(); }); + for (k = ids.length - 1; k >= 0; k -= 1) { + Returns.remove(ids[k]); + } + assert.deepEqual([], Returns.all()); + assert.deepEqual([], Returns.keys()); + } + + // testing all, keys, some, filter + function all() { + var keys = [], + all = [], + some, + ids = []; + + Returns.clear(); + assert.deepEqual(all, Returns.all()); + assert.deepEqual(keys, Returns.keys()); + + all.push({ key: "one", memo: "3131-mtn", _batches_ids: [] }); + keys.push(all[0].key); + Returns.create(all[0]).save(); + + all.push({ key: "two", memo: "3232-hlt", _batches_ids: [] }); + keys.push(all[1].key); + Returns.create(all[1]).save(); + + all.push({ key: "three", memo: "4123-hbc", _batches_ids: [] }); + keys.push(all[2].key); + Returns.create(all[2]).save(); + + all.push({ key: "four", memo: "4441-dtf", _batches_ids: [] }); + keys.push(all[3].key); + Returns.create(all[3]).save(); + + all.push({ key: "five", memo: "4412-abn", _batches_ids: [] }); + keys.push(all[4].key); + Returns.create(all[4]).save(); + + some = Returns.filter(function (ret) { + return /^4\d/.test(ret.memo); + }); + assert.equal(3, some.length); + + some.forEach(function (one) { + ids.push(one.key); + }); + assert.deepEqual(some, Returns.some(ids)); + + assert.deepEqual(all, Returns.all()); + assert.deepEqual(keys, Returns.keys()); + assert.deepEqual(all.slice(1,3), Returns.some(["two", "three"])); + + assert.deepEqual(keys, Returns.keys()); + + console.log('skipping not-implemented `query`'); + } + + function relate() { + var batches = [ + { + uuid: "a", + name: "chuck", + _products_ids: [] + }, + { + name: "daryl", + _products_ids: [] + } + ] + , ret = Returns.create({}) + , batch; + + // Add relation + ret.save(); + ret = Returns.get(ret.key); + ret.batches.add(batches[0]); + assert.deepEqual(Batches.all()[0], batches[0]); + assert.deepEqual(ret.batches[0], batches[0]); + ret.save(); + + // create with an existing relation + ret = Returns.create({ batches: batches }); + batches[1].uuid = ret.batches[1].uuid; + + console.log('skipping assert which requires visual inspection'); + //assert.deepEqual(batches, ret.batches); + //console.log(Batches.all()); + } + + setup(); + empty(); + errors(); + createWithoutKey(); + createWithKey(); + insertRemove(); + all(); + relate(); + + console.log("All tests passed"); +}());