Skip to content
Permalink
Browse files

fix(repository): support embedded models in nested properties

  • Loading branch information...
IsCaster authored and bajtos committed Feb 28, 2019
1 parent 78a2e79 commit d298ec898f3c52224a1844c5e41f0d52cd7ff569
@@ -65,6 +65,54 @@ describe('Repository in Thinking in LoopBack', () => {
expect(stored).to.containDeep({extra: 'additional data'});
});

it('allows models to allow nested model properties', async () => {
@model()
class Role extends Entity {
@property()
name: String;
}

@model()
class Address extends Entity {
@property()
street: String;
}

@model()
class User extends Entity {
@property({
type: 'number',
id: true,
})
id: number;

@property({type: 'string'})
name: String;

@property.array(Role)
roles: Role[];

@property()
address: Address;
}

const userRepo = new DefaultCrudRepository<User, typeof User.prototype.id>(
User,
new DataSource({connector: 'memory'}),
);

const user = {
id: 1,
name: 'foo',
roles: [{name: 'admin'}, {name: 'user'}],
address: {street: 'backstreet'},
};
const created = await userRepo.create(user);

const stored = await userRepo.findById(created.id);
expect(stored).to.containDeep(user);
});

function givenProductRepository() {
const db = new DataSource({
connector: 'memory',
@@ -10,7 +10,9 @@ import {
Entity,
EntityNotFoundError,
juggler,
model,
ModelDefinition,
property,
} from '../../..';

describe('legacy loopback-datasource-juggler', () => {
@@ -121,6 +123,74 @@ describe('DefaultCrudRepository', () => {
originalPropertyDefinition,
);
});

it('converts PropertyDefinition with model type', () => {
@model()
class Role {
@property()
name: String;
}

@model()
class Address {
@property()
street: String;
}

@model()
class User extends Entity {
@property({
type: 'number',
id: true,
})
id: number;

@property({type: 'string'})
name: String;

@property.array(Role)
roles: Role[];

@property()
address: Address;
}

expect(ds.getModel('User')).undefined();

// tslint:disable-next-line:no-unused-expression
new DefaultCrudRepository(User, ds);

const JugglerUser = ds.getModel('User')!;
expect(JugglerUser).to.be.a.Function();

const addressProperty = JugglerUser.definition.properties.address;
const addressModel = addressProperty.type as typeof juggler.ModelBase;
expect(addressModel).to.be.a.Function();
expect(addressModel).to.equal(ds.getModel('Address'));

expect(addressModel.name).to.equal('Address');
expect(addressModel.definition).to.containDeep({
name: 'Address',
properties: {street: {type: String}},
});

const rolesProperty = JugglerUser.definition.properties.roles;
expect(rolesProperty.type)
.to.be.an.Array()
.of.length(1);

// FIXME(bajtos) PropertyDefinition in juggler does not allow array type!
// tslint:disable-next-line:no-any
const rolesModel = (rolesProperty.type as any)[0] as typeof juggler.ModelBase;
expect(rolesModel).to.be.a.Function();
expect(rolesModel).to.equal(ds.getModel('Role'));

expect(rolesModel.name).to.equal('Role');
expect(rolesModel.definition).to.containDeep({
name: 'Role',
properties: {name: {type: String}},
});
});
});

it('shares the backing PersistedModel across repo instances', () => {
@@ -16,20 +16,20 @@ import {
PositionalParameters,
} from '../common-types';
import {EntityNotFoundError} from '../errors';
import {Entity, ModelDefinition} from '../model';
import {Entity, Model, PropertyType} from '../model';
import {Filter, Where} from '../query';
import {
BelongsToDefinition,
HasManyDefinition,
HasManyRepositoryFactory,
createHasManyRepositoryFactory,
BelongsToAccessor,
BelongsToDefinition,
createBelongsToAccessor,
createHasManyRepositoryFactory,
createHasOneRepositoryFactory,
HasManyDefinition,
HasManyRepositoryFactory,
HasOneDefinition,
HasOneRepositoryFactory,
} from '../relations';
import {resolveType} from '../type-resolver';
import {isTypeResolver, resolveType} from '../type-resolver';
import {EntityCrudRepository} from './repository';

export namespace juggler {
@@ -41,6 +41,17 @@ export namespace juggler {
export import PersistedModelClass = legacy.PersistedModelClass;
}

function isModelClass(
propertyType: PropertyType | undefined,
): propertyType is typeof Model {
return (
!isTypeResolver(propertyType) &&
typeof propertyType === 'function' &&
typeof (propertyType as typeof Model).definition === 'object' &&
propertyType.toString().startsWith('class ')
);
}

/**
* This is a bridge to the legacy DAO class. The function mixes DAO methods
* into a model class and attach it to a given data source
@@ -96,24 +107,30 @@ export class DefaultCrudRepository<T extends Entity, ID>
!!definition,
`Entity ${entityClass.name} must have valid model definition.`,
);

assert(
definition.idProperties().length > 0,
`Entity ${entityClass.name} must have at least one id/pk property.`,
);

this.setupPersistedModel(definition);
this.modelClass = this.definePersistedModel(entityClass);
}

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

const dataSource = this.dataSource;

const model = dataSource.getModel(definition.name);
if (model) {
// The backing persisted model has been already defined.
this.modelClass = model as typeof juggler.PersistedModel;
return;
return model as typeof juggler.PersistedModel;
}

// We need to convert property definitions from PropertyDefinition
@@ -124,14 +141,15 @@ export class DefaultCrudRepository<T extends Entity, ID>
// the juggler understands
Object.entries(definition.properties).forEach(([key, value]) => {
if (value.type === 'array' || value.type === Array) {
value = Object.assign({}, value, {type: [resolveType(value.itemType)]});
value = Object.assign({}, value, {
type: [value.itemType && this.resolvePropertyType(value.itemType)],
});
delete value.itemType;
}
value.type = resolveType(value.type);
value.type = this.resolvePropertyType(value.type);
properties[key] = Object.assign({}, value);
});

this.modelClass = dataSource.createModel<juggler.PersistedModelClass>(
const modelClass = dataSource.createModel<juggler.PersistedModelClass>(
definition.name,
properties,
Object.assign(
@@ -143,14 +161,22 @@ export class DefaultCrudRepository<T extends Entity, ID>
{strictDelete: false},
),
);
this.modelClass.attachTo(dataSource);
modelClass.attachTo(dataSource);
return modelClass;
}

private resolvePropertyType(type: PropertyType): PropertyType {
const resolved = resolveType(type);
return isModelClass(resolved)
? this.definePersistedModel(resolved)
: resolved;
}

/**
* @deprecated
* Function to create a constrained relation repository factory
*
* Use `this.createHasManyRepositoryFactoryFor()` instaed
* Use `this.createHasManyRepositoryFactoryFor()` instead
*
* @param relationName Name of the relation defined on the source model
* @param targetRepo Target repository instance
@@ -214,7 +240,7 @@ export class DefaultCrudRepository<T extends Entity, ID>
* @deprecated
* Function to create a belongs to accessor
*
* Use `this.createBelongsToAccessorFor()` instaed
* Use `this.createBelongsToAccessorFor()` instead
*
* @param relationName Name of the relation defined on the source model
* @param targetRepo Target repository instance

0 comments on commit d298ec8

Please sign in to comment.
You can’t perform that action at this time.