Skip to content

allow additional options to be specified for associations #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions lib/annotations/ForeignKey.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import {AssociationForeignKeyOptions} from 'sequelize';
import {Model} from "../models/Model";
import {addForeignKey} from "../services/association";

export function ForeignKey(relatedClassGetter: () => typeof Model): Function {
export function ForeignKey(relatedClassGetter: () => typeof Model, options?: AssociationForeignKeyOptions): Function {

return (target: any, propertyName: string) => {

addForeignKey(target, relatedClassGetter, propertyName);
if (!options) {
options = {name: propertyName};
} else if (!options.name) {
options.name = propertyName;
}

addForeignKey(target, relatedClassGetter, options);
};
}
10 changes: 8 additions & 2 deletions lib/annotations/association/BelongsTo.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import {AssociationOptionsBelongsTo} from 'sequelize';

import {Model} from "../../models/Model";
import {BELONGS_TO, addAssociation} from "../../services/association";

export function BelongsTo(relatedClassGetter: () => typeof Model,
foreignKey?: string): Function {
options?: string | AssociationOptionsBelongsTo): Function {

return (target: any, propertyName: string) => {

if (typeof options === 'string') {
options = {foreignKey: {name: options}};
}

addAssociation(
target,
BELONGS_TO,
relatedClassGetter,
propertyName,
foreignKey
options
);
};
}
11 changes: 8 additions & 3 deletions lib/annotations/association/BelongsToMany.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import {AssociationOptionsBelongsToMany} from 'sequelize';

import {Model} from "../../models/Model";
import {BELONGS_TO_MANY, addAssociation} from "../../services/association";

export function BelongsToMany(relatedClassGetter: () => typeof Model,
through: (() => typeof Model)|string,
foreignKey?: string,
options?: string | AssociationOptionsBelongsToMany,
otherKey?: string): Function {

return (target: any, propertyName: string) => {

if (typeof options === 'string') {
// don't worry, through is mainly here to avoid TS error; actual through value is resolved later
options = {through: '', foreignKey: {name: options}};
}
addAssociation(
target,
BELONGS_TO_MANY,
relatedClassGetter,
propertyName,
foreignKey,
options,
otherKey,
through
);
Expand Down
10 changes: 7 additions & 3 deletions lib/annotations/association/HasMany.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import {AssociationOptionsHasMany} from 'sequelize';

import {Model} from "../../models/Model";
import {HAS_MANY, addAssociation} from "../../services/association";

export function HasMany(relatedClassGetter: () => typeof Model,
foreignKey?: string): Function {
options?: string | AssociationOptionsHasMany): Function {

return (target: any, propertyName: string) => {

if (typeof options === 'string') {
options = {foreignKey: {name: options}};
}
addAssociation(
target,
HAS_MANY,
relatedClassGetter,
propertyName,
foreignKey
options
);
};
}
10 changes: 7 additions & 3 deletions lib/annotations/association/HasOne.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import {AssociationOptionsHasOne} from 'sequelize';

import {Model} from "../../models/Model";
import {addAssociation, HAS_ONE} from "../../services/association";

export function HasOne(relatedClassGetter: () => typeof Model,
foreignKey?: string): Function {
options?: string | AssociationOptionsHasOne): Function {

return (target: any, propertyName: string) => {

if (typeof options === 'string') {
options = {foreignKey: {name: options}};
}
addAssociation(
target,
HAS_ONE,
relatedClassGetter,
propertyName,
foreignKey
options
);
};
}
5 changes: 4 additions & 1 deletion lib/interfaces/ISequelizeAssociation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {AssociationOptionsBelongsTo, AssociationOptionsBelongsToMany, AssociationOptionsHasMany,
AssociationOptionsHasOne, AssociationOptionsManyToMany} from 'sequelize';
import {Model} from "../models/Model";

export interface ISequelizeAssociation {
Expand All @@ -6,7 +8,8 @@ export interface ISequelizeAssociation {
relatedClassGetter: () => typeof Model;
through?: string;
throughClassGetter?: () => typeof Model;
foreignKey?: string;
options?: AssociationOptionsBelongsTo | AssociationOptionsBelongsToMany | AssociationOptionsHasMany |
AssociationOptionsHasOne | AssociationOptionsManyToMany;
otherKey?: string;
as: string;
}
3 changes: 2 additions & 1 deletion lib/interfaces/ISequelizeForeignKeyConfig.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {AssociationForeignKeyOptions} from 'sequelize';
import {Model} from "../models/Model";

export interface ISequelizeForeignKeyConfig {

relatedClassGetter: () => typeof Model;
foreignKey: string;
options: string | AssociationForeignKeyOptions;
}
11 changes: 8 additions & 3 deletions lib/models/BaseSequelize.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {merge} from 'lodash';
import {Model} from "./Model";
import {getModels} from "../services/models";
import {getAssociations, BELONGS_TO_MANY} from "../services/association";
Expand Down Expand Up @@ -89,7 +90,6 @@ export abstract class BaseSequelize {

associations.forEach(association => {

const foreignKey = association.foreignKey || getForeignKey(model, association);
const relatedClass = association.relatedClassGetter();
let through;
let otherKey;
Expand Down Expand Up @@ -126,12 +126,17 @@ export abstract class BaseSequelize {
}
}

model[association.relation](relatedClass, {
// ensure association options by default have most explicit foreignKey options
// so it merges properly with different foreignKey option permutations, or is overrwritten
// completely by options.foreignKey
const foreignKey = getForeignKey(model, association);
const options = merge({foreignKey: {name: foreignKey}}, association.options,
{
as: association.as,
through,
foreignKey,
otherKey
});
model[association.relation](relatedClass, options);

// The associations has to be adjusted
const _association = model['associations'][association.as];
Expand Down
32 changes: 23 additions & 9 deletions lib/services/association.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'reflect-metadata';
import {AssociationOptions, AssociationForeignKeyOptions, AssociationOptionsBelongsTo, AssociationOptionsBelongsToMany,
AssociationOptionsHasMany, AssociationOptionsHasOne, AssociationOptionsManyToMany} from 'sequelize';
import {Model} from "../models/Model";
import {ISequelizeForeignKeyConfig} from "../interfaces/ISequelizeForeignKeyConfig";
import {ISequelizeAssociation} from "../interfaces/ISequelizeAssociation";
Expand All @@ -18,7 +20,9 @@ export function addAssociation(target: any,
relation: string,
relatedClassGetter: () => typeof Model,
as: string,
foreignKey?: string,
options?: AssociationOptionsBelongsTo |
AssociationOptionsBelongsToMany | AssociationOptionsHasMany |
AssociationOptionsHasOne | AssociationOptionsManyToMany,
otherKey?: string,
through?: (() => typeof Model)|string): void {

Expand All @@ -42,7 +46,7 @@ export function addAssociation(target: any,
throughClassGetter,
through: through as string,
as,
foreignKey,
options,
otherKey
});
}
Expand All @@ -52,11 +56,19 @@ export function addAssociation(target: any,
*/
export function getForeignKey(_class: typeof Model,
association: ISequelizeAssociation): string {
const options = association.options as AssociationOptions;

// if foreign key is defined return this one
if (association.foreignKey) {
if (options && options.foreignKey) {
const foreignKey = options.foreignKey;
// if options is an object and has a string foreignKey property, use that as the name
if (typeof foreignKey === 'string') {
return foreignKey;
}

return association.foreignKey;
// if options is an object with foreignKey.name, use that as the name
if (foreignKey.name) {
return foreignKey.name;
}
}

// otherwise calculate the foreign key by related or through class
Expand Down Expand Up @@ -90,8 +102,10 @@ export function getForeignKey(_class: typeof Model,
for (const foreignKey of foreignKeys) {

if (foreignKey.relatedClassGetter() === relatedClass) {

return foreignKey.foreignKey;
if (typeof foreignKey.options === 'string') {
return foreignKey.options;
}
return (foreignKey.options as any).name;
}
}

Expand Down Expand Up @@ -123,7 +137,7 @@ export function getAssociationsByRelation(target: any, relatedClass: any): ISequ
*/
export function addForeignKey(target: any,
relatedClassGetter: () => typeof Model,
attrName: string): void {
options: string | AssociationForeignKeyOptions): void {

let foreignKeys = getForeignKeys(target);

Expand All @@ -134,7 +148,7 @@ export function addForeignKey(target: any,

foreignKeys.push({
relatedClassGetter,
foreignKey: attrName
options
});
}

Expand Down
Loading