added entity model for AR pattern support

This commit is contained in:
Umed Khudoiberdiev 2017-05-19 13:21:25 +05:00
parent 4dfe6f9946
commit ce7f36b162
4 changed files with 383 additions and 4 deletions

View File

@ -0,0 +1,325 @@
import {Repository} from "./Repository";
import {getConnection, getRepository} from "../index";
import {QueryBuilder} from "../query-builder/QueryBuilder";
import {DeepPartial} from "../common/DeepPartial";
import {PersistOptions} from "./PersistOptions";
import {FindOneOptions} from "../find-options/FindOneOptions";
import {RemoveOptions} from "./RemoveOptions";
import {FindManyOptions} from "../find-options/FindManyOptions";
import {Connection} from "../connection/Connection";
/**
* Base abstract entity for all entities, used in ActiveRecord patterns.
*/
export class EntityModel {
// -------------------------------------------------------------------------
// Private Static Properties
// -------------------------------------------------------------------------
/**
* Connection used in all static methods of the EntityModel.
*/
private static usedConnection?: Connection;
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Checks if entity has an id.
* If entity composite compose ids, it will check them all.
*/
hasId(): boolean {
return (this.constructor as any).getRepository().hasId(this);
}
/**
* Saves current entity in the database.
* If entity does not exist in the database then inserts, otherwise updates.
*/
save(): Promise<this> {
return (this.constructor as any).getRepository().persist(this);
}
/**
* Removes current entity from the database.
*/
remove(): Promise<this> {
return (this.constructor as any).getRepository().remove(this);
}
// -------------------------------------------------------------------------
// Public Static Methods
// -------------------------------------------------------------------------
/**
* Sets connection to be used by entity.
*/
static useConnection(connection: Connection) {
this.usedConnection = connection;
}
/**
* Gets current entity's Repository.
*/
static getRepository<T extends EntityModel = any>(): Repository<T> {
const connection = this.usedConnection || getConnection();
return connection.getRepository<T>(this);
}
/**
* Returns object that is managed by this repository.
* If this repository manages entity from schema,
* then it returns a name of that schema instead.
*/
static get target(): Function|string {
return this.getRepository().target;
}
/**
* Checks entity has an id.
* If entity composite compose ids, it will check them all.
*/
static hasId(entity: EntityModel): boolean {
return this.getRepository().hasId(entity);
}
/**
* Gets entity mixed id.
*/
static getId<T extends EntityModel = any>(entity: T): any {
return this.getRepository<T>().getId(entity);
}
/**
* Creates a new query builder that can be used to build a sql query.
*/
static createQueryBuilder<T extends EntityModel = any>(alias: string): QueryBuilder<T> {
return this.getRepository<T>().createQueryBuilder(alias);
}
/**
* Creates a new entity instance.
*/
static create<T extends EntityModel = any>(): T {
return this.getRepository<T>().create();
}
/**
* Merges multiple entities (or entity-like objects) into a given entity.
*/
static merge<T extends EntityModel = any>(mergeIntoEntity: T, ...entityLikes: DeepPartial<T>[]): T {
return this.getRepository<T>().merge(mergeIntoEntity, ...entityLikes);
}
/**
* Creates a new entity from the given plan javascript object. If entity already exist in the database, then
* it loads it (and everything related to it), replaces all values with the new ones from the given object
* and returns this new entity. This new entity is actually a loaded from the db entity with all properties
* replaced from the new object.
*
* Note that given entity-like object must have an entity id / primary key to find entity by.
* Returns undefined if entity with given id was not found.
*/
static preload<T extends EntityModel = any>(entityLike: DeepPartial<T>): Promise<T|undefined> {
return this.getRepository<T>().preload(entityLike);
}
/**
* Saves all given entities in the database.
* If entities do not exist in the database then inserts, otherwise updates.
*/
static save<T extends EntityModel = any>(entities: T[], options?: PersistOptions): Promise<T[]>;
/**
* Saves a given entity in the database.
* If entity does not exist in the database then inserts, otherwise updates.
*/
static save<T extends EntityModel = any>(entity: T, options?: PersistOptions): Promise<T>;
/**
* Saves one or many given entities.
*/
static save<T extends EntityModel = any>(entityOrEntities: T|T[], options?: PersistOptions): Promise<T|T[]> {
return this.getRepository<T>().persist(entityOrEntities as any, options);
}
/**
* Updates entity partially. Entity can be found by a given conditions.
*/
static update<T extends EntityModel = any>(conditions: Partial<T>, partialEntity: DeepPartial<T>, options?: PersistOptions): Promise<void>;
/**
* Updates entity partially. Entity can be found by a given find options.
*/
static update<T extends EntityModel = any>(findOptions: FindOneOptions<T>, partialEntity: DeepPartial<T>, options?: PersistOptions): Promise<void>;
/**
* Updates entity partially. Entity can be found by a given conditions.
*/
static update<T extends EntityModel = any>(conditionsOrFindOptions: Partial<T>|FindOneOptions<T>, partialEntity: DeepPartial<T>, options?: PersistOptions): Promise<void> {
return this.getRepository<T>().update(conditionsOrFindOptions as any, partialEntity, options);
}
/**
* Updates entity partially. Entity will be found by a given id.
*/
static updateById<T extends EntityModel = any>(id: any, partialEntity: DeepPartial<T>, options?: PersistOptions): Promise<void> {
return this.getRepository<T>().updateById(id, partialEntity, options);
}
/**
* Removes a given entities from the database.
*/
static remove<T extends EntityModel = any>(entities: T[], options?: RemoveOptions): Promise<T[]>;
/**
* Removes a given entity from the database.
*/
static remove<T extends EntityModel = any>(entity: T, options?: RemoveOptions): Promise<T>;
/**
* Removes one or many given entities.
*/
static remove<T extends EntityModel = any>(entityOrEntities: T|T[], options?: RemoveOptions): Promise<T|T[]> {
return this.getRepository<T>().remove(entityOrEntities as any, options);
}
/**
* Removes entity by a given entity id.
*/
static removeById<T extends EntityModel = any>(id: any, options?: RemoveOptions): Promise<void> {
return this.getRepository<T>().removeById(id, options);
}
/**
* Counts entities that match given options.
*/
static count<T extends EntityModel = any>(options?: FindManyOptions<T>): Promise<number>;
/**
* Counts entities that match given conditions.
*/
static count<T extends EntityModel = any>(conditions?: DeepPartial<T>): Promise<number>;
/**
* Counts entities that match given find options or conditions.
*/
static count<T extends EntityModel = any>(optionsOrConditions?: FindManyOptions<T>|DeepPartial<T>): Promise<number> {
return this.getRepository<T>().count(optionsOrConditions as any);
}
/**
* Finds entities that match given options.
*/
static find<T extends EntityModel = any>(options?: FindManyOptions<T>): Promise<T[]>;
/**
* Finds entities that match given conditions.
*/
static find<T extends EntityModel = any>(conditions?: DeepPartial<T>): Promise<T[]>;
/**
* Finds entities that match given find options or conditions.
*/
static find<T extends EntityModel = any>(optionsOrConditions?: FindManyOptions<T>|DeepPartial<T>): Promise<T[]> {
return this.getRepository<T>().find(optionsOrConditions as any);
}
/**
* Finds entities that match given find options.
* Also counts all entities that match given conditions,
* but ignores pagination settings (from and take options).
*/
static findAndCount<T extends EntityModel = any>(options?: FindManyOptions<T>): Promise<[ T[], number ]>;
/**
* Finds entities that match given conditions.
* Also counts all entities that match given conditions,
* but ignores pagination settings (from and take options).
*/
static findAndCount<T extends EntityModel = any>(conditions?: DeepPartial<T>): Promise<[ T[], number ]>;
/**
* Finds entities that match given find options or conditions.
* Also counts all entities that match given conditions,
* but ignores pagination settings (from and take options).
*/
static findAndCount<T extends EntityModel = any>(optionsOrConditions?: FindManyOptions<T>|DeepPartial<T>): Promise<[ T[], number ]> {
return this.getRepository<T>().findAndCount(optionsOrConditions as any);
}
/**
* Finds entities by ids.
* Optionally find options can be applied.
*/
static findByIds<T extends EntityModel = any>(ids: any[], options?: FindManyOptions<T>): Promise<T[]>;
/**
* Finds entities by ids.
* Optionally conditions can be applied.
*/
static findByIds<T extends EntityModel = any>(ids: any[], conditions?: DeepPartial<T>): Promise<T[]>;
/**
* Finds entities by ids.
* Optionally find options can be applied.
*/
static findByIds<T extends EntityModel = any>(ids: any[], optionsOrConditions?: FindManyOptions<T>|DeepPartial<T>): Promise<T[]> {
return this.getRepository<T>().findByIds(ids, optionsOrConditions as any);
}
/**
* Finds first entity that matches given options.
*/
static findOne<T extends EntityModel = any>(options?: FindOneOptions<T>): Promise<T|undefined>;
/**
* Finds first entity that matches given conditions.
*/
static findOne<T extends EntityModel = any>(conditions?: DeepPartial<T>): Promise<T|undefined>;
/**
* Finds first entity that matches given conditions.
*/
static findOne<T extends EntityModel = any>(optionsOrConditions?: FindOneOptions<T>|DeepPartial<T>): Promise<T|undefined> {
return this.getRepository<T>().findOne(optionsOrConditions as any);
}
/**
* Finds entity by given id.
* Optionally find options can be applied.
*/
static findOneById<T extends EntityModel = any>(id: any, options?: FindOneOptions<T>): Promise<T|undefined>;
/**
* Finds entity by given id.
* Optionally conditions can be applied.
*/
static findOneById<T extends EntityModel = any>(id: any, conditions?: DeepPartial<T>): Promise<T|undefined>;
/**
* Finds entity by given id.
* Optionally find options or conditions can be applied.
*/
static findOneById<T extends EntityModel = any>(id: any, optionsOrConditions?: FindOneOptions<T>|DeepPartial<T>): Promise<T|undefined> {
return this.getRepository<T>().findOneById(id, optionsOrConditions as any);
}
/**
* Executes a raw SQL query and returns a raw database results.
* Raw query execution is supported only by relational databases (MongoDB is not supported).
*/
static query<T extends EntityModel = any>(query: string, parameters?: any[]): Promise<any> {
return this.getRepository<T>().query(query, parameters);
}
/**
* Clears all the data from the given table/collection (truncates/drops it).
*/
static clear<T extends EntityModel = any>(): Promise<void> {
return this.getRepository<T>().clear();
}
}

