Skip to content

Commit

Permalink
fix(repository): allow model classes with recursive type references
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Sep 12, 2019
1 parent 31dd7bf commit 14ac1ee
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 18 deletions.
Expand Up @@ -210,6 +210,35 @@ describe('DefaultCrudRepository', () => {
expect(User.definition.properties.roles.itemType).to.equal(Role);
expect(User.definition.properties.address.type).to.equal(Address);
});

it('handles recursive model references', () => {
@model()
class ReportState extends Entity {
@property({id: true})
id: string;

@property.array(ReportState, {})
states: ReportState[];

@property({
type: 'string',
})
benchmarkId?: string;

@property({
type: 'string',
})
color?: string;

constructor(data?: Partial<ReportState>) {
super(data);
}
}
const repo = new DefaultCrudRepository(ReportState, ds);
const definition = repo.modelClass.definition;
const typeOfStates = definition.properties.states.type;
expect(typeOfStates).to.eql([repo.modelClass]);
});
});

it('shares the backing PersistedModel across repo instances', () => {
Expand Down
61 changes: 43 additions & 18 deletions packages/repository/src/repositories/legacy-juggler-bridge.ts
Expand Up @@ -127,68 +127,93 @@ export class DefaultCrudRepository<
`Entity ${entityClass.name} must have at least one id/pk property.`,
);

this.modelClass = this.definePersistedModel(entityClass);
this.modelClass = this.definePersistedModel(entityClass, new Map());
}

// Create an internal legacy Model attached to the datasource
private definePersistedModel(
entityClass: typeof Model,
visited: Map<typeof Model, typeof juggler.PersistedModel>,
): typeof juggler.PersistedModel {
const definition = entityClass.definition;
assert(
!!definition,
`Entity ${entityClass.name} must have valid model definition.`,
);

let resolved = visited.get(entityClass);
if (resolved) {
return resolved;
}

const dataSource = this.dataSource;

const model = dataSource.getModel(definition.name);
if (model) {
// The backing persisted model has been already defined.
return model as typeof juggler.PersistedModel;
resolved = model as typeof juggler.PersistedModel;
visited.set(entityClass, resolved);
return resolved;
}

// We need to convert property definitions from PropertyDefinition
// to plain data object because of a juggler limitation
const properties: {[name: string]: object} = {};

const modelClass = dataSource.createModel<juggler.PersistedModelClass>(
definition.name,
definition.properties,
Object.assign(
// settings that users can override
{strict: true},
// user-defined settings
definition.settings,
// settings enforced by the framework
{strictDelete: false},
),
);

// Cache the result to allow recursive refs
visited.set(entityClass, modelClass);

// We need to convert PropertyDefinition into the definition that
// the juggler understands
Object.entries(definition.properties).forEach(([key, value]) => {
// always clone value so that we do not modify the original model definition
// ensures that model definitions can be reused with multiple datasources
if (value.type === 'array' || value.type === Array) {
value = Object.assign({}, value, {
type: [value.itemType && this.resolvePropertyType(value.itemType)],
type: [
value.itemType && this.resolvePropertyType(value.itemType, visited),
],
});
delete value.itemType;
} else {
value = Object.assign({}, value, {
type: this.resolvePropertyType(value.type),
type: this.resolvePropertyType(value.type, visited),
});
}
properties[key] = Object.assign({}, value);
});
const modelClass = dataSource.createModel<juggler.PersistedModelClass>(
definition.name,
properties,
Object.assign(
// settings that users can override
{strict: true},
// user-defined settings
definition.settings,
// settings enforced by the framework
{strictDelete: false},
),
);

// Now the property types have been fully resolved
// Force the model to be rebuilt
modelClass.definition.rawProperties = properties;
delete modelClass.definition.properties;

// Force rebuild definitions
modelClass.definition.build();
modelClass.attachTo(dataSource);
return modelClass;
}

private resolvePropertyType(type: PropertyType): PropertyType {
private resolvePropertyType(
type: PropertyType,
visited: Map<typeof Model, typeof juggler.PersistedModel>,
): PropertyType {
const resolved = resolveType(type);
return isModelClass(resolved)
? this.definePersistedModel(resolved)
? this.definePersistedModel(resolved, visited)
: resolved;
}

Expand Down

0 comments on commit 14ac1ee

Please sign in to comment.