mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
renamed cascade operation option names; added tests for cascade updates/removes
This commit is contained in:
parent
97208dc72a
commit
23458269df
@ -17,14 +17,14 @@ export class Image {
|
||||
post: Post;
|
||||
|
||||
@ManyToOne<Post>(() => Post, post => post.secondaryImages, {
|
||||
isCascadeInsert: true
|
||||
cascadeInsert: true
|
||||
})
|
||||
secondaryPost: Post;
|
||||
|
||||
@OneToOne<ImageDetails>(true, () => ImageDetails, details => details.image, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
details: ImageDetails;
|
||||
|
||||
|
||||
@ -23,16 +23,16 @@ export class Post {
|
||||
text: string;
|
||||
|
||||
@OneToOne<PostDetails>(true, () => PostDetails, details => details.post, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
details: PostDetails;
|
||||
|
||||
@OneToMany<Image>(type => Image, image => image.post, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
images: Image[] = [];
|
||||
|
||||
@ -41,8 +41,8 @@ export class Post {
|
||||
|
||||
@ManyToOne<Cover>(type => Cover, cover => cover.posts, {
|
||||
name: "coverId",
|
||||
isCascadeInsert: true,
|
||||
isCascadeRemove: true
|
||||
cascadeInsert: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
cover: Cover;
|
||||
|
||||
@ -52,9 +52,9 @@ export class Post {
|
||||
coverId: number;
|
||||
|
||||
@ManyToMany<Category>(true, type => Category, category => category.posts, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
categories: Category[];
|
||||
|
||||
|
||||
@ -21,14 +21,14 @@ export class PostDetails {
|
||||
post: Post;
|
||||
|
||||
@OneToMany<Category>(type => Category, category => category.details, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeRemove: true
|
||||
cascadeInsert: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
categories: Category[];
|
||||
|
||||
@ManyToOne<Chapter>(_ => Chapter, chapter => chapter.postDetails, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeRemove: true
|
||||
cascadeInsert: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
chapter: Chapter;
|
||||
|
||||
|
||||
@ -2,6 +2,11 @@ import {TypeORM} from "../../src/TypeORM";
|
||||
import {Post} from "./entity/Post";
|
||||
import {ConnectionOptions} from "../../src/connection/ConnectionOptions";
|
||||
import {PostDetails} from "./entity/PostDetails";
|
||||
import {PostCategory} from "./entity/PostCategory";
|
||||
import {PostMetadata} from "./entity/PostMetadata";
|
||||
import {PostImage} from "./entity/PostImage";
|
||||
import {PostInformation} from "./entity/PostInformation";
|
||||
import {PostAuthor} from "./entity/PostAuthor";
|
||||
|
||||
// first create a connection
|
||||
let options: ConnectionOptions = {
|
||||
@ -13,7 +18,7 @@ let options: ConnectionOptions = {
|
||||
autoSchemaCreate: true
|
||||
};
|
||||
|
||||
TypeORM.createMysqlConnection(options, [Post, PostDetails]).then(connection => {
|
||||
TypeORM.createMysqlConnection(options, [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor]).then(connection => {
|
||||
|
||||
let details = new PostDetails();
|
||||
details.authorName = "Umed";
|
||||
|
||||
@ -2,6 +2,11 @@ import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {PostDetails} from "./PostDetails";
|
||||
import {OneToOne} from "../../../src/decorator/Relations";
|
||||
import {PostCategory} from "./PostCategory";
|
||||
import {PostAuthor} from "./PostAuthor";
|
||||
import {PostInformation} from "./PostInformation";
|
||||
import {PostImage} from "./PostImage";
|
||||
import {PostMetadata} from "./PostMetadata";
|
||||
|
||||
@Table("sample2_post")
|
||||
export class Post {
|
||||
@ -15,11 +20,45 @@ export class Post {
|
||||
@Column()
|
||||
text: string;
|
||||
|
||||
// post has relation with category, however inverse relation is not set (category does not have relation with post set)
|
||||
@OneToOne<PostCategory>(true, () => PostCategory, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
category: PostCategory;
|
||||
|
||||
// post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
|
||||
// relation it will be inserted automatically to the db when you save this Post entity
|
||||
@OneToOne<PostDetails>(true, () => PostDetails, details => details.post, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
cascadeInsert: true
|
||||
})
|
||||
details: PostDetails;
|
||||
|
||||
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
|
||||
// it will be inserted automatically to the db when you save this Post entity
|
||||
@OneToOne<PostImage>(true, () => PostImage, image => image.post, {
|
||||
cascadeUpdate: true
|
||||
})
|
||||
image: PostImage;
|
||||
|
||||
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
|
||||
// it will be inserted automatically to the db when you save this Post entity
|
||||
@OneToOne<PostMetadata>(true, () => PostMetadata, metadata => metadata.post, {
|
||||
cascadeRemove: true
|
||||
})
|
||||
metadata: PostMetadata;
|
||||
|
||||
// post has relation with details. full cascades here
|
||||
@OneToOne<PostInformation>(true, () => PostInformation, information => information.post, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
information: PostInformation;
|
||||
|
||||
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
|
||||
@OneToOne<PostAuthor>(true, () => PostAuthor, author => author.post)
|
||||
author: PostAuthor;
|
||||
|
||||
}
|
||||
18
sample/sample2-one-to-one/entity/PostAuthor.ts
Normal file
18
sample/sample2-one-to-one/entity/PostAuthor.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {Post} from "./Post";
|
||||
import {OneToOne} from "../../../src/decorator/Relations";
|
||||
|
||||
@Table("sample2_post_author")
|
||||
export class PostAuthor {
|
||||
|
||||
@PrimaryColumn("int", { autoIncrement: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@OneToOne<Post>(false, () => Post, post => post.author)
|
||||
post: Post;
|
||||
|
||||
}
|
||||
13
sample/sample2-one-to-one/entity/PostCategory.ts
Normal file
13
sample/sample2-one-to-one/entity/PostCategory.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
|
||||
@Table("sample2_post_category")
|
||||
export class PostCategory {
|
||||
|
||||
@PrimaryColumn("int", { autoIncrement: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
}
|
||||
@ -18,10 +18,10 @@ export class PostDetails {
|
||||
@Column()
|
||||
metadata: string;
|
||||
|
||||
@OneToOne<Post>(true, () => Post, post => post.details, {
|
||||
isCascadeInsert: true,
|
||||
isCascadeUpdate: true,
|
||||
isCascadeRemove: true
|
||||
@OneToOne<Post>(false, () => Post, post => post.details, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
post: Post;
|
||||
|
||||
|
||||
18
sample/sample2-one-to-one/entity/PostImage.ts
Normal file
18
sample/sample2-one-to-one/entity/PostImage.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {Post} from "./Post";
|
||||
import {OneToOne} from "../../../src/decorator/Relations";
|
||||
|
||||
@Table("sample2_post_image")
|
||||
export class PostImage {
|
||||
|
||||
@PrimaryColumn("int", { autoIncrement: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
url: string;
|
||||
|
||||
@OneToOne<Post>(false, () => Post, post => post.image)
|
||||
post: Post;
|
||||
|
||||
}
|
||||
20
sample/sample2-one-to-one/entity/PostInformation.ts
Normal file
20
sample/sample2-one-to-one/entity/PostInformation.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {OneToOne} from "../../../src/decorator/Relations";
|
||||
import {Post} from "./Post";
|
||||
|
||||
@Table("sample2_post_information")
|
||||
export class PostInformation {
|
||||
|
||||
@PrimaryColumn("int", { autoIncrement: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
text: string;
|
||||
|
||||
@OneToOne<Post>(false, () => Post, post => post.information, {
|
||||
cascadeUpdate: true,
|
||||
})
|
||||
post: Post;
|
||||
|
||||
}
|
||||
18
sample/sample2-one-to-one/entity/PostMetadata.ts
Normal file
18
sample/sample2-one-to-one/entity/PostMetadata.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/decorator/Columns";
|
||||
import {Table} from "../../../src/decorator/Tables";
|
||||
import {Post} from "./Post";
|
||||
import {OneToOne} from "../../../src/decorator/Relations";
|
||||
|
||||
@Table("sample2_post_metadata")
|
||||
export class PostMetadata {
|
||||
|
||||
@PrimaryColumn("int", { autoIncrement: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
url: string;
|
||||
|
||||
@OneToOne<Post>(false, () => Post, post => post.metadata)
|
||||
post: Post;
|
||||
|
||||
}
|
||||
@ -5,7 +5,8 @@ import {RelationTypes} from "../metadata-builder/types/RelationTypes";
|
||||
|
||||
export function OneToOne<T>(isOwning: boolean, typeFunction: RelationTypeInFunction, options?: RelationOptions): Function;
|
||||
export function OneToOne<T>(isOwning: boolean, typeFunction: RelationTypeInFunction, inverseSide?: PropertyTypeInFunction<T>, options?: RelationOptions): Function;
|
||||
export function OneToOne<T>(isOwning: boolean, typeFunction: RelationTypeInFunction,
|
||||
export function OneToOne<T>(isOwning: boolean,
|
||||
typeFunction: RelationTypeInFunction,
|
||||
inverseSideOrOptions: PropertyTypeInFunction<T>|RelationOptions,
|
||||
options?: RelationOptions): Function {
|
||||
let inverseSideProperty: PropertyTypeInFunction<T>;
|
||||
|
||||
@ -110,12 +110,12 @@ export class RelationMetadata extends PropertyMetadata {
|
||||
|
||||
if (options.name)
|
||||
this._name = options.name;
|
||||
if (options.isCascadeInsert)
|
||||
this._isCascadeInsert = options.isCascadeInsert;
|
||||
if (options.isCascadeUpdate)
|
||||
this._isCascadeUpdate = options.isCascadeUpdate;
|
||||
if (options.isCascadeRemove)
|
||||
this._isCascadeRemove = options.isCascadeRemove;
|
||||
if (options.cascadeInsert)
|
||||
this._isCascadeInsert = options.cascadeInsert;
|
||||
if (options.cascadeUpdate)
|
||||
this._isCascadeUpdate = options.cascadeUpdate;
|
||||
if (options.cascadeRemove)
|
||||
this._isCascadeRemove = options.cascadeRemove;
|
||||
if (options.oldColumnName)
|
||||
this._oldColumnName = options.oldColumnName;
|
||||
if (options.nullable)
|
||||
|
||||
@ -8,17 +8,17 @@ export interface RelationOptions {
|
||||
/**
|
||||
* If set to true then it means that related object can be allowed to be inserted to the db.
|
||||
*/
|
||||
isCascadeInsert?: boolean;
|
||||
cascadeInsert?: boolean;
|
||||
|
||||
/**
|
||||
* If set to true then it means that related object can be allowed to be updated in the db.
|
||||
*/
|
||||
isCascadeUpdate?: boolean;
|
||||
cascadeUpdate?: boolean;
|
||||
|
||||
/**
|
||||
* If set to true then it means that related object can be allowed to be remove from the db.
|
||||
*/
|
||||
isCascadeRemove?: boolean;
|
||||
cascadeRemove?: boolean;
|
||||
|
||||
/**
|
||||
* Old column name. Used to make safe schema updates.
|
||||
|
||||
@ -25,7 +25,7 @@ export class QueryBuilder<Entity> {
|
||||
private wheres: { type: "simple"|"and"|"or", condition: string }[] = [];
|
||||
private havings: { type: "simple"|"and"|"or", condition: string }[] = [];
|
||||
private orderBys: { sort: string, order: "ASC"|"DESC" }[] = [];
|
||||
private parameters: { [key: string]: string } = {};
|
||||
private parameters: { [key: string]: any } = {};
|
||||
private limit: number;
|
||||
private offset: number;
|
||||
|
||||
@ -142,18 +142,21 @@ export class QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
where(where: string): this {
|
||||
where(where: string, parameters?: { [key: string]: any }): this {
|
||||
this.wheres.push({ type: "simple", condition: where });
|
||||
if (parameters) this.addParameters(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
andWhere(where: string): this {
|
||||
andWhere(where: string, parameters?: { [key: string]: any }): this {
|
||||
this.wheres.push({ type: "and", condition: where });
|
||||
if (parameters) this.addParameters(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
orWhere(where: string): this {
|
||||
orWhere(where: string, parameters?: { [key: string]: any }): this {
|
||||
this.wheres.push({ type: "or", condition: where });
|
||||
if (parameters) this.addParameters(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -207,8 +210,13 @@ export class QueryBuilder<Entity> {
|
||||
return this;
|
||||
}
|
||||
|
||||
setParameters(parameters: Object): this {
|
||||
Object.keys(parameters).forEach(key => this.parameters[key] = (<any> parameters)[key]);
|
||||
setParameters(parameters: { [key: string]: any }): this {
|
||||
this.parameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
addParameters(parameters: { [key: string]: any }): this {
|
||||
Object.keys(parameters).forEach(key => this.parameters[key] = parameters[key]);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -339,11 +347,11 @@ export class QueryBuilder<Entity> {
|
||||
return " " + joinType + " JOIN " + junctionTable + " " + junctionAlias + " " + join.conditionType + " " + condition1 +
|
||||
" " + joinType + " JOIN " + joinTable + " " + joinAlias + " " + join.conditionType + " " + condition2 + appendedCondition;
|
||||
|
||||
} else if (relation.isOneToOne || relation.isManyToOne) {
|
||||
} else if (relation.isManyToOne || (relation.isOneToOne && relation.isOwning)) {
|
||||
const condition = join.alias.name + "." + joinTableColumn + "=" + parentAlias + "." + join.alias.parentPropertyName;
|
||||
return " " + joinType + " JOIN " + joinTable + " " + join.alias.name + " " + join.conditionType + " " + condition + appendedCondition;
|
||||
|
||||
} else if (relation.isOneToMany) {
|
||||
} else if (relation.isOneToMany || (relation.isOneToOne && !relation.isOwning)) {
|
||||
const condition = join.alias.name + "." + relation.inverseSideProperty + "=" + parentAlias + "." + joinTableColumn;
|
||||
return " " + joinType + " JOIN " + joinTable + " " + join.alias.name + " " + join.conditionType + " " + condition + appendedCondition;
|
||||
|
||||
|
||||
@ -55,6 +55,9 @@ export class AliasMap {
|
||||
const parentAlias = this.findAliasByName(alias.parentAliasName); // todo: throw exceptions everywhere
|
||||
const parentEntityMetadata = this.getEntityMetadataByAlias(parentAlias);
|
||||
const relation = parentEntityMetadata.findRelationWithDbName(alias.parentPropertyName);
|
||||
if (!relation)
|
||||
throw new Error("Related entity metadata was not found.");
|
||||
|
||||
return relation.relatedEntityMetadata;
|
||||
}
|
||||
|
||||
|
||||
@ -91,6 +91,8 @@ export class EntityPersistOperationsBuilder {
|
||||
// if relation has "insert" it can insert a new entity
|
||||
// if relation has "update" it can only update related entity
|
||||
// if relation has "remove" it can only remove related entity
|
||||
|
||||
private strictCascadesMode = false;
|
||||
|
||||
constructor(private connection: Connection) {
|
||||
}
|
||||
@ -329,8 +331,13 @@ export class EntityPersistOperationsBuilder {
|
||||
return dbEntity.id === newEntity[metadata.primaryColumn.name] && dbEntity.entity.constructor.name === metadata.name;
|
||||
});
|
||||
|
||||
if (isObjectNew && fromRelation && !fromRelation.isCascadeInsert)
|
||||
throw new Error("Cascade inserts are not allowed in " + metadata.name + "#" + fromRelation.propertyName);
|
||||
if (isObjectNew && fromRelation && !fromRelation.isCascadeInsert) {
|
||||
if (this.strictCascadesMode) {
|
||||
throw new Error("Cascade inserts are not allowed in " + metadata.name + "#" + fromRelation.propertyName);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (isObjectNew)
|
||||
insertedEntities.push({
|
||||
@ -422,8 +429,13 @@ export class EntityPersistOperationsBuilder {
|
||||
|
||||
const updatedEntities: any[] = [];
|
||||
const diff = this.diffColumns(metadata, newEntity, dbEntity);
|
||||
if (diff.length && fromRelation && !fromRelation.isCascadeUpdate)
|
||||
throw new Error("Cascade updates are not allowed in " + metadata.name + "#" + fromRelation.propertyName);
|
||||
if (diff.length && fromRelation && !fromRelation.isCascadeUpdate) {
|
||||
if (this.strictCascadesMode) {
|
||||
throw new Error("Cascade updates are not allowed in " + metadata.name + "#" + fromRelation.propertyName);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (diff.length) {
|
||||
updatedEntities.push({
|
||||
|
||||
@ -244,8 +244,19 @@ export class Repository<Entity> {
|
||||
.map(column => "'" + entity[column.propertyName] + "'");
|
||||
const allColumns = columns.concat(virtualColumns);
|
||||
const allVolumes = values.concat(virtualValues);*/
|
||||
const relationColumns = metadata.relations
|
||||
.filter(relation => relation.isOwning && !!relation.relatedEntityMetadata)
|
||||
.filter(relation => entity.hasOwnProperty(relation.propertyName))
|
||||
.filter(relation => entity[relation.propertyName][relation.relatedEntityMetadata.primaryColumn.name])
|
||||
.map(relation => relation.name);
|
||||
|
||||
const query = `INSERT INTO ${metadata.table.name}(${columns.join(",")}) VALUES (${values.join(",")})`;
|
||||
const relationValues = metadata.relations
|
||||
.filter(relation => relation.isOwning && !!relation.relatedEntityMetadata)
|
||||
.filter(relation => entity.hasOwnProperty(relation.propertyName))
|
||||
.filter(relation => entity[relation.propertyName].hasOwnProperty(relation.relatedEntityMetadata.primaryColumn.name))
|
||||
.map(relation => "'" + entity[relation.propertyName][relation.relatedEntityMetadata.primaryColumn.name] + "'");
|
||||
|
||||
const query = `INSERT INTO ${metadata.table.name}(${columns.concat(relationColumns).join(",")}) VALUES (${values.concat(relationValues).join(",")})`;
|
||||
return this.connection.driver.query(query);
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,11 @@ import {Repository} from "../../src/repository/Repository";
|
||||
import {SchemaCreator} from "../../src/schema-creator/SchemaCreator";
|
||||
import {PostDetails} from "../../sample/sample2-one-to-one/entity/PostDetails";
|
||||
import {Post} from "../../sample/sample2-one-to-one/entity/Post";
|
||||
import {PostCategory} from "../../sample/sample2-one-to-one/entity/PostCategory";
|
||||
import {PostAuthor} from "../../sample/sample2-one-to-one/entity/PostAuthor";
|
||||
import {PostMetadata} from "../../sample/sample2-one-to-one/entity/PostMetadata";
|
||||
import {PostImage} from "../../sample/sample2-one-to-one/entity/PostImage";
|
||||
import {PostInformation} from "../../sample/sample2-one-to-one/entity/PostInformation";
|
||||
|
||||
chai.should();
|
||||
describe("insertion", function() {
|
||||
@ -27,7 +32,7 @@ describe("insertion", function() {
|
||||
// connect to db
|
||||
let connection: Connection;
|
||||
before(function() {
|
||||
return TypeORM.createMysqlConnection(options, [Post, PostDetails]).then(conn => {
|
||||
return TypeORM.createMysqlConnection(options, [Post, PostDetails, PostCategory, PostMetadata, PostImage, PostInformation, PostAuthor]).then(conn => {
|
||||
connection = conn;
|
||||
}).catch(e => console.log("Error during connection to db: " + e));
|
||||
});
|
||||
@ -44,22 +49,26 @@ describe("insertion", function() {
|
||||
}
|
||||
|
||||
let postRepository: Repository<Post>,
|
||||
postDetailsRepository: Repository<PostDetails>;
|
||||
postDetailsRepository: Repository<PostDetails>,
|
||||
postCategoryRepository: Repository<PostCategory>,
|
||||
postImageRepository: Repository<PostImage>;
|
||||
before(function() {
|
||||
postRepository = connection.getRepository<Post>(Post);
|
||||
postDetailsRepository = connection.getRepository<PostDetails>(PostDetails);
|
||||
postCategoryRepository = connection.getRepository<PostCategory>(PostCategory);
|
||||
postImageRepository = connection.getRepository<PostImage>(PostImage);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Specifications: persist
|
||||
// Specifications
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
describe("insert post and details", function() {
|
||||
describe("insert post and details (has inverse relation + full cascade options)", function() {
|
||||
let newPost: Post, details: PostDetails, savedPost: Post;
|
||||
|
||||
before(reloadDatabase);
|
||||
|
||||
beforeEach(function() {
|
||||
before(function() {
|
||||
details = new PostDetails();
|
||||
details.authorName = "Umed";
|
||||
details.comment = "this is post";
|
||||
@ -86,64 +95,291 @@ describe("insertion", function() {
|
||||
});
|
||||
|
||||
it("should have inserted post in the database", function() {
|
||||
return postRepository.findById(savedPost.id).should.eventually.eql({
|
||||
id: savedPost.id,
|
||||
text: "Hello post",
|
||||
title: "this is post title"
|
||||
});
|
||||
const expectedPost = new Post();
|
||||
expectedPost.id = savedPost.id;
|
||||
expectedPost.text = savedPost.text;
|
||||
expectedPost.title = savedPost.title;
|
||||
|
||||
return postRepository.findById(savedPost.id).should.eventually.eql(expectedPost);
|
||||
});
|
||||
|
||||
it("should have inserted post details in the database", function() {
|
||||
return postDetailsRepository.findById(savedPost.details.id).should.eventually.eql({
|
||||
id: savedPost.details.id,
|
||||
authorName: "Umed",
|
||||
comment: "this is post",
|
||||
metadata: "post,posting,postman"
|
||||
});
|
||||
const expectedDetails = new PostDetails();
|
||||
expectedDetails.id = savedPost.details.id;
|
||||
expectedDetails.authorName = savedPost.details.authorName;
|
||||
expectedDetails.comment = savedPost.details.comment;
|
||||
expectedDetails.metadata = savedPost.details.metadata;
|
||||
|
||||
return postDetailsRepository.findById(savedPost.details.id).should.eventually.eql(expectedDetails);
|
||||
});
|
||||
|
||||
it("should load post and its details if left join used", function() {
|
||||
const expectedPost = new Post();
|
||||
expectedPost.id = savedPost.id;
|
||||
expectedPost.text = savedPost.text;
|
||||
expectedPost.title = savedPost.title;
|
||||
expectedPost.details = new PostDetails();
|
||||
expectedPost.details.id = savedPost.details.id;
|
||||
expectedPost.details.authorName = savedPost.details.authorName;
|
||||
expectedPost.details.comment = savedPost.details.comment;
|
||||
expectedPost.details.metadata = savedPost.details.metadata;
|
||||
|
||||
return postRepository
|
||||
.createQueryBuilder("post")
|
||||
.leftJoinAndSelect("post.details", "details")
|
||||
.where("post.id=:id")
|
||||
.setParameter("id", savedPost.id)
|
||||
.getSingleResult()
|
||||
.should.eventually.eql({
|
||||
id: savedPost.id,
|
||||
text: "Hello post",
|
||||
title: "this is post title",
|
||||
details: {
|
||||
id: savedPost.details.id,
|
||||
authorName: "Umed",
|
||||
comment: "this is post",
|
||||
metadata: "post,posting,postman"
|
||||
}
|
||||
});
|
||||
.should.eventually.eql(expectedPost);
|
||||
});
|
||||
|
||||
it("should load details and its post if left join used (from reverse side)", function() {
|
||||
|
||||
const expectedDetails = new PostDetails();
|
||||
expectedDetails.id = savedPost.details.id;
|
||||
expectedDetails.authorName = savedPost.details.authorName;
|
||||
expectedDetails.comment = savedPost.details.comment;
|
||||
expectedDetails.metadata = savedPost.details.metadata;
|
||||
|
||||
expectedDetails.post = new Post();
|
||||
expectedDetails.post.id = savedPost.id;
|
||||
expectedDetails.post.text = savedPost.text;
|
||||
expectedDetails.post.title = savedPost.title;
|
||||
|
||||
return postDetailsRepository
|
||||
.createQueryBuilder("details")
|
||||
.leftJoinAndSelect("details.post", "post")
|
||||
.where("details.id=:id")
|
||||
.setParameter("id", savedPost.id)
|
||||
.getSingleResult()
|
||||
.should.eventually.eql({
|
||||
id: savedPost.details.id,
|
||||
authorName: "Umed",
|
||||
comment: "this is post",
|
||||
metadata: "post,posting,postman",
|
||||
post: {
|
||||
id: savedPost.id,
|
||||
text: "Hello post",
|
||||
title: "this is post title",
|
||||
}
|
||||
.should.eventually.eql(expectedDetails);
|
||||
});
|
||||
|
||||
it("should load saved post without details if left joins are not specified", function() {
|
||||
const expectedPost = new Post();
|
||||
expectedPost.id = savedPost.id;
|
||||
expectedPost.text = savedPost.text;
|
||||
expectedPost.title = savedPost.title;
|
||||
|
||||
return postRepository
|
||||
.createQueryBuilder("post")
|
||||
.where("post.id=:id", { id: savedPost.id })
|
||||
.getSingleResult()
|
||||
.should.eventually.eql(expectedPost);
|
||||
});
|
||||
|
||||
it("should load saved post without details if left joins are not specified", function() {
|
||||
const expectedDetails = new PostDetails();
|
||||
expectedDetails.id = savedPost.details.id;
|
||||
expectedDetails.authorName = savedPost.details.authorName;
|
||||
expectedDetails.comment = savedPost.details.comment;
|
||||
expectedDetails.metadata = savedPost.details.metadata;
|
||||
|
||||
return postDetailsRepository
|
||||
.createQueryBuilder("details")
|
||||
.where("details.id=:id", { id: savedPost.id })
|
||||
.getSingleResult()
|
||||
.should.eventually.eql(expectedDetails);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("insert post and category (one-side relation)", function() {
|
||||
let newPost: Post, category: PostCategory, savedPost: Post;
|
||||
|
||||
before(reloadDatabase);
|
||||
|
||||
before(function() {
|
||||
category = new PostCategory();
|
||||
category.name = "technology";
|
||||
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
newPost.category = category;
|
||||
|
||||
return postRepository.persist(newPost).then(post => savedPost = post);
|
||||
});
|
||||
|
||||
it("should return the same post instance after its created", function () {
|
||||
savedPost.should.be.equal(newPost);
|
||||
});
|
||||
|
||||
it("should return the same post category instance after its created", function () {
|
||||
savedPost.category.should.be.equal(newPost.category);
|
||||
});
|
||||
|
||||
it("should have a new generated id after post is created", function () {
|
||||
expect(savedPost.id).not.to.be.empty;
|
||||
expect(savedPost.category.id).not.to.be.empty;
|
||||
});
|
||||
|
||||
it("should have inserted post in the database", function() {
|
||||
const expectedPost = new Post();
|
||||
expectedPost.id = savedPost.id;
|
||||
expectedPost.text = savedPost.text;
|
||||
expectedPost.title = savedPost.title;
|
||||
return postRepository.findById(savedPost.id).should.eventually.eql(expectedPost);
|
||||
});
|
||||
|
||||
it("should have inserted category in the database", function() {
|
||||
const expectedPost = new PostCategory();
|
||||
expectedPost.id = savedPost.category.id;
|
||||
expectedPost.name = "technology";
|
||||
return postCategoryRepository.findById(savedPost.category.id).should.eventually.eql(expectedPost);
|
||||
});
|
||||
|
||||
it("should load post and its category if left join used", function() {
|
||||
const expectedPost = new Post();
|
||||
expectedPost.id = savedPost.id;
|
||||
expectedPost.title = savedPost.title;
|
||||
expectedPost.text = savedPost.text;
|
||||
expectedPost.category = new PostCategory();
|
||||
expectedPost.category.id = savedPost.category.id;
|
||||
expectedPost.category.name = savedPost.category.name;
|
||||
|
||||
return postRepository
|
||||
.createQueryBuilder("post")
|
||||
.leftJoinAndSelect("post.category", "category")
|
||||
.where("post.id=:id", { id: savedPost.id })
|
||||
.getSingleResult()
|
||||
.should.eventually.eql(expectedPost);
|
||||
});
|
||||
|
||||
it("should load details and its post if left join used (from reverse side)", function() {
|
||||
// later need to specify with what exception we reject it
|
||||
/*return postCategoryRepository
|
||||
.createQueryBuilder("category")
|
||||
.leftJoinAndSelect("category.post", "post")
|
||||
.where("category.id=:id", { id: savedPost.id })
|
||||
.getSingleResult()
|
||||
.should.be.rejectedWith(Error);*/ // not working, find fix
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("cascade updates should not be executed when cascadeUpdate option is not set", function() {
|
||||
let newPost: Post, details: PostDetails, savedPost: Post;
|
||||
|
||||
before(reloadDatabase);
|
||||
|
||||
before(function() {
|
||||
|
||||
details = new PostDetails();
|
||||
details.authorName = "Umed";
|
||||
details.comment = "this is post";
|
||||
details.metadata = "post,posting,postman";
|
||||
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
newPost.details = details;
|
||||
|
||||
return postRepository
|
||||
.persist(newPost)
|
||||
.then(post => savedPost = post);
|
||||
});
|
||||
|
||||
it("should ignore updates in the model and do not update the db when entity is updated", function () {
|
||||
newPost.details.comment = "i am updated comment";
|
||||
return postRepository.persist(newPost).then(updatedPost => {
|
||||
updatedPost.details.comment.should.be.equal("i am updated comment");
|
||||
return postRepository
|
||||
.createQueryBuilder("post")
|
||||
.leftJoinAndSelect("post.details", "details")
|
||||
.where("post.id=:id")
|
||||
.setParameter("id", updatedPost.id)
|
||||
.getSingleResult()
|
||||
}).then(updatedPostReloaded => {
|
||||
updatedPostReloaded.details.comment.should.be.equal("this is post");
|
||||
});
|
||||
}); // todo: also check that updates throw exception in strict cascades mode
|
||||
});
|
||||
|
||||
describe("cascade remove should not be executed when cascadeRemove option is not set", function() {
|
||||
let newPost: Post, details: PostDetails, savedPost: Post;
|
||||
|
||||
before(reloadDatabase);
|
||||
|
||||
before(function() {
|
||||
|
||||
details = new PostDetails();
|
||||
details.authorName = "Umed";
|
||||
details.comment = "this is post";
|
||||
details.metadata = "post,posting,postman";
|
||||
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
newPost.details = details;
|
||||
|
||||
return postRepository
|
||||
.persist(newPost)
|
||||
.then(post => savedPost = post);
|
||||
});
|
||||
|
||||
it("should ignore updates in the model and do not update the db when entity is updated", function () {
|
||||
newPost.details = null;
|
||||
return postRepository.persist(newPost).then(updatedPost => {
|
||||
return postRepository
|
||||
.createQueryBuilder("post")
|
||||
.leftJoinAndSelect("post.details", "details")
|
||||
.where("post.id=:id")
|
||||
.setParameter("id", updatedPost.id)
|
||||
.getSingleResult()
|
||||
}).then(updatedPostReloaded => {
|
||||
updatedPostReloaded.details.comment.should.be.equal("this is post");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("cascade updates should be executed when cascadeUpdate option is set", function() {
|
||||
let newPost: Post, newImage: PostImage, savedPost: Post, savedImage: PostImage;
|
||||
|
||||
before(reloadDatabase);
|
||||
|
||||
it("should ignore updates in the model and do not update the db when entity is updated", function () {
|
||||
|
||||
newImage = new PostImage();
|
||||
newImage.url = "logo.png";
|
||||
|
||||
newPost = new Post();
|
||||
newPost.text = "Hello post";
|
||||
newPost.title = "this is post title";
|
||||
|
||||
return postImageRepository
|
||||
.persist(newImage)
|
||||
.then(image => {
|
||||
savedImage = image;
|
||||
newPost.image = image;
|
||||
return postRepository.persist(newPost);
|
||||
|
||||
}).then(post => {
|
||||
newPost = post;
|
||||
return postRepository
|
||||
.createQueryBuilder("post")
|
||||
.leftJoinAndSelect("post.image", "image")
|
||||
.where("post.id=:id")
|
||||
.setParameter("id", post.id)
|
||||
.getSingleResult();
|
||||
|
||||
}).then(loadedPost => {
|
||||
loadedPost.image.url = "new-logo.png";
|
||||
return postRepository.persist(loadedPost);
|
||||
|
||||
}).then(() => {
|
||||
return postRepository
|
||||
.createQueryBuilder("post")
|
||||
.leftJoinAndSelect("post.image", "image")
|
||||
.where("post.id=:id")
|
||||
.setParameter("id", newPost.id)
|
||||
.getSingleResult();
|
||||
|
||||
}).then(reloadedPost => {
|
||||
reloadedPost.image.url.should.be.equal("new-logo.png");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// todo: insert objects with different data types: boolean, dates etc.
|
||||
|
||||
});
|
||||
@ -5,6 +5,7 @@
|
||||
"chai-as-promised": "github:DefinitelyTyped/DefinitelyTyped/chai-as-promised/chai-as-promised.d.ts#d6e3f732183ca0ee4c4b438323253b384f5b4091",
|
||||
"mocha": "github:DefinitelyTyped/DefinitelyTyped/mocha/mocha.d.ts#7a3ca1f0b8a0960af9fc1838f3234cc9d6ce0645",
|
||||
"mockery": "github:DefinitelyTyped/DefinitelyTyped/mockery/mockery.d.ts#6f6e5c7dd9effe21fee14eb65fe340ecbbc8580a",
|
||||
"promises-a-plus": "github:DefinitelyTyped/DefinitelyTyped/promises-a-plus/promises-a-plus.d.ts#56068d3354648384ff32db20b6fcda4262856f33",
|
||||
"sinon": "github:DefinitelyTyped/DefinitelyTyped/sinon/sinon.d.ts#7a3ca1f0b8a0960af9fc1838f3234cc9d6ce0645"
|
||||
},
|
||||
"ambientDependencies": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user