Permalink
Browse files

feat(Hydrator): add nested hydration for collections

  • Loading branch information...
RWOverdijk committed Jan 4, 2017
1 parent 6624079 commit e7024545f92c24fde8fb237b4ea02a202391b273
Showing with 144 additions and 15 deletions.
  1. +96 −7 src/Hydrator.ts
  2. +48 −8 src/Scope.ts
@@ -6,9 +6,6 @@ import {UnitOfWork} from './UnitOfWork';
import {EntityInterface, ProxyInterface} from './EntityInterface';
import {Scope, Entity} from './Scope';
/**
* Hydrate results into entities.
*/
export class Hydrator {
/**
* A flat object maintaining a mapping between aliases and recipes.
@@ -29,7 +26,7 @@ export class Hydrator {
*
* @type {IdentityMap}
*/
private identityMap: IdentityMap = new IdentityMap;
private identityMap: IdentityMap;
/**
* Reference to the unit of work.
@@ -53,6 +50,7 @@ export class Hydrator {
public constructor(entityManager: Scope) {
this.unitOfWork = entityManager.getUnitOfWork();
this.entityManager = entityManager;
this.identityMap = entityManager.getIdentityMap();
}
/**
@@ -111,11 +109,13 @@ export class Hydrator {
let primaryKeyAliased = `${alias}.${primaryKey}`;
let recipe = {
hydrate : false,
parent : null,
entity : mapping.getTarget(),
primaryKey: {alias: primaryKeyAliased, property: primaryKey},
property : property,
type : joinType,
columns : {},
property,
alias,
};
this.recipeIndex[alias] = recipe;
@@ -189,6 +189,11 @@ export class Hydrator {
}
}
if (recipe.parent) {
// Assign self to parent (only for many).
recipe.parent.entities[row[recipe.parent.column]][recipe.parent.property].add({_skipDirty: entity});
}
if (recipe.joins) {
this.hydrateJoins(recipe, row, entity);
}
@@ -198,6 +203,21 @@ export class Hydrator {
return entity;
}
/**
* Clear a catalogue for `alias`.
*
* @param {string} [alias]
*
* @returns {Hydrator}
*/
public clearCatalogue(alias?: string): this {
let recipe = this.getRecipe(alias);
delete recipe.catalogue;
return this;
}
/**
* Hydrate the joins for a recipe.
*
@@ -210,6 +230,10 @@ export class Hydrator {
let joinRecipe = recipe.joins[alias];
let hydrated = this.hydrate(row, joinRecipe);
if (!joinRecipe.hydrate) {
return;
}
if (joinRecipe.type === 'single') {
entity[joinRecipe.property] = {_skipDirty: hydrated};
@@ -225,6 +249,60 @@ export class Hydrator {
});
}
/**
* Add entity to catalogue.
*
* @param {Recipe} recipe
* @param {ProxyInterface} entity
*
* @returns {Hydrator}
*/
private addToCatalogue(recipe: Recipe, entity: ProxyInterface): this {
let primary = entity[recipe.primaryKey.property];
let catalogue = this.getCatalogue(recipe.alias);
catalogue.entities[primary] = entity;
catalogue.primaries.add(primary);
return this;
}
/**
* Enable catalogue for alias.
*
* @param {string} alias
*
* @returns {Catalogue}
*/
public enableCatalogue(alias: string): Catalogue {
return this.getCatalogue(alias);
}
/**
* Check if catalogue exists for alias.
*
* @param {string} alias
*
* @returns {boolean}
*/
public hasCatalogue(alias: string): boolean {
return !!this.getRecipe(alias).catalogue;
}
/**
* Get the catalogue for alias.
*
* @param {string} alias
*
* @returns {Catalogue}
*/
public getCatalogue(alias: string): Catalogue {
let recipe = this.getRecipe(alias);
recipe.catalogue = recipe.catalogue || {entities: {}, primaries: new ArrayCollection()};
return recipe.catalogue;
}
/**
* Apply mapping to a new entity.
*
@@ -242,8 +320,7 @@ export class Hydrator {
entity[recipe.primaryKey.property] = row[recipe.primaryKey.alias];
Object.getOwnPropertyNames(recipe.columns).forEach(alias => {
let property = recipe.columns[alias];
entity[property] = row[alias];
entity[recipe.columns[alias]] = row[alias];
});
this.unitOfWork.registerClean(entity, true);
@@ -252,15 +329,27 @@ export class Hydrator {
this.identityMap.register(entity, patched);
if (recipe.catalogue) {
this.addToCatalogue(recipe, patched);
}
return patched;
}
}
export interface Catalogue {
entities: Object,
primaries: ArrayCollection<string|number>
}
export interface Recipe {
hydrate: boolean,
alias: string,
entity: {new ()},
primaryKey: {alias: string, property: string},
columns: {},
catalogue?: Catalogue,
parent?: {property: string, column: string, entities: Object},
joins?: {[key: string]: Recipe},
property?: string,
type?: string,
@@ -8,6 +8,8 @@ import {Wetland} from './Wetland';
import {Hydrator} from './Hydrator';
import {Homefront} from 'homefront';
import {EntityProxy} from './EntityProxy';
import {ArrayCollection} from './ArrayCollection';
import {IdentityMap} from './IdentityMap';
export class Scope {
@@ -26,6 +28,13 @@ export class Scope {
*/
private wetland: Wetland;
/**
* Maintain list of hydrated entities.
*
* @type {IdentityMap}
*/
private identityMap: IdentityMap = new IdentityMap;
/**
* Construct a new Scope.
*
@@ -38,6 +47,15 @@ export class Scope {
this.unitOfWork = new UnitOfWork(this);
}
/**
* Get the identity map for this scope.
*
* @returns {IdentityMap}
*/
public getIdentityMap(): IdentityMap {
return this.identityMap;
}
/**
* Proxy method the entityManager getRepository method.
*
@@ -62,22 +80,32 @@ export class Scope {
}
/**
* Get a reference to a persisted row without actually loading it.
* Get a reference to a persisted row without actually loading it. Returns entity from identity map when available.
*
* @param {Entity} entity
* @param {*} primaryKeyValue
* @param {Entity} entity
* @param {*} primaryKeyValue
* @param {boolean} proxy Whether or not to proxy the reference (if used for updates for instance).
*
* @returns {EntityInterface}
*/
public getReference(entity: Entity, primaryKeyValue: any): EntityInterface {
let ReferenceClass = this.resolveEntityReference(entity);
public getReference(entity: Entity, primaryKeyValue: any, proxy: boolean = true): ProxyInterface {
let ReferenceClass = this.resolveEntityReference(entity);
let fromMap = this.identityMap.fetch(ReferenceClass, primaryKeyValue);
if (fromMap) {
return fromMap;
}
let reference = new ReferenceClass;
let primaryKey = Mapping.forEntity(ReferenceClass).getPrimaryKey();
reference[primaryKey] = primaryKeyValue;
// Not super important, but it's a nice-to-have to prevent mutations on the reference as it is stricter.
this.unitOfWork.registerClean(reference);
if (proxy) {
return this.attach(reference);
}
return reference;
}
@@ -120,6 +148,17 @@ export class Scope {
return Promise.all(refreshes);
}
/**
* Get the mapping for provided entity. Can be an instance, constructor or the name of the entity.
*
* @param {EntityInterface|string|{}} entity
*
* @returns {Mapping}
*/
public getMapping<T>(entity: T): Mapping<T> {
return this.manager.getMapping(entity);
}
/**
* Get the reference to an entity constructor by name.
*
@@ -172,11 +211,12 @@ export class Scope {
* Attach an entity (proxy it).
*
* @param {EntityInterface} entity
* @param {boolean} active
*
* @returns {EntityInterface&ProxyInterface}
*/
public attach<T>(entity: T): T {
return EntityProxy.patchEntity(entity, this);
public attach<T>(entity: T, active: boolean = false): T {
return EntityProxy.patchEntity(entity, this, active);
}
/**

0 comments on commit e702454

Please sign in to comment.