Skip to content

Commit

Permalink
feat(core): do not require entity attribute in collection decorators (#…
Browse files Browse the repository at this point in the history
…207)

Now the type from `prop: Collection<MyEntity>` is sniffed via reflection, so you do not have to define `entity` or `type`
attributes in collection decorators (@manytomany and @OneToMany).
  • Loading branch information
B4nan committed Oct 17, 2019
1 parent f7eaabb commit 89bbeb0
Show file tree
Hide file tree
Showing 14 changed files with 30 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -147,7 +147,7 @@ export class MongoBook implements MongoEntity<MongoBook> {
@ManyToOne()
author: Author;

@ManyToMany(() => BookTag, tag => tag.books, { owner: true })
@ManyToMany()
tags = new Collection<BookTag>(this);

constructor(title: string, author: Author) {
Expand Down
5 changes: 4 additions & 1 deletion docs/defining-entities.md
Expand Up @@ -32,7 +32,7 @@ export class Book implements IdEntity<Book> {
@ManyToOne(() => Publisher) // or you can specify the entity as class reference or string name
publisher?: Publisher;

@ManyToMany(() => BookTag, tag => tag.books, { owner: true })
@ManyToMany() // owning side can be simple as this!
tags = new Collection<BookTag>(this);

constructor(title: string, author: Author) {
Expand Down Expand Up @@ -96,6 +96,9 @@ export class Author implements MongoEntity<Author> {
@OneToMany(() => Book, book => book.author)
books = new Collection<Book>(this);

@ManyToMany()
friends = new Collection<Author>(this);

@ManyToOne()
favouriteBook: Book;

Expand Down
4 changes: 2 additions & 2 deletions docs/relationships.md
Expand Up @@ -62,7 +62,7 @@ export class Author implements IdEntity<Author> {
@OneToMany('Book', 'author')
books2 = new Collection<Book>(this);

@OneToMany({ entity: () => Book, mappedBy: book => book.author })
@OneToMany({ mappedBy: book => book.author }) // referenced entity type can be sniffer too
books3 = new Collection<Book>(this);

@OneToMany({ entity: () => Book, mappedBy: 'author', orphanRemoval: true })
Expand Down Expand Up @@ -137,7 +137,7 @@ Here are examples of how you can define ManyToMany relationship:
export class Book implements IdEntity<Book> {

// when none of `owner/inverseBy/mappedBy` is provided, it will be considered owning side
@ManyToMany(() => BookTag)
@ManyToMany()
tags1 = new Collection<BookTag>(this);

@ManyToMany(() => BookTag, 'books', { owner: true })
Expand Down
9 changes: 2 additions & 7 deletions lib/decorators/ManyToMany.ts
Expand Up @@ -6,7 +6,7 @@ import { EntityName, EntityProperty, AnyEntity } from '../types';
import { QueryOrder } from '../query';

export function ManyToMany<T extends AnyEntity<T>>(
entity: ManyToManyOptions<T> | string | (() => EntityName<T>),
entity?: ManyToManyOptions<T> | string | (() => EntityName<T>),
mappedBy?: (string & keyof T) | ((e: T) => any),
options: Partial<ManyToManyOptions<T>> = {},
) {
Expand All @@ -24,18 +24,13 @@ export function ManyToMany<T extends AnyEntity<T>>(

const meta = MetadataStorage.getMetadata(target.constructor.name);
Utils.lookupPathFromDecorator(meta);

if (!options.entity) {
throw new Error(`'@ManyToMany({ entity: string | Function })' is required in '${target.constructor.name}.${propertyName}'`);
}

const property = { name: propertyName, reference: ReferenceType.MANY_TO_MANY, owner: !!options.inversedBy, cascade: [Cascade.PERSIST, Cascade.MERGE] } as EntityProperty<T>;
meta.properties[propertyName] = Object.assign(property, options);
};
}

export interface ManyToManyOptions<T extends AnyEntity<T>> extends ReferenceOptions<T> {
entity: string | (() => EntityName<T>);
entity?: string | (() => EntityName<T>);
owner?: boolean;
inversedBy?: (string & keyof T) | ((e: T) => any);
mappedBy?: (string & keyof T) | ((e: T) => any);
Expand Down
6 changes: 1 addition & 5 deletions lib/decorators/OneToMany.ts
Expand Up @@ -25,10 +25,6 @@ export function createOneToDecorator<T extends AnyEntity<T>>(
Utils.lookupPathFromDecorator(meta);

if (reference === ReferenceType.ONE_TO_MANY) {
if (!options.entity) {
throw new Error(`'@OneToMany({ entity: string | Function })' is required in '${target.constructor.name}.${propertyName}'`);
}

if ((options as any).fk) {
throw new Error(`@OneToMany({ fk })' is deprecated, use 'mappedBy' instead in '${target.constructor.name}.${propertyName}'`);
}
Expand Down Expand Up @@ -56,7 +52,7 @@ export function createOneToDecorator<T extends AnyEntity<T>>(
}

export type OneToManyOptions<T extends AnyEntity<T>> = ReferenceOptions<T> & {
entity: string | (() => EntityName<T>);
entity?: string | (() => EntityName<T>);
orphanRemoval?: boolean;
orderBy?: { [field: string]: QueryOrder };
joinColumn?: string;
Expand Down
16 changes: 11 additions & 5 deletions lib/metadata/TypeScriptMetadataProvider.ts
Expand Up @@ -44,7 +44,8 @@ export class TypeScriptMetadataProvider extends MetadataProvider {
prop.nullable = true;
}

this.processReferenceWrapper(prop);
this.processWrapper(prop, 'IdentifiedReference');
this.processWrapper(prop, 'Collection');
}

private async readTypeFromSource(file: string, name: string, prop: EntityProperty): Promise<{ type: string; optional?: boolean }> {
Expand All @@ -71,11 +72,16 @@ export class TypeScriptMetadataProvider extends MetadataProvider {
return source;
}

private processReferenceWrapper(prop: EntityProperty): void {
const m = prop.type.match(/^IdentifiedReference<(\w+),?.*>$/);
private processWrapper(prop: EntityProperty, wrapper: string): void {
const m = prop.type.match(new RegExp(`^${wrapper}<(\\w+),?.*>$`));

if (m) {
prop.type = m[1];
if (!m) {
return;
}

prop.type = m[1];

if (wrapper === 'IdentifiedReference') {
prop.wrappedReference = true;
}
}
Expand Down
3 changes: 0 additions & 3 deletions tests/decorators.test.ts
Expand Up @@ -16,8 +16,6 @@ class Test6 implements AnyEntity<Test6> {}
describe('decorators', () => {

test('ManyToMany', () => {
expect(() => ManyToMany({} as any)(new Test(), 'test')).toThrowError(`@ManyToMany({ entity: string | Function })' is required in 'Test.test`);

const storage = MetadataStorage.getMetadata();
ManyToMany({ entity: () => Test })(new Test2(), 'test0');
expect(storage.Test2.properties.test0).toMatchObject({
Expand Down Expand Up @@ -61,7 +59,6 @@ describe('decorators', () => {
});

test('OneToMany', () => {
expect(() => OneToMany({} as any)(new Test(), 'test')).toThrowError(`@OneToMany({ entity: string | Function })' is required in 'Test.test`);
expect(() => OneToMany({ entity: () => Test, fk: 'test' } as any)(new Test(), 'test')).toThrowError(`@OneToMany({ fk })' is deprecated, use 'mappedBy' instead in 'Test.test`);

const storage = MetadataStorage.getMetadata();
Expand Down
2 changes: 1 addition & 1 deletion tests/entities-sql/Author2.ts
Expand Up @@ -36,7 +36,7 @@ export class Author2 extends BaseEntity2 {
@Property({ length: 0 })
born?: Date;

@OneToMany('Book2', 'author', { orderBy: { title: QueryOrder.ASC } })
@OneToMany({ mappedBy: 'author', orderBy: { title: QueryOrder.ASC } })
books!: Collection<Book2>;

@ManyToOne()
Expand Down
2 changes: 1 addition & 1 deletion tests/entities-sql/FooBaz2.ts
Expand Up @@ -10,7 +10,7 @@ export class FooBaz2 implements IdEntity<FooBaz2> {
@Property()
name: string;

@OneToOne(() => FooBar2, 'baz')
@OneToOne({ mappedBy: 'baz' })
bar?: FooBar2;

@Property({ version: true })
Expand Down
4 changes: 2 additions & 2 deletions tests/entities-sql/Publisher2.ts
Expand Up @@ -9,10 +9,10 @@ export class Publisher2 extends BaseEntity2 {
@Property()
name: string;

@OneToMany({ entity: () => Book2, mappedBy: 'publisher' })
@OneToMany({ mappedBy: 'publisher' })
books!: Collection<Book2>;

@ManyToMany({ entity: () => Test2, fixedOrder: true })
@ManyToMany({ fixedOrder: true })
tests!: Collection<Test2>;

@Property({ type: 'string', length: 10 })
Expand Down
2 changes: 1 addition & 1 deletion tests/entities-sql/Test2.ts
Expand Up @@ -10,7 +10,7 @@ export class Test2 implements IdEntity<Test2> {
@Property()
name?: string;

@OneToOne({ cascade: [], inversedBy: 'test' })
@OneToOne({ entity: 'Book2', cascade: [], inversedBy: 'test' })
book?: Book2;

@Property({ version: true })
Expand Down
2 changes: 1 addition & 1 deletion tests/entities/Author.ts
Expand Up @@ -34,7 +34,7 @@ export class Author extends BaseEntity {
@OneToMany(() => Book, book => book.author, { referenceColumnName: '_id', cascade: [Cascade.PERSIST], orphanRemoval: true })
books = new Collection<Book>(this);

@ManyToMany(() => Author)
@ManyToMany()
friends: Collection<Author> = new Collection<Author>(this);

@ManyToOne()
Expand Down
2 changes: 1 addition & 1 deletion tests/entities/Book.ts
Expand Up @@ -30,7 +30,7 @@ export class Book extends BaseEntity3 {
@ManyToOne({ cascade: [Cascade.PERSIST, Cascade.REMOVE] })
publisher!: IdentifiedReference<Publisher, '_id' | 'id'>;

@ManyToMany(() => BookTag)
@ManyToMany()
tags = new Collection<BookTag>(this);

@Property()
Expand Down
4 changes: 2 additions & 2 deletions tests/entities/Publisher.ts
Expand Up @@ -16,10 +16,10 @@ export class Publisher implements MongoEntity<Publisher> {
@Property()
name: string;

@OneToMany({ entity: () => Book.name, mappedBy: 'publisher' })
@OneToMany({ mappedBy: 'publisher' })
books = new Collection<Book>(this);

@ManyToMany({ entity: () => Test.name, owner: true, eager: true })
@ManyToMany({ eager: true })
tests = new Collection<Test>(this);

@Property()
Expand Down

0 comments on commit 89bbeb0

Please sign in to comment.