MORPH for relationDecorator (#15420)

# Adding MORPH support  to the relationDecorator

Extends the @WorkspaceRelation decorator to support morph relations by
adding isMorphRelation and morphId options

The implementation enforces type safety through TypeScript discriminated
unions, ensuring morphId is required when isMorphRelation: true, and
includes runtime validation that throws a descriptive error if morphId
is missing.

The morph relation metadata is properly stored in metadataArgsStorage
and integrates seamlessly with the existing StandardFieldRelationFactory
to convert decorated fields into FieldMetadataType.MORPH_RELATION
entities with appropriate database constraints.



To test :
in person entity, add
```ts
  @WorkspaceRelation({
    standardId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordCompany,
    type: RelationType.MANY_TO_ONE,
    label: msg`Test Related Record of Company`,
    description: msg`Test relation to company`,
    icon: 'IconLink',
    inverseSideTarget: () => CompanyWorkspaceEntity,
    inverseSideFieldKey: 'testRelatedPeople',
    onDelete: RelationOnDeleteAction.CASCADE,
    isMorphRelation: true,
    morphId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordMorphId,
  })
  @WorkspaceIsNullable()
  @WorkspaceIsSystem()
  testRelatedRecordCompany: Relation<CompanyWorkspaceEntity> | null;

  @WorkspaceJoinColumn('testRelatedRecordCompany')
  testRelatedRecordCompanyId: string | null;

  @WorkspaceRelation({
    standardId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordOpportunity,
    type: RelationType.MANY_TO_ONE,
    label: msg`Test Related Record of Opportunity`,
    description: msg`Test relation to opportunity`,
    icon: 'IconLink',
    inverseSideTarget: () => OpportunityWorkspaceEntity,
    inverseSideFieldKey: 'testRelatedPeople',
    onDelete: RelationOnDeleteAction.CASCADE,
    isMorphRelation: true,
    morphId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordMorphId,
  })
  @WorkspaceIsNullable()
  @WorkspaceIsSystem()
  testRelatedRecordOpportunity: Relation<OpportunityWorkspaceEntity> | null;

  @WorkspaceJoinColumn('testRelatedRecordOpportunity')
  testRelatedRecordOpportunityId: string | null;
```

in opportunity,
```ts
@WorkspaceRelation({
    standardId: OPPORTUNITY_STANDARD_FIELD_IDS.testRelatedPeople,
    type: RelationType.ONE_TO_MANY,
    label: msg`Test Related People`,
    description: msg`People linked to the opportunity via Test Related Record`,
    icon: 'IconUsers',
    inverseSideTarget: () => PersonWorkspaceEntity,
    inverseSideFieldKey: 'testRelatedRecordOpportunity',
    onDelete: RelationOnDeleteAction.SET_NULL,
  })
  @WorkspaceIsNullable()
  @WorkspaceIsSystem()
  testRelatedPeople: Relation<PersonWorkspaceEntity[]>;
  ```

in company,
```ts
  @WorkspaceRelation({
    standardId: COMPANY_STANDARD_FIELD_IDS.testRelatedPeople,
    type: RelationType.ONE_TO_MANY,
    label: msg`Test Related People`,
description: msg`People linked to the company via Test Related Record`,
    icon: 'IconUsers',
    inverseSideTarget: () => PersonWorkspaceEntity,
    inverseSideFieldKey: 'testRelatedRecordCompany',
    onDelete: RelationOnDeleteAction.SET_NULL,
  })
  @WorkspaceIsNullable()
  @WorkspaceIsSystem()
  testRelatedPeople: Relation<PersonWorkspaceEntity[]>;
```

In constants/standard-field-ids.ts, add the standard fields ids
```ts
// COMPANY_STANDARD_FIELD_IDS
testRelatedPeople: '20202020-7a90-47e9-b31f-916a716fd212',

// OPPORTUNITY_STANDARD_FIELD_IDS
testRelatedPeople: '20202020-7a90-47e9-b31f-916a716fd213',

// PERSON_STANDARD_FIELD_IDS
testRelatedRecordOpportunity: '20202020-5a90-47e9-b31f-916a716fd211',
testRelatedRecordCompany: '20202020-1eb2-4298-910b-66b015b36d72',
testRelatedRecordMorphId: '20202020-6ccf-48e3-bb92-bc9961bc011e',
```
This commit is contained in:
Guillim
2025-11-02 13:31:48 +01:00
committed by GitHub
parent 498d89633c
commit 53bb534af8
11 changed files with 190 additions and 47 deletions
@@ -1,4 +1,6 @@
import { type MessageDescriptor } from '@lingui/core';
import { isDefined, isUUID } from 'class-validator';
import { CustomError } from 'twenty-shared/utils';
import { type ObjectType } from 'typeorm';
import { type RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
@@ -9,7 +11,7 @@ import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
import { TypedReflect } from 'src/utils/typed-reflect';
interface WorkspaceRelationBaseOptions<TClass> {
interface WorkspaceRelationMinimumBaseOptions<TClass> {
standardId: string;
label: MessageDescriptor;
description?:
@@ -21,17 +23,32 @@ interface WorkspaceRelationBaseOptions<TClass> {
onDelete?: RelationOnDeleteAction;
}
interface WorkspaceOtherRelationOptions<TClass>
extends WorkspaceRelationBaseOptions<TClass> {
type: RelationType.ONE_TO_MANY;
interface WorkspaceRegularRelationBaseOptions<TClass>
extends WorkspaceRelationMinimumBaseOptions<TClass> {
isMorphRelation?: false;
}
interface WorkspaceManyToOneRelationOptions<TClass extends object>
extends WorkspaceRelationBaseOptions<TClass> {
type: RelationType.MANY_TO_ONE;
inverseSideFieldKey: keyof TClass;
interface WorkspaceMorphRelationBaseOptions<TClass>
extends WorkspaceRelationMinimumBaseOptions<TClass> {
isMorphRelation: true;
morphId: string;
}
type WorkspaceRelationBaseOptions<TClass> =
| WorkspaceRegularRelationBaseOptions<TClass>
| WorkspaceMorphRelationBaseOptions<TClass>;
type WorkspaceOtherRelationOptions<TClass> =
WorkspaceRelationBaseOptions<TClass> & {
type: RelationType.ONE_TO_MANY;
};
type WorkspaceManyToOneRelationOptions<TClass extends object> =
WorkspaceRelationBaseOptions<TClass> & {
type: RelationType.MANY_TO_ONE;
inverseSideFieldKey: keyof TClass;
};
type WorkspaceRelationOptions<TClass extends object> =
| WorkspaceOtherRelationOptions<TClass>
| WorkspaceManyToOneRelationOptions<TClass>;
@@ -73,6 +90,20 @@ export function WorkspaceRelation<TClass extends object>(
const label = options.label.message ?? '';
const isLabelSyncedWithName = computeMetadataNameFromLabel(label) === name;
if (options.isMorphRelation && !isDefined(options.morphId)) {
throw new CustomError(
'Morph relation must have a morph id',
'MORPH_RELATION_MUST_HAVE_MORPH_ID',
);
}
if (options.isMorphRelation && !isUUID(options.morphId)) {
throw new CustomError(
'Morph ID must be a valid UUID',
'INVALID_MORPH_ID_FORMAT',
);
}
metadataArgsStorage.addRelations({
target: object.constructor,
standardId: options.standardId,
@@ -97,6 +128,8 @@ export function WorkspaceRelation<TClass extends object>(
isSystem,
isUIReadOnly,
gate,
isMorphRelation: options.isMorphRelation ?? false,
morphId: options.isMorphRelation ? options.morphId : undefined,
isLabelSyncedWithName,
});
};
@@ -91,4 +91,14 @@ export interface WorkspaceRelationMetadataArgs {
readonly isActive?: boolean;
readonly isLabelSyncedWithName: boolean;
/**
* Is morph relation.
*/
readonly isMorphRelation: boolean;
/**
* Morph id.
*/
readonly morphId?: string;
}
@@ -57,6 +57,10 @@ export const createIndexMigration = async (
isFieldMetadataEntityOfType(
fieldMetadata,
FieldMetadataType.RELATION,
) ||
isFieldMetadataEntityOfType(
fieldMetadata,
FieldMetadataType.MORPH_RELATION,
)
) {
if (!fieldMetadata.settings) {
@@ -26,7 +26,9 @@ export class WorkspaceMigrationFieldRelationFactory {
async create(
originalObjectMetadataCollection: ObjectMetadataEntity[],
fieldMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
fieldMetadataCollection: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[],
action:
| WorkspaceMigrationBuilderAction.CREATE
| WorkspaceMigrationBuilderAction.DELETE,
@@ -34,7 +36,9 @@ export class WorkspaceMigrationFieldRelationFactory {
async create(
originalObjectMetadataCollection: ObjectMetadataEntity[],
fieldMetadataUpdateCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[],
fieldMetadataUpdateCollection: FieldMetadataUpdate<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[],
action: WorkspaceMigrationBuilderAction.UPDATE,
): Promise<Partial<WorkspaceMigrationEntity>[]>;
@@ -44,8 +48,12 @@ export class WorkspaceMigrationFieldRelationFactory {
async create(
originalObjectMetadataCollection: ObjectMetadataEntity[],
fieldMetadataCollectionOrFieldMetadataUpdateCollection:
| FieldMetadataEntity<FieldMetadataType.RELATION>[]
| FieldMetadataUpdate<FieldMetadataType.RELATION>[],
| FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[]
| FieldMetadataUpdate<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[],
action: WorkspaceMigrationBuilderAction,
): Promise<Partial<WorkspaceMigrationEntity>[]> {
const originalObjectMetadataMap = originalObjectMetadataCollection.reduce(
@@ -189,10 +189,12 @@ export class WorkspaceMigrationRunnerService {
case WorkspaceMigrationTableActionType.ALTER_INDEXES:
if (tableMigration.indexes && tableMigration.indexes.length > 0) {
const tableName = tableMigration.newName ?? tableMigration.name;
await this.handleIndexesChanges(
queryRunner,
schemaName,
tableMigration.newName ?? tableMigration.name,
tableName,
tableMigration.indexes,
);
}
@@ -25,13 +25,21 @@ export class WorkspaceFieldRelationComparator {
constructor() {}
public compare(
originalFieldMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
standardFieldMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
originalFieldMetadataCollection: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[],
standardFieldMetadataCollection: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[],
): FieldRelationComparatorResult[] {
const result: FieldRelationComparatorResult[] = [];
const propertiesMap: Record<
string,
Partial<FieldMetadataEntity<FieldMetadataType.RELATION>>
Partial<
FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
>
> = {};
// Double security to only compare non-custom fields
@@ -103,7 +111,9 @@ export class WorkspaceFieldRelationComparator {
fieldMetadataDifferenceMap,
)) {
const findField = (
field: FieldMetadataEntity<FieldMetadataType.RELATION>,
field: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>,
) => {
return field.standardId === fieldId;
};
@@ -211,6 +221,8 @@ export class WorkspaceFieldRelationComparator {
standardId: standardFieldMetadata.standardId ?? undefined,
description: relationFieldMetadata.description ?? undefined,
icon: relationFieldMetadata.icon ?? undefined,
type: standardFieldMetadata.type,
morphId: standardFieldMetadata.morphId ?? undefined,
},
});
} else if (allOldPropertiesAreNull) {
@@ -222,6 +234,8 @@ export class WorkspaceFieldRelationComparator {
standardId: standardFieldMetadata.standardId ?? undefined,
description: relationFieldMetadata.description ?? undefined,
icon: relationFieldMetadata.icon ?? undefined,
type: standardFieldMetadata.type,
morphId: standardFieldMetadata.morphId ?? undefined,
},
});
} else if (allNewPropertiesAreNull) {
@@ -238,6 +252,8 @@ export class WorkspaceFieldRelationComparator {
standardId: standardFieldMetadata.standardId ?? undefined,
description: relationFieldMetadata.description ?? undefined,
icon: relationFieldMetadata.icon ?? undefined,
type: standardFieldMetadata.type,
morphId: standardFieldMetadata.morphId ?? undefined,
},
});
}
@@ -21,7 +21,9 @@ export class StandardFieldRelationFactory {
computeRelationFieldsForCustomObject(
customObjectMetadata: ObjectMetadataEntity,
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
): FieldMetadataEntity<FieldMetadataType.RELATION>[] {
): FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[] {
const workspaceRelationMetadataArgsCollection =
metadataArgsStorage.filterRelations(CustomWorkspaceEntity);
@@ -111,7 +113,9 @@ export class StandardFieldRelationFactory {
standardObjectMetadataWorkspaceEntity: typeof BaseWorkspaceEntity,
context: WorkspaceSyncContext,
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
): FieldMetadataEntity<FieldMetadataType.RELATION>[] {
): FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[] {
const workspaceEntityMetadataArgs = metadataArgsStorage.filterEntities(
standardObjectMetadataWorkspaceEntity,
);
@@ -282,9 +286,13 @@ export class StandardFieldRelationFactory {
);
}
const type = workspaceRelationMetadataArgs.isMorphRelation
? FieldMetadataType.MORPH_RELATION
: FieldMetadataType.RELATION;
return {
...sourceFieldMetadata,
type: FieldMetadataType.RELATION,
type,
settings: {
relationType: workspaceRelationMetadataArgs.type,
onDelete:
@@ -293,9 +301,10 @@ export class StandardFieldRelationFactory {
: undefined,
joinColumnName,
},
morphId: workspaceRelationMetadataArgs.morphId ?? null,
relationTargetObjectMetadataId: targetObjectMetadata.id,
relationTargetFieldMetadataId: targetFieldMetadata.id,
} satisfies FieldMetadataEntity<FieldMetadataType.RELATION>;
} satisfies FieldMetadataEntity<typeof type>;
});
return [...staticRelations, ...relationsFromDynamicRelations];
@@ -56,16 +56,30 @@ export type FieldComparatorResult =
export type FieldRelationComparatorResult =
| ComparatorSkipResult
| ComparatorCreateResult<
Partial<ComputedPartialFieldMetadata<FieldMetadataType.RELATION>> & {
Partial<
ComputedPartialFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
> & {
id: string;
type: FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION;
}
>
| ComparatorUpdateResult<
Partial<ComputedPartialFieldMetadata<FieldMetadataType.RELATION>> & {
Partial<
ComputedPartialFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
> & {
id: string;
type: FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION;
}
>
| ComparatorDeleteResult<FieldMetadataEntity<FieldMetadataType.RELATION>>;
| ComparatorDeleteResult<
FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
>;
export type IndexComparatorResult =
| ComparatorCreateResult<Partial<IndexMetadataEntity>>
@@ -17,6 +17,7 @@ import { type PartialFieldMetadata } from 'src/engine/workspace-manager/workspac
import { type PartialIndexMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-index-metadata.interface';
import { type UpdaterOptions } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/updater-options.interface';
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { type FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@@ -212,21 +213,31 @@ export class WorkspaceMetadataUpdaterService {
storage: WorkspaceSyncStorage,
options?: UpdaterOptions,
): Promise<{
createdFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[];
updatedFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[];
deletedFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[];
createdFieldRelationMetadataCollection: FieldMetadataUpdate<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[];
updatedFieldRelationMetadataCollection: FieldMetadataUpdate<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[];
deletedFieldRelationMetadataCollection: FieldMetadataUpdate<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[];
}> {
let createdFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[] =
[];
let updatedFieldRelationMetadataCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[] =
[];
let createdFieldRelationMetadataCollection: FieldMetadataUpdate<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[] = [];
let updatedFieldRelationMetadataCollection: FieldMetadataUpdate<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[] = [];
/**
* Create field relation metadata
*/
if (!options || options.actions.includes('create')) {
createdFieldRelationMetadataCollection = await this.updateEntities<
FieldMetadataEntity<FieldMetadataType.RELATION>
FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
>(
manager,
FieldMetadataEntity,
@@ -240,7 +251,9 @@ export class WorkspaceMetadataUpdaterService {
*/
if (!options || options.actions.includes('update')) {
updatedFieldRelationMetadataCollection = await this.updateEntities<
FieldMetadataEntity<FieldMetadataType.RELATION>
FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
>(
manager,
FieldMetadataEntity,
@@ -320,14 +333,21 @@ export class WorkspaceMetadataUpdaterService {
const fieldMetadata = originalObjectMetadataCollection
.find((object) => object.id === indexMetadata.objectMetadataId)
?.fields.find((field) => {
if (
isFieldMetadataEntityOfType(field, FieldMetadataType.RELATION)
) {
if (isFieldMetadataRelationOrMorphRelation(field)) {
if (field.settings?.joinColumnName === column) {
return true;
}
}
if (
isFieldMetadataEntityOfType(
field,
FieldMetadataType.MORPH_RELATION,
)
) {
return;
}
if (field.name === column) {
return true;
}
@@ -177,8 +177,12 @@ export class WorkspaceSyncFieldMetadataRelationService {
const originalFieldRelationMetadataCollection =
(originalObjectMetadata?.fields.filter(
(field) => field.type === FieldMetadataType.RELATION,
) ?? []) as FieldMetadataEntity<FieldMetadataType.RELATION>[];
(field) =>
field.type === FieldMetadataType.RELATION ||
field.type === FieldMetadataType.MORPH_RELATION,
) ?? []) as FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[];
if (originalFieldRelationMetadataCollection.length === 0) {
continue;
@@ -204,8 +208,12 @@ export class WorkspaceSyncFieldMetadataRelationService {
for (const customObjectMetadata of customObjectMetadataCollection) {
const originalFieldRelationMetadataCollection =
(customObjectMetadata.fields.filter(
(field) => field.type === FieldMetadataType.RELATION,
) ?? []) as FieldMetadataEntity<FieldMetadataType.RELATION>[];
(field) =>
field.type === FieldMetadataType.RELATION ||
field.type === FieldMetadataType.MORPH_RELATION,
) ?? []) as FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[];
const customFieldMetadataRelationCollection =
this.standardFieldRelationFactory.computeRelationFieldsForCustomObject(
@@ -30,17 +30,24 @@ export class WorkspaceSyncStorage {
// Field relation metadata
private readonly _fieldRelationMetadataCreateCollection: (Partial<
ComputedPartialFieldMetadata<FieldMetadataType.RELATION>
ComputedPartialFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
> & {
id: string;
type?: FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION;
})[] = [];
private readonly _fieldRelationMetadataUpdateCollection: (Partial<
ComputedPartialFieldMetadata<FieldMetadataType.RELATION>
ComputedPartialFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
> & {
id: string;
type?: FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION;
})[] = [];
private readonly _fieldRelationMetadataDeleteCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[] =
[];
private readonly _fieldRelationMetadataDeleteCollection: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>[] = [];
// Index metadata
private readonly _indexMetadataCreateCollection: Partial<IndexMetadataEntity>[] =
@@ -124,23 +131,35 @@ export class WorkspaceSyncStorage {
}
addCreateFieldRelationMetadata(
field: Partial<ComputedPartialFieldMetadata<FieldMetadataType.RELATION>> & {
field: Partial<
ComputedPartialFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
> & {
id: string;
type: FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION;
},
) {
this._fieldRelationMetadataCreateCollection.push(field);
}
addUpdateFieldRelationMetadata(
field: Partial<ComputedPartialFieldMetadata<FieldMetadataType.RELATION>> & {
field: Partial<
ComputedPartialFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>
> & {
id: string;
type: FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION;
},
) {
this._fieldRelationMetadataUpdateCollection.push(field);
}
addDeleteFieldRelationMetadata(
field: FieldMetadataEntity<FieldMetadataType.RELATION>,
field: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>,
) {
this._fieldRelationMetadataDeleteCollection.push(field);
}