Add entity data denormalisation helper

This commit is contained in:
Kimmo Puputti 2017-02-23 11:57:12 +02:00
parent 36d40054a6
commit d263202723
2 changed files with 160 additions and 1 deletions

View file

@ -1,3 +1,5 @@
import { reduce } from 'lodash';
/**
* Combine the given relationships objects
*
@ -52,3 +54,49 @@ export const updatedEntities = (oldEntities, apiResponse) => {
return newEntities;
};
/**
* Denormalise the entities with the given IDs from the entities object
*
* This function calculates the dernormalised tree structure from the
* normalised entities object with all the relationships joined in.
*
* @param {Object} entities entities object in the SDK Redux store
* @param {String} type entity type of the given IDs
* @param {Array<UUID>} ids IDs to pick from the entities
*/
export const denormalisedEntities = (entities, type, ids) => {
if (!entities[type] && ids.length > 0) {
throw new Error(`No entities of type ${type}`);
}
return ids.map(id => {
const entity = entities[type][id.uuid];
if (!entity) {
throw new Error(`Entity ${type} with id ${id.uuid} not found`);
}
const { relationships, ...entityData } = entity;
if (relationships) {
// Recursively join in all the relationship entities
return reduce(
relationships,
(ent, relRef, relName) => {
// A relationship reference can be either a single object or
// an array of objects. We want to keep that form in the final
// result.
const hasMultipleRefs = Array.isArray(relRef);
const refs = hasMultipleRefs ? relRef : [relRef];
const relIds = refs.map(ref => ref.data.id);
const relType = refs[0].data.type;
const rels = denormalisedEntities(entities, relType, relIds);
// eslint-disable-next-line no-param-reassign
ent[relName] = hasMultipleRefs ? rels : rels[0];
return ent;
},
entityData,
);
}
return entityData;
});
};

View file

@ -1,5 +1,10 @@
import { types } from 'sharetribe-sdk';
import { combinedRelationships, combinedResourceObjects, updatedEntities } from './data';
import {
combinedRelationships,
combinedResourceObjects,
updatedEntities,
denormalisedEntities,
} from './data';
const { UUID } = types;
@ -162,4 +167,110 @@ describe('data utils', () => {
});
});
});
describe('denormalisedEntities()', () => {
const createListing = id => ({
id: new UUID(id),
type: 'listing',
attributes: { title: `Listing ${id} title`, description: `Listing ${id} description` },
});
const createUser = id => ({
id: new UUID(id),
type: 'user',
attributes: { name: `User ${id} name` },
});
const createImage = id => ({
id: new UUID(id),
type: 'image',
attributes: { url: `https://example.com/${id}.jpg`, width: 300, height: 200 },
});
it('return no results with empty ids', () => {
const entities = { listing: { listing1: createListing('listing1') } };
expect(denormalisedEntities(entities, 'listing', [])).toEqual([]);
});
it('return no results with empty entities and empty ids', () => {
const entities = {};
expect(denormalisedEntities(entities, 'listing', [])).toEqual([]);
});
it('throws when selecting a nonexistent type', () => {
const entities = { listing: { listing1: createListing('listing1') } };
expect(() => denormalisedEntities(entities, 'user', [new UUID('user1')])).toThrow(
'No entities of type user',
);
});
it('throws when selecting a nonexistent id', () => {
const entities = { listing: { listing1: createListing('listing1') } };
const ids = [new UUID('listing2')];
expect(() => denormalisedEntities(entities, 'listing', ids)).toThrow(
'Entity listing with id listing2 not found',
);
});
it('throws if a related entity is not found', () => {
const listing1 = createListing('listing1');
listing1.relationships = { author: { data: createUser('user1') } };
const entities = { listing: { listing1 } };
const ids = [listing1.id];
expect(() => denormalisedEntities(entities, 'listing', ids)).toThrow(
'No entities of type user',
);
});
it('returns the selected entities', () => {
const listing1 = createListing('listing1');
const listing2 = createListing('listing2');
const listing3 = createListing('listing3');
const entities = { listing: { listing1, listing2, listing3 } };
const ids = [listing1.id, listing3.id];
expect(denormalisedEntities(entities, 'listing', ids)).toEqual([listing1, listing3]);
});
it('denormalises simple relationships', () => {
const user1 = createUser('user1');
const listing1 = createListing('listing1');
const listing1Relationships = { author: { data: user1 } };
const listing1WithRelationships = { ...listing1, relationships: listing1Relationships };
const listing2 = createListing('listing2');
const entities = {
listing: { listing1: listing1WithRelationships, listing2 },
user: { user1 },
};
const ids = [listing1.id];
expect(denormalisedEntities(entities, 'listing', ids)).toEqual([
{ ...listing1, author: user1 },
]);
});
it('denormalises multiple relationships', () => {
const user1 = createUser('user1');
const image1 = createImage('image1');
const image2 = createImage('image2');
const listing1 = createListing('listing1');
const listing1Relationships = {
author: { data: user1 },
images: [{ data: image1 }, { data: image2 }],
};
const listing1WithRelationships = { ...listing1, relationships: listing1Relationships };
const listing2 = createListing('listing2');
const listing3 = createListing('listing3');
const entities = {
listing: { listing1: listing1WithRelationships, listing2, listing3 },
user: { user1 },
image: { image1, image2 },
};
const ids = [listing1.id, listing2.id];
expect(denormalisedEntities(entities, 'listing', ids)).toEqual([
{ ...listing1, author: user1, images: [image1, image2] },
listing2,
]);
});
});
});