View File

@ -53,7 +53,7 @@ export class Repository<Entity extends ObjectLiteral> {
/**
* Checks if entity has an id.
* If entity contains compose ids, then it checks them all.
* If entity composite compose ids, it will check them all.
*/
hasId(entity: Entity): boolean {
return this.metadata.hasId(entity);
@ -136,19 +136,19 @@ export class Repository<Entity extends ObjectLiteral> {
}
/**
* Persists (saves) all given entities in the database.
* Saves all given entities in the database.
* If entities do not exist in the database then inserts, otherwise updates.
*/
async persist(entities: Entity[], options?: PersistOptions): Promise<Entity[]>;
/**
* Persists (saves) a given entity in the database.
* Saves a given entity in the database.
* If entity does not exist in the database then inserts, otherwise updates.
*/
async persist(entity: Entity, options?: PersistOptions): Promise<Entity>;
/**
* Persists one or many given entities.
* Saves one or many given entities.
*/
async persist(entityOrEntities: Entity|Entity[], options?: PersistOptions): Promise<Entity|Entity[]> {
@ -430,6 +430,8 @@ export class Repository<Entity extends ObjectLiteral> {
* use EntityManager.transaction method instead.
*
* Transactions are supported only by relational databases (MongoDB is not supported).
*
* @deprecated use entity manager's transaction method instead.
*/
async transaction(runInTransaction: (repository: Repository<Entity>) => Promise<any>|any): Promise<any> {
const queryRunnerProvider = this.queryRunnerProvider || new QueryRunnerProvider(this.connection.driver, true);

View File

@ -0,0 +1,34 @@
import "reflect-metadata";
import {Post} from "./entity/Post";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../utils/test-utils";
import {Connection} from "../../../src/connection/Connection";
import {PromiseUtils} from "../../../src/util/PromiseUtils";
describe("entity-model", () => {
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchemaOnConnection: true,
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
it("should save successfully and use static methods successfully", () => PromiseUtils.runInSequence(connections, async connection => {
Post.useConnection(connection); // change connection each time because of AR specifics
const post = new Post();
post.title = "About ActiveRecord";
post.text = "Huge description how good or bad ActiveRecord is.";
await post.save();
const loadedPost = await Post.findOneById(1);
loadedPost!.should.be.instanceOf(Post);
loadedPost!.id.should.be.eql(1);
loadedPost!.title.should.be.eql("About ActiveRecord");
loadedPost!.text.should.be.eql("Huge description how good or bad ActiveRecord is.");
}));
});

View File

@ -0,0 +1,18 @@
import {Entity} from "../../../../src/decorator/entity/Entity";
import {EntityModel} from "../../../../src/repository/EntityModel";
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../src/decorator/columns/Column";
@Entity()
export class Post extends EntityModel {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
text: string;
}