From d8608600add69c044b70515b87a3e1ba200eb6df Mon Sep 17 00:00:00 2001 From: Haydn Ewers Date: Thu, 20 Aug 2015 10:53:33 +0930 Subject: [PATCH 1/3] Add support for pseudonyms. --- example/types/comment.js | 3 +- example/types/product.js | 5 ++- spec/store/core/define-spec.js | 24 +++++++++++++ src/store.js | 63 ++++++++++++++++++++-------------- 4 files changed, 64 insertions(+), 31 deletions(-) diff --git a/example/types/comment.js b/example/types/comment.js index 6de9d78..9152cf4 100644 --- a/example/types/comment.js +++ b/example/types/comment.js @@ -1,5 +1,4 @@ var Comment = { body: Store.attr(), - // FIXME: There shouldn't be a need to set the inverse relationship here. - product: Store.hasOne({ inverse: "comments" }) + product: Store.hasOne() }; diff --git a/example/types/product.js b/example/types/product.js index 26a6c4c..4437acb 100644 --- a/example/types/product.js +++ b/example/types/product.js @@ -2,8 +2,7 @@ var Product = { title: Store.attr(), description: Store.attr(), categories: Store.hasMany(), - // Because the inverse relationship isn't "products" we need provide it). - comments: Store.hasMany({ inverse: "product" }), - // This relationship is one-way (no iverse relationship). + comments: Store.hasMany(), + // This relationship is one-way (no inverse relationship). relatedProducts: Store.hasMany("related") }; diff --git a/spec/store/core/define-spec.js b/spec/store/core/define-spec.js index 95106b6..69abe50 100644 --- a/spec/store/core/define-spec.js +++ b/spec/store/core/define-spec.js @@ -8,4 +8,28 @@ describe("define", function() { store = new Store(); }); + it("must accept pseudonyms", function () { + store.define([ "comments", "comment" ], { + product: Store.hasOne() + }); + store.define([ "products", "product" ], { + comments: Store.hasMany() + }); + expect(store.find("comment", "67")).toBe(store.find("comments", "67")); + expect(store.find("product", "8")).toBe(store.find("products", "8")); + store.add({ + "type": "comment", + "id": "1", + "relationships": { + "product": { + "data": { + "type": "products", + "id": "1" + } + } + } + }); + expect(store.find("product", "1").comments[0]).toBe(store.find("comments", "1")); + }); + }); diff --git a/src/store.js b/src/store.js index 6ac0e4a..2fa7e98 100644 --- a/src/store.js +++ b/src/store.js @@ -96,8 +96,8 @@ export default class Store { * `push()` method. * * @since 0.1.0 - * @param {Object} object - Resource Object to add. See: - http://jsonapi.org/format/#document-resource-objects + * @param {!Object} object - Resource Object to add. See: + http://jsonapi.org/format/#document-resource-objects * @return {undefined} - Nothing. */ add(object) { @@ -106,7 +106,9 @@ export default class Store { let resource = this.find(object.type, object.id); let definition = this._types[object.type]; Object.keys(definition).forEach(fieldName => { - this._addField(object, resource, definition, fieldName); + if (fieldName[0] !== "_") { + this._addField(object, resource, definition, fieldName); + } }); } else { throw new TypeError(`The data must have a type and id`); @@ -120,12 +122,14 @@ export default class Store { * Defines a type of resource. * * @since 0.2.0 - * @param {string} name - Name of the resource. - * @param {Object} defition - The resource's definition. + * @param {!string|string[]} names - Name(s) of the resource. + * @param {!Object} definition - The resource's definition. * @return {undefined} - Nothing. */ - define(name, defition) { - this._types[name] = defition; + define(names, definition) { + names = (names.constructor === Array) ? names : [ names ]; + definition._names = names; + names.forEach(name => this._types[name] = definition); } /** @@ -141,11 +145,13 @@ export default class Store { * @return {Object|Object[]} - Either the resource or an array of resources. */ find(type, id) { - var definition; if (type) { - definition = this._types[type]; + let definition = this._types[type]; if (definition) { - this._data[type] = this._data[type] || {}; + if (!this._data[type]) { + let collection = {}; + definition._names.forEach(t => this._data[t] = collection); + } if (id) { if (!this._data[type][id]) { this._data[type][id] = { @@ -154,7 +160,9 @@ export default class Store { id: id }; Object.keys(definition).forEach(key => { - this._data[type][id][key] = definition[key].default; + if (key[0] !== "_") { + this._data[type][id][key] = definition[key].default; + } }); } return this._data[type][id]; @@ -244,23 +252,26 @@ export default class Store { _addInverseRelationship(sourceResource, sourceFieldName, targetResource, sourceField) { var targetDefinition = this._types[targetResource.type]; - var targetFieldName = sourceField.inverse || targetResource.type; - var targetField = targetDefinition && targetDefinition[targetFieldName]; - targetResource._dependents.push({ type: sourceResource.type, id: sourceResource.id, fieldName: sourceFieldName }); - if (targetField) { - if (targetField.type === "has-one") { - sourceResource._dependents.push({ type: targetResource.type, id: targetResource.id, fieldName: targetFieldName }); - targetResource[targetFieldName] = sourceResource; - } else if (targetField.type === "has-many") { - sourceResource._dependents.push({ type: targetResource.type, id: targetResource.id, fieldName: targetFieldName }); - if (targetResource[targetFieldName].indexOf(sourceResource) === -1) { - targetResource[targetFieldName].push(sourceResource); + var sourceDefinition = this._types[sourceResource.type]; + if (targetDefinition) { + let targetFieldName = [ sourceField.inverse ].concat(sourceDefinition._names).find(x => targetDefinition[x]); + let targetField = targetDefinition && targetDefinition[targetFieldName]; + targetResource._dependents.push({ type: sourceResource.type, id: sourceResource.id, fieldName: sourceFieldName }); + if (targetField) { + if (targetField.type === "has-one") { + sourceResource._dependents.push({ type: targetResource.type, id: targetResource.id, fieldName: targetFieldName }); + targetResource[targetFieldName] = sourceResource; + } else if (targetField.type === "has-many") { + sourceResource._dependents.push({ type: targetResource.type, id: targetResource.id, fieldName: targetFieldName }); + if (targetResource[targetFieldName].indexOf(sourceResource) === -1) { + targetResource[targetFieldName].push(sourceResource); + } + } else if (targetField.type === "attr") { + throw new Error(`The the inverse relationship for '${sourceFieldName}' is an attribute ('${targetFieldName}')`); } - } else if (targetField.type === "attr") { - throw new Error(`The the inverse relationship for '${sourceFieldName}' is an attribute ('${targetFieldName}')`); + } else if (sourceField.inverse) { + throw new Error(`The the inverse relationship for '${sourceFieldName}' is missing ('${sourceField.inverse}')`); } - } else if (sourceField.inverse) { - throw new Error(`The the inverse relationship for '${sourceFieldName}' is missing ('${sourceField.inverse}')`); } } -- 2.51.2 From bafd75a6d1536d3e6defcfbbf3509c1c30990681 Mon Sep 17 00:00:00 2001 From: Haydn Ewers Date: Thu, 20 Aug 2015 11:42:19 +0930 Subject: [PATCH 2/3] Add array find pollyfill. --- package.json | 3 +++ src/store.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/package.json b/package.json index b1914b0..c317044 100644 --- a/package.json +++ b/package.json @@ -11,5 +11,8 @@ "devDependencies": { "babel": "^5.8.21", "jasmine": "^2.3.2" + }, + "dependencies": { + "array.prototype.find": "^1.0.0" } } diff --git a/src/store.js b/src/store.js index d51d0fd..8b5e591 100644 --- a/src/store.js +++ b/src/store.js @@ -1,3 +1,5 @@ +import "array.prototype.find"; + export default class Store { /** -- 2.51.2 From 1c9d69bdca0fd5138ffeb46a43e38544a559e67b Mon Sep 17 00:00:00 2001 From: Haydn Ewers Date: Thu, 20 Aug 2015 11:43:17 +0930 Subject: [PATCH 3/3] Ensure define throws an error if defining a type that's already been defined. --- spec/store/core/define-spec.js | 16 ++++++++++++---- src/store.js | 8 +++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/spec/store/core/define-spec.js b/spec/store/core/define-spec.js index 69abe50..0532a76 100644 --- a/spec/store/core/define-spec.js +++ b/spec/store/core/define-spec.js @@ -1,10 +1,10 @@ var Store = require("../../../src/store"); -describe("define", function() { +describe("define", function () { var store; - beforeEach(function() { + beforeEach(function () { store = new Store(); }); @@ -18,7 +18,7 @@ describe("define", function() { expect(store.find("comment", "67")).toBe(store.find("comments", "67")); expect(store.find("product", "8")).toBe(store.find("products", "8")); store.add({ - "type": "comment", + "type": "comments", "id": "1", "relationships": { "product": { @@ -29,7 +29,15 @@ describe("define", function() { } } }); - expect(store.find("product", "1").comments[0]).toBe(store.find("comments", "1")); + expect(store.find("product", "1").comments[0]).toBe(store.find("comment", "1")); + expect(store.find("comments", "1").product).toBe(store.find("products", "1")); + }); + + it("must throw an error if you try to define a type that has already been defined", function () { + store.define("example", {}); + expect(function () { + store.define([ "sample", "example"], {}); + }).toThrowError("The type 'example' has already been defined."); }); }); diff --git a/src/store.js b/src/store.js index 8b5e591..d24e7d2 100644 --- a/src/store.js +++ b/src/store.js @@ -131,7 +131,13 @@ export default class Store { define(names, definition) { names = (names.constructor === Array) ? names : [ names ]; definition._names = names; - names.forEach(name => this._types[name] = definition); + names.forEach(name => { + if (!this._types[name]) { + this._types[name] = definition; + } else { + throw new Error(`The type '${name}' has already been defined.`); + } + }); } /** -- 2.51.2