├── .gitignore ├── .travis.yml ├── @types ├── index.d.ts └── orm_mirror │ ├── orm.d.ts │ └── sql-query.d.ts ├── License ├── Readme.md ├── build ├── bundle.js └── orm_mirror.js ├── lib ├── index.js ├── modules │ ├── mysql.js │ └── sqlite3.js └── patch.js ├── package.json ├── src ├── index.ts ├── modules │ ├── index.d.ts │ ├── mysql.ts │ └── sqlite3.ts └── patch.ts └── test ├── index.js ├── integration ├── association-extend.js ├── association-hasmany-extra.js ├── association-hasmany-hooks.js ├── association-hasmany.js ├── association-hasone-required.js ├── association-hasone-reverse.js ├── association-hasone-zeroid.js ├── association-hasone.js ├── date-type.js ├── event.js ├── hook.js ├── instance.js ├── model-aggregate.js ├── model-clear.js ├── model-count.js ├── model-create.js ├── model-exists.js ├── model-find-chain.js ├── model-find-mapsto.js ├── model-find.js ├── model-get.js ├── model-keys.js ├── model-one.js ├── model-save.js ├── model-sync.js ├── predefined-validators.js ├── property-custom.js ├── property-lazyload.js ├── property-maps-to.js ├── property.js ├── settings.js ├── smart-types.js └── validation.js └── support ├── opts.js └── spec_helper.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test.db* 3 | package-lock.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: '6' 3 | env: 4 | - VERSION=0.21.0 5 | - VERSION=0.22.0 6 | - VERSION=0.23.0 7 | - VERSION=0.24.0 8 | script: 9 | - npm i 10 | - mkdir -p ./node_modules/.bin 11 | - curl -SL "https://github.com/fibjs/fibjs/releases/download/v${VERSION}/fibjs-v${VERSION}-linux-x64.xz" -o ./node_modules/.bin/fibjs.xz 12 | - xz -d ./node_modules/.bin/fibjs.xz 13 | - chmod a+x ./node_modules/.bin/fibjs 14 | - npm run ci 15 | -------------------------------------------------------------------------------- /@types/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import OrmNS = require('orm') 4 | import SqlQueryNS from 'sqlquery' 5 | 6 | declare module "orm" { 7 | /* Connection About Patch :start */ 8 | 9 | /** 10 | * it should be defined in 'orm' but there is not, so we should fix it 11 | */ 12 | interface ConnInstanceInOrmConnDriverDB { 13 | begin(): void; 14 | close(): void; 15 | commit(): void; 16 | rollback(): void; 17 | trans(func: Function): boolean 18 | execute(sql: string, ...args: any[]): any[]; 19 | } 20 | 21 | interface SQLiteConnInstanceInOrmConnDriverDB extends ConnInstanceInOrmConnDriverDB, Class_SQLite { 22 | } 23 | interface MySQLConnInstanceInOrmConnDriverDB extends ConnInstanceInOrmConnDriverDB, Class_MySQL { 24 | } 25 | interface MSSQLConnInstanceInOrmConnDriverDB extends ConnInstanceInOrmConnDriverDB, Class_MSSQL { 26 | } 27 | 28 | interface DbInstanceInOrmConnDriver { 29 | conn: ConnInstanceInOrmConnDriverDB 30 | } 31 | export interface OrigOrmExecQueryOpts { 32 | [key: string]: any; 33 | } 34 | export interface OrigOrmConnDriver { 35 | // dialog type 36 | dialect: string; 37 | propertyToValue: Function; 38 | valueToProperty: Function; 39 | insert: Function; 40 | db: DbInstanceInOrmConnDriver 41 | } 42 | /** 43 | * then we should patch still 44 | */ 45 | export interface PatchedOrmConnDriver extends OrigOrmConnDriver { 46 | execQuerySync: (query: SqlQueryNS.Query, opt: OrigOrmExecQueryOpts) => any 47 | } 48 | 49 | export interface OrmConnectionOpts { 50 | } 51 | /* Connection About Patch :end */ 52 | 53 | export interface FibOrmFixedModelOptions /* extends OrmNS.ModelOptions */ { 54 | id?: string[]; 55 | autoFetch?: boolean; 56 | autoFetchLimit?: number; 57 | cacheFetch?: boolean; 58 | hooks?: OrmNS.Hooks; 59 | methods?: { [name: string]: Function }; 60 | 61 | [extensibleProperty: string]: any; 62 | } 63 | 64 | export interface FibORM extends OrmNS.ORM { 65 | /* all fixed: start */ 66 | models: { [key: string]: FibOrmFixedModel }; 67 | 68 | use(plugin: string, options?: any): FibORM; 69 | use(plugin: Plugin, options?: any): FibORM; 70 | 71 | define(name: string, properties: { [key: string]: OrigModelPropertyDefinition }, opts?: FibOrmFixedModelOptions): FibOrmFixedModel; 72 | ping(callback: (err: Error) => void): FibORM; 73 | close(callback: (err: Error) => void): FibORM; 74 | load(file: string, callback: (err: Error) => void): any; 75 | sync(callback: (err: Error) => void): FibORM; 76 | drop(callback: (err: Error) => void): FibORM; 77 | /* all fixed: end */ 78 | 79 | /* memeber patch: start */ 80 | driver: PatchedOrmConnDriver 81 | begin: () => any 82 | commit: () => any 83 | rollback: () => any 84 | trans: (func: Function) => any 85 | 86 | syncSync(): void; 87 | 88 | [extraMember: string]: any; 89 | /* memeber patch: end */ 90 | } 91 | // bad annotation but 'db' is used as like 'orm' ever, so we use 'FibOrmDB' to substitute FibORM 92 | type FibOrmDB = FibORM 93 | 94 | export function connectSync(opts: OrmNS.FibORMIConnectionOptions | string): FibOrmDB; 95 | 96 | export interface FibORMIConnectionOptions extends OrmNS.IConnectionOptions { 97 | timezone: string; 98 | } 99 | 100 | interface InstanceAssociationItem { 101 | name: string; 102 | field: OrigDetailedModelProperty 103 | // funcname = string; 104 | getAccessor: string; 105 | setAccessor: string; 106 | hasAccessor: string; 107 | delAccessor: string; 108 | addAccessor: string; 109 | } 110 | 111 | interface InstanceOptions extends OrmNS.ModelOptions { 112 | one_associations: InstanceAssociationItem[] 113 | many_associations: InstanceAssociationItem[] 114 | extend_associations: InstanceAssociationItem[] 115 | association_properties: any 116 | fieldToPropertyMap: any 117 | } 118 | 119 | interface PatchedSyncfiedModelOrInstance { 120 | /** 121 | * @important 122 | * 123 | * methods patchSyncfied by 'fib-orm' 124 | */ 125 | countSync: Function; 126 | firstSync: Function; 127 | lastSync: Function; 128 | allSync: Function; 129 | whereSync: Function; 130 | findSync: Function; 131 | removeSync: Function; 132 | runSync: Function; 133 | } 134 | 135 | interface PatchedSyncfiedInstanceWithDbWriteOperation extends PatchedSyncfiedModelOrInstance { 136 | saveSync: Function; 137 | removeSync: Function; 138 | validateSync: Function; 139 | modelSync: Function; 140 | } 141 | interface PatchedSyncfiedInstanceWithAssociations { 142 | /** 143 | * generated by association, but you don't know what it is 144 | */ 145 | /* getXxx: Function; */ 146 | /* setXxx: Function; */ 147 | /* removeXxx: Function; */ 148 | 149 | /* findByXxx: Function; */ 150 | [associationFunc: string]: Function; 151 | } 152 | 153 | // keep compatible to orig one in 'orm' 154 | interface OrigDetailedModelProperty extends OrmNS.Property { 155 | /** 156 | * text | number | integer | boolean | date | enum | object | point | binary | serial 157 | * view details in https://github.com/dresende/node-orm2/wiki/Model-Properties 158 | */ 159 | type: string 160 | unique?: boolean 161 | } 162 | type OrigModelPropertyDefinition = OrigDetailedModelProperty | 163 | String | Function | Date | Object | any[] 164 | 165 | type OrigAggreteGenerator = (...args: any[]) => OrmNS.IAggregated 166 | interface OrigHooks extends OrmNS.Hooks { 167 | afterAutoFetch?: (next?) => void 168 | } 169 | 170 | export interface FibOrmFixedModel extends OrmNS.Model, OrigHooks, PatchedSyncfiedModelOrInstance { 171 | (): FibOrmFixedModel;// FibOrmFixedModelInstance; 172 | (...ids: any[]): FibOrmFixedModel;// FibOrmFixedModelInstance; 173 | 174 | new (): FibOrmFixedModelInstance; 175 | new (...ids: any[]): FibOrmFixedModelInstance; 176 | 177 | properties: { [property: string]: OrigDetailedModelProperty } 178 | allProperties: { [key: string]: OrigDetailedModelProperty } 179 | 180 | find(): any /* OrmNS.Model|OrmNS.IChainFind */; 181 | 182 | /** 183 | * methods used to add associations 184 | */ 185 | hasOne: (...args: any[]) => any; 186 | hasMany: (...args: any[]) => any; 187 | extendsTo: (...args: any[]) => OrmNS.Model; 188 | 189 | extends: { [extendModel: string]: ExtendModelWrapper }; 190 | 191 | [extraProperty: string]: any; 192 | } 193 | 194 | export interface ExtendModelWrapper { 195 | // 'hasOne', 'hasMany' 196 | type: string; 197 | reversed?: boolean; 198 | model: FibOrmFixedExtendModel; 199 | } 200 | 201 | export interface FibOrmFixedExtendModel extends FibOrmFixedModel { 202 | model_name: string; 203 | } 204 | 205 | export interface FibOrmFindLikeQueryObject { 206 | [key: string]: any; 207 | } 208 | 209 | // patch the missing field defined in orm/lib/Instance.js (such as defined by Object.defineProperty) 210 | export interface FibOrmFixedModelInstance extends OrmNS.Instance { 211 | /* all fixed: start */ 212 | on(event: string, callback): FibOrmFixedModelInstance; 213 | save(): Instance; 214 | save(data: { [property: string]: any; }, callback: (err: Error) => void): FibOrmFixedModelInstance; 215 | save(data: { [property: string]: any; }, options: any, callback: (err: Error) => void): FibOrmFixedModelInstance; 216 | saved: boolean; 217 | remove(callback: (err: Error) => void): FibOrmFixedModelInstance; 218 | isInstance: boolean; 219 | isPersisted: boolean; 220 | isShell: boolean; 221 | validate(callback: (errors: Error[]) => void); 222 | /* all fixed: end */ 223 | 224 | /* missing fix: start */ 225 | set: Function; 226 | markAsDirty: Function; 227 | dirtyProperties: object; 228 | __singleton_uid: string | number; 229 | __opts?: InstanceOptions; 230 | model: FibOrmFixedModel; 231 | 232 | /* missing fix: end */ 233 | 234 | [extraProperty: string]: any; 235 | } 236 | 237 | export interface FibOrmPatchedSyncfiedInstantce extends PatchedSyncfiedInstanceWithDbWriteOperation, PatchedSyncfiedInstanceWithAssociations { 238 | } 239 | 240 | export interface FibOrmPatchedSyncfiedDueToAggregationInstance { 241 | /* function getXxx() */ 242 | } 243 | 244 | // export type FibOrmObjectToPatch = 245 | // FibOrmFixedModel | FibOrmFixedModelInstance 246 | // | FibOrmPatchedSyncfiedInstantce | PatchedSyncfiedInstanceWithDbWriteOperation | PatchedSyncfiedInstanceWithAssociations 247 | 248 | export interface IChainFibORMFind extends PatchedSyncfiedModelOrInstance, SqlQueryNS.SelectQuery { 249 | only(args: string|string[]): IChainFibORMFind; 250 | only(...args: string[]): IChainFibORMFind; 251 | order(...order: string[]): IChainFibORMFind; 252 | } 253 | /* Orm About Patch :end */ 254 | } 255 | -------------------------------------------------------------------------------- /@types/orm_mirror/orm.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module "orm" { 4 | 5 | import events = require('events'); 6 | import sqlquery = require('sqlquery'); 7 | 8 | module orm { 9 | 10 | /** 11 | * Parameter Type Interfaces 12 | **/ 13 | 14 | export interface Model { 15 | (): Instance; 16 | (...ids: any[]): Instance; 17 | 18 | properties: { [property: string]: Property }; 19 | settings: Settings; 20 | 21 | drop(callback?: (err: Error) => void): Model; 22 | sync(callback?: (err: Error) => void): Model; 23 | get(...args: any[]): Model; 24 | find(conditions: { [property: string]: any }, callback: (err: Error, results: Instance[]) => void): Model; 25 | find(conditions: { [property: string]: any }, options: { 26 | limit?: number; 27 | order?: any; 28 | }, callback: (err: Error, results: Instance[]) => void): Model; 29 | find(conditions: { [property: string]: any }, limit: number, order: string[], callback: (err: Error, results: Instance[]) => void): Model; 30 | find(conditions: { [property: string]: any }): IChainFind; 31 | 32 | all(conditions: { [property: string]: any }, callback: (err: Error, results: Instance[]) => void): Model; 33 | all(conditions: { [property: string]: any }, options: { 34 | limit?: number; 35 | order?: any; 36 | }, callback: (err: Error, results: Instance[]) => void): Model; 37 | all(conditions: { [property: string]: any }, limit: number, order: string[], callback: (err: Error, results: Instance[]) => void): Model; 38 | 39 | one(conditions: { [property: string]: any }, callback: (err: Error, result: Instance) => void): Model; 40 | one(conditions: { [property: string]: any }, options: { 41 | limit?: number; 42 | order?: any; 43 | }, callback: (err: Error, result: Instance) => void): Model; 44 | one(conditions: { [property: string]: any }, limit: number, order: string[], callback: (err: Error, result: Instance) => void): Model; 45 | 46 | count(callback: (err: Error, count: number) => void): Model; 47 | count(conditions: { [property: string]: any }, callback: (err: Error, count: number) => void): Model; 48 | 49 | aggregate(conditions: { [property: string]: any }): IAggregated; 50 | aggregate(properties: string[]): IAggregated; 51 | aggregate(conditions: { [property: string]: any }, properties: string[]): IAggregated; 52 | 53 | exists(id: any, callback: (err: Error, exists: boolean) => void): Model; 54 | exists(...args: any[]): Model; 55 | 56 | create(data: { [property: string]: any; }, callback: (err: Error, instance: Instance) => void): Model; 57 | create(...args: any[]): Model; 58 | 59 | clear(): Model; 60 | 61 | table: string; 62 | id: string[]; 63 | 64 | [property: string]: any; 65 | } 66 | 67 | export interface Instance { 68 | on(event: string, callback): Instance; 69 | save(): Instance; 70 | save(data: { [property: string]: any; }, callback: (err: Error) => void): Instance; 71 | save(data: { [property: string]: any; }, options: any, callback: (err: Error) => void): Instance; 72 | saved: boolean; 73 | remove(callback: (err: Error) => void): Instance; 74 | isInstance: boolean; 75 | isPersisted: boolean; 76 | isShell: boolean; 77 | validate(callback: (errors: Error[]) => void); 78 | model: Model; 79 | 80 | [property: string]: any; 81 | } 82 | 83 | export interface ModelOptions { 84 | id?: string[]; 85 | autoFetch?: boolean; 86 | autoFetchLimit?: number; 87 | cacheFetch?: boolean; 88 | hooks?: { [property: string]: Hooks }; 89 | methods?: { [name: string]: Function }; 90 | } 91 | 92 | export interface Hooks { 93 | beforeValidation?: (next?) => void; 94 | beforeCreate?: (next?) => void; 95 | afterCreate?: (next?) => void; 96 | beforeSave?: (next?) => void; 97 | afterSave?: (next?) => void; 98 | afterLoad?: (next?) => void; 99 | afterAutoFetch?: (next?) => void; 100 | beforeRemove?: (next?) => void; 101 | afterRemove?: (next?) => void; 102 | } 103 | 104 | export interface IConnectionOptions { 105 | protocol: string; 106 | host?: string; 107 | port?: number; 108 | auth?: string; 109 | username?: string; 110 | password?: string; 111 | database?: string; 112 | pool?: boolean; 113 | debug?: boolean; 114 | } 115 | 116 | export interface IAggregated { 117 | groupBy(...columns: string[]): IAggregated; 118 | limit(limit: number): IAggregated; 119 | limit(offset: number, limit: number): IAggregated; 120 | order(...order: string[]): IAggregated; 121 | select(columns: string[]): IAggregated; 122 | select(...columns: string[]): IAggregated; 123 | as(alias: string): IAggregated; 124 | call(fun: string, args: any[]): IAggregated; 125 | get(callback: (err: Error, instance: Instance) => void); 126 | } 127 | 128 | export interface IChainFind { 129 | find(conditions: { [property: string]: any }): IChainFind; 130 | only(...args: string[]): IChainFind; 131 | limit(limit: number): IChainFind; 132 | offset(offset: number): IChainFind; 133 | run(callback: (err: Error, results: Instance[]) => void): void; 134 | count(callback: (err: Error, count: number) => void): void; 135 | remove(callback: (err: Error) => void): void; 136 | save(callback: (err: Error) => void): void; 137 | each(callback: (result: Instance) => void): void; 138 | each(): IChainFind; 139 | filter(callback: (result: Instance) => boolean): IChainFind; 140 | sort(callback: (a: Instance, b: Instance) => boolean): IChainFind; 141 | get(callback: (results: Instance[]) => void): IChainFind; 142 | } 143 | 144 | /* 145 | * Classes 146 | */ 147 | 148 | export class ORM extends events.EventEmitter { 149 | validators: enforce; 150 | enforce: enforce; 151 | settings: Settings; 152 | driver_name: string; 153 | driver: any; 154 | tools: any; 155 | models: { [key: string]: Model }; 156 | plugins: Plugin[]; 157 | 158 | use(plugin: string, options?: any): ORM; 159 | use(plugin: Plugin, options?: any): ORM; 160 | 161 | define(name: string, properties: { [key: string]: Property }, opts?: ModelOptions): Model; 162 | ping(callback: (err: Error) => void): ORM; 163 | close(callback: (err: Error) => void): ORM; 164 | load(file: string, callback: (err: Error) => void): any; 165 | sync(callback: (err: Error) => void): ORM; 166 | drop(callback: (err: Error) => void): ORM; 167 | } 168 | 169 | export class enforce { 170 | static required(message?: string); 171 | static notEmptyString(message?: string); 172 | static rangeNumber(min: number, max: number, message?: string); 173 | static rangeLength(min: number, max: number, message?: string); 174 | static insideList(inside: string[], message?: string); 175 | static insideList(inside: number[], message?: string); 176 | static outsideList(outside: string[], message?: string); 177 | static outsideList(outside: number[], message?: string); 178 | static password(conditions?: string, message?: string); 179 | static patterns(expr: RegExp, message?: string); 180 | static patterns(expr: string, flags: string, message?: string); 181 | static equalToProperty(name: string, message?: string); 182 | static unique(message?: string); 183 | static unique(opts: { ignoreCase: boolean }, message?: string); 184 | } 185 | 186 | export function equalToProperty(name: string, message?: string); 187 | export function unique(message?: string); 188 | export function unique(opts: { ignoreCase: boolean }, message?: string); 189 | 190 | export class singleton { 191 | static clear(key?: string): singleton; 192 | static get(key, opts: { 193 | identityCache?: any; 194 | saveCheck?: boolean; 195 | }, createCb: Function, returnCb: Function); 196 | } 197 | 198 | export class Settings { 199 | static Container: any; 200 | 201 | static defaults(): { 202 | properties: { 203 | primary_key: string; 204 | association_key: string; 205 | required: boolean; 206 | }; 207 | 208 | instance: { 209 | identityCache: boolean; 210 | identityCacheSaveCheck: boolean; 211 | autoSave: boolean; 212 | autoFetch: boolean; 213 | autoFetchLimit: number; 214 | cascadeRemove: boolean; 215 | returnAllErrors: boolean; 216 | }; 217 | 218 | connection: { 219 | reconnect: boolean; 220 | poll: boolean; 221 | debug: boolean; 222 | }; 223 | }; 224 | 225 | constructor(settings: any); 226 | 227 | //[key: string]: { 228 | // get: (key, def) => any; 229 | // set: (key, value) => Settings; 230 | // unset: (...keys: string[]) => Settings; 231 | //} 232 | 233 | } 234 | 235 | export var settings: Settings; 236 | 237 | export class Property { 238 | static normalize(property: string, settings: Settings): any; 239 | static validate(value: any, property: string): any; 240 | } 241 | 242 | export interface ErrorCodes { 243 | QUERY_ERROR: number; 244 | NOT_FOUND: number; 245 | NOT_DEFINED: number; 246 | NO_SUPPORT: number; 247 | MISSING_CALLBACK: number; 248 | PARAM_MISMATCH: number; 249 | CONNECTION_LOST: number; 250 | 251 | generateError(code: number, message: string, extra: any): Error; 252 | } 253 | 254 | export function Text(type: string): sqlquery.TextQuery; 255 | export function eq(value: any): sqlquery.Comparator; 256 | export function ne(value: any): sqlquery.Comparator; 257 | export function gt(value: any): sqlquery.Comparator; 258 | export function gte(value: any): sqlquery.Comparator; 259 | export function lt(value: any): sqlquery.Comparator; 260 | export function lte(value: any): sqlquery.Comparator; 261 | export function like(value: string): sqlquery.Comparator; 262 | export function between(a: number, b: number): sqlquery.Comparator; 263 | export function not_between(a: number, b: number): sqlquery.Comparator; 264 | export function express(uri: string, handlers: { 265 | define(db: ORM, models: { [key: string]: Model }); 266 | }): (req, res, next) => void; 267 | export function use(connection, protocol: string, options, callback: (err: Error, db?: ORM) => void); 268 | export function connect(uri: string): ORM; 269 | export function connect(uri: string, callback: (err: Error, db: ORM) => void); 270 | export function connect(options: IConnectionOptions): ORM; 271 | export function connect(options: IConnectionOptions, callback: (err: Error, db: ORM) => void); 272 | } 273 | 274 | export = orm; 275 | } 276 | -------------------------------------------------------------------------------- /@types/orm_mirror/sql-query.d.ts: -------------------------------------------------------------------------------- 1 | declare module "sqlquery" { 2 | module sqlquery { 3 | export class Query { 4 | constructor(dialect: string); 5 | constructor(options: { 6 | dialect: string; 7 | }); 8 | static Text(type: string): TextQuery; 9 | 10 | static Comparators: string[]; 11 | static between(a: number, b: number): Comparator; 12 | static not_between(a: number, b: number): Comparator; 13 | static like(expression: string): Comparator; 14 | static eq(value: any): Comparator; 15 | static ne(value: any): Comparator; 16 | static gt(value: any): Comparator; 17 | static gte(value: any): Comparator; 18 | static lt(value: any): Comparator; 19 | static lte(value: any): Comparator; 20 | 21 | escapeId(id: string): string; 22 | escapeId(id: string, table: string): string; 23 | escapeVal(value: any): string; 24 | select(): SelectQuery; 25 | insert(): InsertQuery; 26 | update(): UpdateQuery; 27 | remove(): RemoveQuery; 28 | } 29 | 30 | export interface Comparator { 31 | sql_comparator(): string; 32 | from?: any; 33 | to?: any; 34 | expr?: string; 35 | value?: any; 36 | } 37 | 38 | export interface TextQuery { 39 | data: any; 40 | type: string; 41 | } 42 | 43 | export interface SelectQuery { 44 | select(fields: string): SelectQuery; 45 | calculateFoundRows: SelectQuery; 46 | as(alias: string): SelectQuery; 47 | fun(fun: string, column: string, alias: string): SelectQuery; 48 | from(table: string, from_id: string, to_id: string): SelectQuery; 49 | from(table: string, from_id: string, to_table: string, to_id: string): SelectQuery; 50 | where(...args: any[]): SelectQuery; 51 | whereExists(table: string, table_link: string, link: string, conditions: { [column: string]: any }): SelectQuery; 52 | groupBy(...columns: string[]): SelectQuery; 53 | offset(offset: number): SelectQuery; 54 | limit(limit: number): SelectQuery; 55 | order(column: string, direction: string): SelectQuery; 56 | build(): string; 57 | } 58 | 59 | export interface InsertQuery { 60 | into(table: string): InsertQuery; 61 | set(values: { [key: string]: any }[]): InsertQuery; 62 | build(): string; 63 | } 64 | 65 | export interface UpdateQuery { 66 | into(table: string): UpdateQuery; 67 | set(values: { [column: string]: any }): UpdateQuery; 68 | where(...conditions: { [column: string]: any }[]): UpdateQuery; 69 | build(): string; 70 | } 71 | 72 | export interface RemoveQuery { 73 | from(table: string): RemoveQuery; 74 | where(...conditions: { [column: string]: any }[]): RemoveQuery; 75 | build(): string; 76 | } 77 | } 78 | export = sqlquery; 79 | } 80 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Diogo Resende and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/bundle.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const fibTypify = require('fib-typify') 5 | 6 | const baseDir = path.resolve(__dirname, '../src') 7 | const distDir = path.resolve(__dirname, '../lib') 8 | 9 | fibTypify.compileDirectoryTo(baseDir, distDir, { 10 | compilerOptions: { 11 | target: 'es6', 12 | module: 'commonjs' 13 | } 14 | }) -------------------------------------------------------------------------------- /build/orm_mirror.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env fibjs 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | const filepathList = [ 7 | 'orm.d.ts', 8 | 'sql-query.d.ts' 9 | ] 10 | 11 | const distDir = path.resolve(__dirname, '../@types/orm_mirror') 12 | if (!fs.exists(distDir)) { 13 | fs.mkdir(distDir) 14 | } 15 | 16 | filepathList.forEach((filepath) => { 17 | fs.copy( 18 | path.resolve(__dirname, '../node_modules/orm/lib/TypeScript', filepath), 19 | path.join(distDir, filepath) 20 | ) 21 | }) -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const vm = require("vm"); 4 | const patch = require('./patch'); 5 | const sbox = new vm.SandBox({ 6 | util: require('util'), 7 | events: require('events'), 8 | url: require('url'), 9 | sqlite3: require('./modules/sqlite3'), 10 | mysql: require('./modules/mysql') 11 | }); 12 | const orm = sbox.require('orm', __filename); 13 | module.exports = patch(orm); 14 | -------------------------------------------------------------------------------- /lib/modules/mysql.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | /// 4 | Object.defineProperty(exports, "__esModule", { value: true }); 5 | const db = require("db"); 6 | class Database { 7 | constructor(connOpts) { 8 | this.opts = connOpts; 9 | } 10 | on(ev) { } 11 | ping(cb) { 12 | setImmediate(cb); 13 | } 14 | connect(cb) { 15 | const that = this; 16 | const openMySQL = db.openMySQL; 17 | openMySQL(this.opts, function (e, conn) { 18 | if (!e) 19 | that.conn = conn; 20 | cb(e); 21 | }); 22 | } 23 | query(sql, cb) { 24 | this.conn.execute(sql, cb); 25 | } 26 | execute(sql) { 27 | return this.conn.execute(sql); 28 | } 29 | end(cb) { 30 | this.conn.close(cb); 31 | } 32 | } 33 | exports.createConnection = function (connOpts) { 34 | return new Database(connOpts); 35 | }; 36 | -------------------------------------------------------------------------------- /lib/modules/sqlite3.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | const db = require("db"); 5 | class Database { 6 | constructor(fname) { 7 | this.conn = db.openSQLite(fname); 8 | } 9 | on(ev) { } 10 | all(sql, cb) { 11 | this.conn.execute(sql, cb); 12 | } 13 | get(sql, cb) { 14 | this.all(sql, function (e, r) { 15 | if (e) 16 | cb(e); 17 | cb(e, r[0]); 18 | }); 19 | } 20 | execute(sql) { 21 | return this.conn.execute(sql); 22 | } 23 | close() { 24 | this.conn.close(); 25 | } 26 | } 27 | exports.Database = Database; 28 | -------------------------------------------------------------------------------- /lib/patch.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | const util = require("util"); 5 | const url = require("url"); 6 | // patch async function to sync function 7 | function patchSync(o, funcs) { 8 | funcs.forEach(function (func) { 9 | const old_func = o[func]; 10 | if (old_func) { 11 | Object.defineProperty(o, func + 'Sync', { 12 | value: util.sync(old_func), 13 | writable: true 14 | }); 15 | } 16 | }); 17 | } 18 | // hook find, patch result 19 | function patchResult(o) { 20 | var old_func = o.find; 21 | var m = o.model || o; 22 | var comps = ['val', 'from', 'to']; 23 | if (old_func.is_new) 24 | return; 25 | /** 26 | * filter the Date-Type SelectQuery Property corresponding item when call find-like executor ('find', 'get', 'where') 27 | * @param opt 28 | */ 29 | function filter_date(opt) { 30 | for (var k in opt) { 31 | if (k === 'or') 32 | opt[k].forEach(filter_date); 33 | else { 34 | var p = m.allProperties[k]; 35 | if (p && p.type === 'date') { 36 | var v = opt[k]; 37 | if (!util.isDate(v)) { 38 | if (util.isNumber(v) || util.isString(v)) 39 | opt[k] = new Date(v); 40 | else if (util.isObject(v)) { 41 | comps.forEach(c => { 42 | var v1 = v[c]; 43 | if (util.isArray(v1)) { 44 | v1.forEach((v2, i) => { 45 | if (!util.isDate(v2)) 46 | v1[i] = new Date(v2); 47 | }); 48 | } 49 | else if (v1 !== undefined && !util.isDate(v1)) { 50 | v[c] = new Date(v1); 51 | } 52 | }); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | } 59 | var new_func = function () { 60 | var opt = arguments[0]; 61 | if (util.isObject(opt) && !util.isFunction(opt)) { 62 | /** filter opt to make Date-Type SelectQuery Property corresponding item */ 63 | filter_date(opt); 64 | } 65 | var rs = old_func.apply(this, Array.prototype.slice.apply(arguments)); 66 | if (rs) { 67 | patchResult(rs); 68 | patchSync(rs, [ 69 | "count", 70 | "first", 71 | "last", 72 | 'all', 73 | 'where', 74 | 'find', 75 | 'remove', 76 | 'run' 77 | ]); 78 | } 79 | return rs; 80 | }; 81 | new_func.is_new = true; 82 | o.where = o.all = o.find = new_func; 83 | } 84 | function patchObject(m) { 85 | var methods = [ 86 | "save", 87 | "remove", 88 | "validate", 89 | "model" 90 | ]; 91 | function enum_associations(assoc) { 92 | assoc.forEach(function (item) { 93 | if (item.getAccessor) 94 | methods.push(item.getAccessor); 95 | if (item.setAccessor) 96 | methods.push(item.setAccessor); 97 | if (item.hasAccessor) 98 | methods.push(item.hasAccessor); 99 | if (item.delAccessor) 100 | methods.push(item.delAccessor); 101 | if (item.addAccessor) 102 | methods.push(item.addAccessor); 103 | }); 104 | } 105 | // patch associations methods 106 | var opts = m.__opts; 107 | if (opts) { 108 | enum_associations(opts.one_associations); 109 | enum_associations(opts.many_associations); 110 | enum_associations(opts.extend_associations); 111 | enum_associations(opts.association_properties); 112 | for (var f in opts.fieldToPropertyMap) { 113 | if (opts.fieldToPropertyMap[f].lazyload) { 114 | var name = f.charAt(0).toUpperCase() + f.slice(1); 115 | methods.push('get' + name); 116 | methods.push('set' + name); 117 | methods.push('remove' + name); 118 | } 119 | } 120 | ; 121 | } 122 | patchSync(m, methods); 123 | } 124 | function patchHas(m, funcs) { 125 | funcs.forEach(function (func) { 126 | var old_func = m[func]; 127 | if (old_func) 128 | m[func] = function () { 129 | var r = old_func.apply(this, Array.prototype.slice.apply(arguments)); 130 | var name = arguments[0]; 131 | name = 'findBy' + name.charAt(0).toUpperCase() + name.slice(1); 132 | patchSync(this, [name]); 133 | return r; 134 | }; 135 | }); 136 | } 137 | function patchAggregate(m) { 138 | var aggregate = m.aggregate; 139 | m.aggregate = function () { 140 | var r = aggregate.apply(this, Array.prototype.slice.apply(arguments)); 141 | patchSync(r, ['get']); 142 | return r; 143 | }; 144 | } 145 | function patchModel(m, opts) { 146 | var _afterAutoFetch; 147 | if (opts !== undefined && opts.hooks) 148 | _afterAutoFetch = opts.hooks.afterAutoFetch; 149 | m.afterAutoFetch(function (next) { 150 | patchObject(this); 151 | if (_afterAutoFetch) { 152 | if (_afterAutoFetch.length > 0) 153 | return _afterAutoFetch(next); 154 | _afterAutoFetch(); 155 | } 156 | next(); 157 | }); 158 | patchResult(m); 159 | patchSync(m, [ 160 | "clear", 161 | "count", 162 | "exists", 163 | "one", 164 | "where", 165 | 'all', 166 | 'create', 167 | 'drop', 168 | 'find', 169 | 'get', 170 | 'sync' 171 | ]); 172 | patchHas(m, [ 173 | 'hasOne', 174 | 'extendsTo' 175 | ]); 176 | patchAggregate(m); 177 | } 178 | function patchInsert(table, data, keyProperties, cb) { 179 | var q = this.query.insert() 180 | .into(table) 181 | .set(data) 182 | .build(); 183 | this.db.all(q, function (err, info) { 184 | if (err) 185 | return cb(err); 186 | if (!keyProperties) 187 | return cb(null); 188 | var i, ids = {}, prop; 189 | if (keyProperties.length == 1 && keyProperties[0].type == 'serial') { 190 | ids[keyProperties[0].name] = info.insertId; 191 | return cb(null, ids); 192 | } 193 | else { 194 | for (i = 0; i < keyProperties.length; i++) { 195 | prop = keyProperties[i]; 196 | // Zero is a valid value for an ID column 197 | ids[prop.name] = data[prop.mapsTo] !== undefined ? data[prop.mapsTo] : null; 198 | } 199 | return cb(null, ids); 200 | } 201 | }.bind(this)); 202 | } 203 | ; 204 | function patchDriver(driver) { 205 | if (driver.dialect === 'sqlite') 206 | driver.insert = patchInsert; 207 | var propertyToValue = driver.propertyToValue; 208 | driver.propertyToValue = function (value, property) { 209 | if (property.type === 'date' && 210 | (util.isNumber(value) || util.isString(value))) 211 | value = new Date(value); 212 | return propertyToValue.call(this, value, property); 213 | }; 214 | var valueToProperty = driver.valueToProperty; 215 | driver.valueToProperty = function (value, property) { 216 | if (property.type === 'date' && 217 | (util.isNumber(value) || util.isString(value))) 218 | value = new Date(value); 219 | return valueToProperty.call(this, value, property); 220 | }; 221 | } 222 | function execQuerySync(query, opt) { 223 | if (arguments.length == 2) 224 | query = this.query.escape(query, opt); 225 | return this.db.execute(query); 226 | } 227 | module.exports = function (orm) { 228 | var conn = util.sync(orm.connect); 229 | orm.connectSync = function (opts) { 230 | if (typeof opts == 'string') 231 | opts = url.parse(opts, true).toJSON(); 232 | else if (typeof opts == 'object') 233 | opts = util.clone(opts); 234 | if (opts.protocol === 'sqlite:' && opts.timezone === undefined) 235 | opts.timezone = 'UTC'; 236 | var db = conn.call(this, opts); 237 | patchSync(db, [ 238 | 'sync', 239 | 'close', 240 | 'drop', 241 | 'ping' 242 | ]); 243 | patchDriver(db.driver); 244 | var def = db.define; 245 | db.define = function (name, properties, opts) { 246 | if (opts !== undefined) { 247 | opts = util.clone(opts); 248 | if (opts.hooks !== undefined) 249 | opts.hooks = util.clone(opts.hooks); 250 | } 251 | var m = def.call(this, name, properties, opts); 252 | patchModel(m, opts); 253 | return m; 254 | }; 255 | db.begin = function () { 256 | return this.driver.db.conn.begin(); 257 | }; 258 | db.commit = function () { 259 | return this.driver.db.conn.commit(); 260 | }; 261 | db.rollback = function () { 262 | return this.driver.db.conn.rollback(); 263 | }; 264 | db.trans = function (func) { 265 | return this.driver.db.conn.trans(func); 266 | }; 267 | db.driver.execQuerySync = execQuerySync; 268 | return db; 269 | }; 270 | return orm; 271 | }; 272 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fib-orm", 3 | "version": "1.4.2", 4 | "description": "Object Relational Mapping for fibjs", 5 | "main": "lib/index.js", 6 | "types": "@types/index.d.ts", 7 | "repository": "git://github.com/fibjs/fib-orm.git", 8 | "author": "Liu Hu ", 9 | "homepage": "https://github.com/fibjs/fib-orm", 10 | "license": "MIT", 11 | "keywords": [ 12 | "orm", 13 | "database", 14 | "fibjs" 15 | ], 16 | "files": [ 17 | "@types", 18 | "lib" 19 | ], 20 | "scripts": { 21 | "ci": "fibjs test", 22 | "bundle": "fibjs ./build/bundle" 23 | }, 24 | "ci": { 25 | "type": "travis", 26 | "version": [ 27 | "0.21.0", 28 | "0.22.0", 29 | "0.23.0", 30 | "0.24.0" 31 | ] 32 | }, 33 | "dependencies": { 34 | "orm": "^3.2.2" 35 | }, 36 | "devDependencies": { 37 | "@fibjs/ci": "^2.0.0", 38 | "@types/fibjs": "github:fibjs/fib-types#v1.0.4", 39 | "fib-typify": "^0.1.0", 40 | "lodash": "^4.17.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import vm = require('vm') 2 | const patch = require('./patch'); 3 | 4 | const sbox = new vm.SandBox({ 5 | util: require('util'), 6 | events: require('events'), 7 | url: require('url'), 8 | sqlite3: require('./modules/sqlite3'), 9 | mysql: require('./modules/mysql') 10 | }); 11 | 12 | const orm = sbox.require('orm', __filename); 13 | 14 | module.exports = patch(orm); -------------------------------------------------------------------------------- /src/modules/index.d.ts: -------------------------------------------------------------------------------- 1 | interface DatabaseBase { 2 | on: (ev) => void; 3 | execute: (sql: string) => void; 4 | 5 | end?: (cb: Function) => void; 6 | close?: () => void; 7 | connect?: (cb: Function) => void 8 | query?: Function; 9 | } -------------------------------------------------------------------------------- /src/modules/mysql.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import db = require('db'); 5 | import OrmNS from 'orm'; 6 | 7 | class Database implements DatabaseBase { 8 | conn: OrmNS.ConnInstanceInOrmConnDriverDB; 9 | opts: OrmNS.OrmConnectionOpts; 10 | 11 | constructor(connOpts) { 12 | this.opts = connOpts; 13 | } 14 | 15 | on(ev) {} 16 | 17 | ping(cb: Function) { 18 | setImmediate(cb); 19 | } 20 | 21 | connect(cb: Function): void { 22 | const that = this; 23 | const openMySQL: Function = db.openMySQL 24 | 25 | openMySQL(this.opts, function (e: Error, conn: OrmNS.ConnInstanceInOrmConnDriverDB) { 26 | if (!e) 27 | that.conn = conn; 28 | cb(e); 29 | }); 30 | } 31 | 32 | query(sql: string, cb: Function) { 33 | this.conn.execute(sql, cb); 34 | } 35 | 36 | execute(sql: string) { 37 | return this.conn.execute(sql); 38 | } 39 | 40 | end(cb: Function) { 41 | this.conn.close(cb); 42 | } 43 | } 44 | 45 | export const createConnection = function (connOpts: OrmNS.OrmConnectionOpts) { 46 | return new Database(connOpts); 47 | }; 48 | -------------------------------------------------------------------------------- /src/modules/sqlite3.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import db = require('db'); 4 | import OrmNS from 'orm'; 5 | 6 | export class Database implements DatabaseBase { 7 | conn: OrmNS.ConnInstanceInOrmConnDriverDB; 8 | 9 | constructor(fname) { 10 | this.conn = db.openSQLite(fname); 11 | } 12 | 13 | on(ev) {} 14 | 15 | all(sql: string, cb: Function) { 16 | this.conn.execute(sql, cb); 17 | } 18 | 19 | get(sql: string, cb: Function) { 20 | this.all(sql, function (e, r) { 21 | if (e) 22 | cb(e); 23 | cb(e, r[0]); 24 | }); 25 | } 26 | 27 | execute(sql: string) { 28 | return this.conn.execute(sql); 29 | } 30 | 31 | close() { 32 | this.conn.close(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/patch.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import OrmNS from 'orm' 4 | import SqlQueryNS from 'sqlquery' 5 | 6 | import * as util from 'util' 7 | import * as url from 'url' 8 | 9 | interface ModelFuncToPatch extends Function { 10 | is_new?: boolean; 11 | } 12 | 13 | type HashOfModelFuncNameToPath = string[]; 14 | 15 | // patch async function to sync function 16 | function patchSync( 17 | o: OrmNS.FibOrmFixedModel | OrmNS.FibOrmFixedModelInstance | OrmNS.FibOrmDB, 18 | funcs: HashOfModelFuncNameToPath 19 | ) { 20 | funcs.forEach(function (func) { 21 | const old_func = o[func]; 22 | if (old_func) { 23 | Object.defineProperty(o, func + 'Sync', { 24 | value: util.sync(old_func), 25 | writable: true 26 | }); 27 | } 28 | }) 29 | } 30 | 31 | // hook find, patch result 32 | function patchResult(o: OrmNS.FibOrmFixedModelInstance | OrmNS.FibOrmFixedModel): void { 33 | var old_func: ModelFuncToPatch = o.find; 34 | var m: OrmNS.FibOrmFixedModel = o.model || o; 35 | var comps = ['val', 'from', 'to']; 36 | 37 | if (old_func.is_new) 38 | return; 39 | 40 | /** 41 | * filter the Date-Type SelectQuery Property corresponding item when call find-like executor ('find', 'get', 'where') 42 | * @param opt 43 | */ 44 | function filter_date(opt) { 45 | for (var k in opt) { 46 | if (k === 'or') 47 | opt[k].forEach(filter_date); 48 | else { 49 | var p = m.allProperties[k]; 50 | if (p && p.type === 'date') { 51 | var v: any = opt[k]; 52 | 53 | if (!util.isDate(v)) { 54 | if (util.isNumber(v) || util.isString(v)) 55 | opt[k] = new Date(v); 56 | else if (util.isObject(v)) { 57 | comps.forEach(c => { 58 | var v1 = v[c]; 59 | 60 | if (util.isArray(v1)) { 61 | v1.forEach((v2, i) => { 62 | if (!util.isDate(v2)) 63 | v1[i] = new Date(v2); 64 | }); 65 | } else if (v1 !== undefined && !util.isDate(v1)) { 66 | v[c] = new Date(v1); 67 | } 68 | }); 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | var new_func: ModelFuncToPatch = function () { 77 | var opt = arguments[0]; 78 | 79 | if (util.isObject(opt) && !util.isFunction(opt)) { 80 | /** filter opt to make Date-Type SelectQuery Property corresponding item */ 81 | filter_date(opt); 82 | } 83 | 84 | var rs: OrmNS.FibOrmFixedModelInstance = old_func.apply(this, Array.prototype.slice.apply(arguments)); 85 | if (rs) { 86 | patchResult(rs); 87 | patchSync(rs, [ 88 | "count", 89 | "first", 90 | "last", 91 | 'all', 92 | 'where', 93 | 'find', 94 | 'remove', 95 | 'run' 96 | ]); 97 | } 98 | return rs; 99 | } 100 | 101 | new_func.is_new = true; 102 | o.where = o.all = o.find = new_func; 103 | } 104 | 105 | function patchObject(m: OrmNS.FibOrmFixedModelInstance) { 106 | var methods = [ 107 | "save", 108 | "remove", 109 | "validate", 110 | "model" 111 | ]; 112 | 113 | function enum_associations(assoc: OrmNS.InstanceAssociationItem[]) { 114 | assoc.forEach(function (item) { 115 | if (item.getAccessor) 116 | methods.push(item.getAccessor); 117 | if (item.setAccessor) 118 | methods.push(item.setAccessor); 119 | if (item.hasAccessor) 120 | methods.push(item.hasAccessor); 121 | if (item.delAccessor) 122 | methods.push(item.delAccessor); 123 | if (item.addAccessor) 124 | methods.push(item.addAccessor); 125 | }); 126 | } 127 | 128 | // patch associations methods 129 | var opts = m.__opts; 130 | if (opts) { 131 | enum_associations(opts.one_associations); 132 | enum_associations(opts.many_associations); 133 | enum_associations(opts.extend_associations); 134 | enum_associations(opts.association_properties); 135 | 136 | 137 | for (var f in opts.fieldToPropertyMap) { 138 | if (opts.fieldToPropertyMap[f].lazyload) { 139 | var name = f.charAt(0).toUpperCase() + f.slice(1); 140 | methods.push('get' + name); 141 | methods.push('set' + name); 142 | methods.push('remove' + name); 143 | } 144 | }; 145 | } 146 | 147 | patchSync(m, methods); 148 | } 149 | 150 | function patchHas(m: OrmNS.FibOrmFixedModel, funcs: HashOfModelFuncNameToPath) { 151 | funcs.forEach(function (func) { 152 | var old_func: ModelFuncToPatch = m[func]; 153 | if (old_func) 154 | m[func] = function () { 155 | var r = old_func.apply(this, Array.prototype.slice.apply(arguments)); 156 | 157 | var name = arguments[0]; 158 | name = 'findBy' + name.charAt(0).toUpperCase() + name.slice(1); 159 | patchSync(this, [name]); 160 | 161 | return r; 162 | } 163 | }) 164 | } 165 | 166 | function patchAggregate(m: OrmNS.FibOrmFixedModel) { 167 | var aggregate: OrmNS.OrigAggreteGenerator = m.aggregate; 168 | m.aggregate = function () { 169 | var r = aggregate.apply(this, Array.prototype.slice.apply(arguments)); 170 | patchSync(r, ['get']); 171 | return r; 172 | }; 173 | } 174 | 175 | function patchModel(m: OrmNS.FibOrmFixedModel, opts: OrmNS.ModelOptions) { 176 | var _afterAutoFetch; 177 | if (opts !== undefined && opts.hooks) 178 | _afterAutoFetch = opts.hooks.afterAutoFetch; 179 | 180 | m.afterAutoFetch(function (next) { 181 | patchObject(this as OrmNS.FibOrmFixedModelInstance); 182 | 183 | if (_afterAutoFetch) { 184 | if (_afterAutoFetch.length > 0) 185 | return _afterAutoFetch(next); 186 | _afterAutoFetch(); 187 | } 188 | 189 | next(); 190 | }); 191 | 192 | patchResult(m); 193 | 194 | patchSync(m, [ 195 | "clear", 196 | "count", 197 | "exists", 198 | "one", 199 | "where", 200 | 'all', 201 | 'create', 202 | 'drop', 203 | 'find', 204 | 'get', 205 | 'sync' 206 | ]); 207 | 208 | patchHas(m, [ 209 | 'hasOne', 210 | 'extendsTo' 211 | ]); 212 | 213 | patchAggregate(m); 214 | } 215 | 216 | interface keyPropertiesTypeItem { 217 | type: string; 218 | name: string; 219 | } 220 | function patchInsert(table: string, data: any, keyProperties: keyPropertiesTypeItem[], cb: Function) { 221 | var q = this.query.insert() 222 | .into(table) 223 | .set(data) 224 | .build(); 225 | 226 | this.db.all(q, function (err, info) { 227 | if (err) return cb(err); 228 | if (!keyProperties) return cb(null); 229 | 230 | var i, ids = {}, 231 | prop; 232 | 233 | if (keyProperties.length == 1 && keyProperties[0].type == 'serial') { 234 | ids[keyProperties[0].name] = info.insertId; 235 | return cb(null, ids); 236 | } else { 237 | for (i = 0; i < keyProperties.length; i++) { 238 | prop = keyProperties[i]; 239 | // Zero is a valid value for an ID column 240 | ids[prop.name] = data[prop.mapsTo] !== undefined ? data[prop.mapsTo] : null; 241 | } 242 | return cb(null, ids); 243 | } 244 | }.bind(this)); 245 | }; 246 | 247 | function patchDriver(driver: OrmNS.OrigOrmConnDriver) { 248 | if (driver.dialect === 'sqlite') 249 | driver.insert = patchInsert; 250 | 251 | var propertyToValue = driver.propertyToValue; 252 | driver.propertyToValue = function (value, property) { 253 | if (property.type === 'date' && 254 | (util.isNumber(value) || util.isString(value))) 255 | value = new Date(value); 256 | return propertyToValue.call(this, value, property); 257 | } 258 | 259 | var valueToProperty = driver.valueToProperty; 260 | driver.valueToProperty = function (value, property) { 261 | if (property.type === 'date' && 262 | (util.isNumber(value) || util.isString(value))) 263 | value = new Date(value); 264 | return valueToProperty.call(this, value, property); 265 | } 266 | } 267 | 268 | function execQuerySync(query: SqlQueryNS.Query, opt) { 269 | if (arguments.length == 2) 270 | query = this.query.escape(query, opt); 271 | 272 | return this.db.execute(query); 273 | } 274 | 275 | module.exports = function (orm: typeof OrmNS) { 276 | var conn = util.sync(orm.connect); 277 | orm.connectSync = function (opts: OrmNS.FibORMIConnectionOptions) { 278 | if (typeof opts == 'string') 279 | opts = url.parse(opts, true).toJSON(); 280 | else if (typeof opts == 'object') 281 | opts = util.clone(opts); 282 | 283 | if (opts.protocol === 'sqlite:' && opts.timezone === undefined) 284 | opts.timezone = 'UTC'; 285 | 286 | var db: OrmNS.FibOrmDB = conn.call(this, opts); 287 | 288 | patchSync(db, [ 289 | 'sync', 290 | 'close', 291 | 'drop', 292 | 'ping' 293 | ]); 294 | 295 | patchDriver(db.driver); 296 | 297 | var def = db.define; 298 | db.define = function (name: string, properties: OrmNS.Property, opts: OrmNS.ModelOptions) { 299 | if (opts !== undefined) { 300 | opts = util.clone(opts); 301 | if (opts.hooks !== undefined) 302 | opts.hooks = util.clone(opts.hooks); 303 | } 304 | 305 | var m: OrmNS.FibOrmFixedModel = def.call(this, name, properties, opts); 306 | patchModel(m, opts); 307 | return m; 308 | } 309 | 310 | db.begin = function () { 311 | return this.driver.db.conn.begin(); 312 | }; 313 | 314 | db.commit = function () { 315 | return this.driver.db.conn.commit(); 316 | }; 317 | 318 | db.rollback = function () { 319 | return this.driver.db.conn.rollback(); 320 | }; 321 | 322 | db.trans = function (func) { 323 | return this.driver.db.conn.trans(func); 324 | }; 325 | 326 | db.driver.execQuerySync = execQuerySync; 327 | 328 | return db; 329 | } 330 | 331 | return orm; 332 | } 333 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/fibjs 2 | 3 | var test = require("test"); 4 | test.setup(); 5 | 6 | function t() { 7 | run('./integration/association-extend.js'); 8 | run('./integration/association-hasmany-extra.js'); 9 | run('./integration/association-hasmany-hooks.js'); 10 | run('./integration/association-hasmany.js'); 11 | run('./integration/association-hasone.js'); 12 | run('./integration/association-hasone-required.js'); 13 | run('./integration/association-hasone-reverse.js'); 14 | run('./integration/association-hasone-zeroid.js'); 15 | run('./integration/event.js'); 16 | 17 | run('./integration/hook.js'); 18 | 19 | run('./integration/instance.js'); 20 | run('./integration/model-aggregate.js'); 21 | run('./integration/model-clear.js'); 22 | run('./integration/model-count.js'); 23 | run('./integration/model-create.js'); 24 | run('./integration/model-exists.js'); 25 | run('./integration/model-find-chain.js'); 26 | run('./integration/model-find-mapsto.js'); 27 | run('./integration/model-find.js'); 28 | run('./integration/model-get.js'); 29 | run('./integration/model-keys.js'); 30 | run('./integration/model-one.js'); 31 | run('./integration/model-save.js'); 32 | run('./integration/model-sync.js'); 33 | 34 | run('./integration/predefined-validators.js'); 35 | 36 | run('./integration/property-custom.js'); 37 | run('./integration/property-lazyload.js'); 38 | run('./integration/property-maps-to.js'); 39 | run('./integration/property.js'); 40 | 41 | run('./integration/settings.js'); 42 | 43 | run('./integration/smart-types.js'); 44 | 45 | run('./integration/validation.js'); 46 | 47 | run('./integration/date-type.js'); 48 | 49 | test.run(); 50 | } 51 | 52 | t(); -------------------------------------------------------------------------------- /test/integration/association-extend.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.extendsTo()", function () { 5 | var db = null; 6 | var Person = null; 7 | var PersonAddress = null; 8 | 9 | var setup = function () { 10 | return function () { 11 | Person = db.define("person", { 12 | name: String 13 | }); 14 | PersonAddress = Person.extendsTo("address", { 15 | street: String, 16 | number: Number 17 | }); 18 | 19 | ORM.singleton.clear(); 20 | 21 | helper.dropSync([Person, PersonAddress], function () { 22 | var person = Person.createSync({ 23 | name: "John Doe" 24 | }); 25 | person.setAddressSync(new PersonAddress({ 26 | street: "Liberty", 27 | number: 123 28 | })); 29 | }); 30 | }; 31 | }; 32 | 33 | before(function () { 34 | db = helper.connect(); 35 | }); 36 | 37 | after(function () { 38 | return db.closeSync(); 39 | }); 40 | 41 | describe("when calling hasAccessor", function () { 42 | before(setup()); 43 | 44 | it("should return true if found", function () { 45 | var John = Person.find().firstSync(); 46 | var hasAddress = John.hasAddressSync(); 47 | assert.equal(hasAddress, true); 48 | }); 49 | 50 | it("should return false if not found", function () { 51 | var John = Person.find().firstSync(); 52 | 53 | John.removeAddressSync(); 54 | assert.throws(function () { 55 | John.hasAddressSync(); 56 | }) 57 | }); 58 | 59 | it("should return error if instance not with an ID", function () { 60 | var Jane = new Person({ 61 | name: "Jane" 62 | }); 63 | 64 | try { 65 | Jane.hasAddressSync(); 66 | } catch (err) { 67 | assert.propertyVal(err, "code", ORM.ErrorCodes.NOT_DEFINED); 68 | } 69 | }); 70 | }); 71 | 72 | describe("when calling getAccessor", function () { 73 | before(setup()); 74 | 75 | it("should return extension if found", function () { 76 | var John = Person.find().firstSync(); 77 | var Address = John.getAddressSync(); 78 | assert.isObject(Address); 79 | assert.propertyVal(Address, "street", "Liberty"); 80 | }); 81 | 82 | it("should return error if not found", function () { 83 | var John = Person.find().firstSync(); 84 | 85 | John.removeAddressSync(); 86 | 87 | try { 88 | John.getAddressSync(); 89 | } catch (err) { 90 | assert.propertyVal(err, "code", ORM.ErrorCodes.NOT_FOUND); 91 | } 92 | }); 93 | 94 | it("should return error if instance not with an ID", function () { 95 | var Jane = new Person({ 96 | name: "Jane" 97 | }); 98 | 99 | try { 100 | Jane.getAddressSync(); 101 | } catch (err) { 102 | assert.propertyVal(err, "code", ORM.ErrorCodes.NOT_DEFINED); 103 | } 104 | }); 105 | }); 106 | 107 | describe("when calling setAccessor", function () { 108 | before(setup()); 109 | 110 | it("should remove any previous extension", function () { 111 | var John = Person.find().firstSync(); 112 | 113 | var c = PersonAddress.find({ 114 | number: 123 115 | }).countSync(); 116 | 117 | assert.equal(c, 1); 118 | 119 | var addr = new PersonAddress({ 120 | street: "4th Ave", 121 | number: 4 122 | }); 123 | 124 | John.setAddressSync(addr); 125 | 126 | var Address = John.getAddressSync(); 127 | 128 | assert.isObject(Address); 129 | assert.propertyVal(Address, "street", addr.street); 130 | 131 | var c = PersonAddress.find({ 132 | number: 123 133 | }).countSync(); 134 | 135 | assert.equal(c, 0); 136 | }); 137 | }); 138 | 139 | describe("when calling delAccessor", function () { 140 | before(setup()); 141 | 142 | it("should remove any extension", function () { 143 | var John = Person.find().firstSync(); 144 | 145 | var c = PersonAddress.find({ 146 | number: 123 147 | }).countSync(); 148 | assert.equal(c, 1); 149 | 150 | var addr = new PersonAddress({ 151 | street: "4th Ave", 152 | number: 4 153 | }); 154 | 155 | John.removeAddressSync(); 156 | 157 | var c = PersonAddress.find({ 158 | number: 123 159 | }).countSync(); 160 | assert.equal(c, 0); 161 | }); 162 | 163 | it("should return error if instance not with an ID", function () { 164 | var Jane = new Person({ 165 | name: "Jane" 166 | }); 167 | try { 168 | Jane.removeAddressSync(); 169 | } catch (err) { 170 | assert.propertyVal(err, "code", ORM.ErrorCodes.NOT_DEFINED); 171 | } 172 | }); 173 | }); 174 | 175 | describe("findBy()", function () { 176 | before(setup()); 177 | 178 | it("should throw if no conditions passed", function () { 179 | assert.throws(function () { 180 | Person.findByAddressSync(); 181 | }); 182 | }); 183 | 184 | it("should lookup in Model based on associated model properties", function () { 185 | var people = Person.findByAddressSync({ 186 | number: 123 187 | }); 188 | 189 | assert.ok(Array.isArray(people)); 190 | assert.ok(people.length == 1); 191 | }); 192 | 193 | it("should return a ChainFind if no callback passed", function () { 194 | var ChainFind = Person.findByAddress({ 195 | number: 123 196 | }); 197 | assert.isFunction(ChainFind.run); 198 | }); 199 | }); 200 | }); -------------------------------------------------------------------------------- /test/integration/association-hasmany-extra.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("hasMany extra properties", function () { 5 | var db = null; 6 | var Person = null; 7 | var Pet = null; 8 | 9 | var setup = function (opts) { 10 | opts = opts || {}; 11 | return function () { 12 | db.settings.set('instance.identityCache', false); 13 | 14 | Person = db.define('person', { 15 | name: String, 16 | }, opts); 17 | Pet = db.define('pet', { 18 | name: String 19 | }); 20 | Person.hasMany('pets', Pet, { 21 | since: Date, 22 | data: Object 23 | }); 24 | 25 | return helper.dropSync([Person, Pet]); 26 | }; 27 | }; 28 | 29 | before(function () { 30 | db = helper.connect(); 31 | }); 32 | 33 | after(function () { 34 | db.closeSync(); 35 | }); 36 | 37 | describe("if passed to addAccessor", function () { 38 | before(setup()); 39 | 40 | it("should be added to association", function () { 41 | var people = Person.createSync([{ 42 | name: "John" 43 | }]); 44 | 45 | var pets = Pet.createSync([{ 46 | name: "Deco" 47 | }, { 48 | name: "Mutt" 49 | }]); 50 | 51 | var data = { 52 | adopted: true 53 | }; 54 | 55 | people[0].addPetsSync(pets, { 56 | since: new Date(), 57 | data: data 58 | }); 59 | 60 | var John = Person.find({ 61 | name: "John" 62 | }, { 63 | autoFetch: true 64 | }).firstSync(); 65 | 66 | assert.property(John, "pets"); 67 | assert.ok(Array.isArray(pets)); 68 | 69 | assert.equal(John.pets.length, 2); 70 | 71 | assert.property(John.pets[0], "name"); 72 | assert.property(John.pets[0], "extra"); 73 | assert.isObject(John.pets[0].extra); 74 | assert.property(John.pets[0].extra, "since"); 75 | assert.ok(John.pets[0].extra.since instanceof Date); 76 | 77 | assert.equal(typeof John.pets[0].extra.data, 'object'); 78 | assert.equal(JSON.stringify(data), JSON.stringify(John.pets[0].extra.data)); 79 | }); 80 | }); 81 | }); -------------------------------------------------------------------------------- /test/integration/association-hasmany-hooks.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("hasMany hooks", function () { 5 | var db = null; 6 | var Person = null; 7 | var Pet = null; 8 | 9 | var setup = function (props, opts) { 10 | return function () { 11 | db.settings.set('instance.identityCache', false); 12 | 13 | Person = db.define('person', { 14 | name: String, 15 | }); 16 | Pet = db.define('pet', { 17 | name: String 18 | }); 19 | Person.hasMany('pets', Pet, props || {}, opts || {}); 20 | 21 | helper.dropSync([Person, Pet]); 22 | }; 23 | }; 24 | 25 | before(function () { 26 | db = helper.connect(); 27 | }); 28 | 29 | after(function () { 30 | return db.closeSync(); 31 | }); 32 | 33 | describe("beforeSave", function () { 34 | var had_extra = false; 35 | 36 | before(setup({ 37 | born: Date 38 | }, { 39 | hooks: { 40 | beforeSave: function (extra, next) { 41 | had_extra = (typeof extra == "object"); 42 | return next(); 43 | } 44 | } 45 | })); 46 | 47 | it("should pass extra data to hook if extra defined", function () { 48 | var John = Person.createSync({ 49 | name: "John" 50 | }); 51 | var Deco = Pet.createSync({ 52 | name: "Deco" 53 | }); 54 | 55 | John.addPetsSync(Deco); 56 | assert.isTrue(had_extra); 57 | }); 58 | }); 59 | 60 | describe("beforeSave", function () { 61 | var had_extra = false; 62 | 63 | before(setup({}, { 64 | hooks: { 65 | beforeSave: function (next) { 66 | assert.isFunction(next); 67 | return next(); 68 | } 69 | } 70 | })); 71 | 72 | it("should not pass extra data to hook if extra defined", function () { 73 | var John = Person.createSync({ 74 | name: "John" 75 | }); 76 | var Deco = Pet.createSync({ 77 | name: "Deco" 78 | }); 79 | John.addPetsSync(Deco); 80 | }); 81 | }); 82 | 83 | describe("beforeSave", function () { 84 | var had_extra = false; 85 | 86 | before(setup({}, { 87 | hooks: { 88 | beforeSave: function (next) { 89 | setTimeout(function () { 90 | return next(new Error('blocked')); 91 | }, 100); 92 | } 93 | } 94 | })); 95 | 96 | it("should block if error returned", function () { 97 | var John = Person.createSync({ 98 | name: "John" 99 | }); 100 | var Deco = Pet.createSync({ 101 | name: "Deco" 102 | }); 103 | 104 | try { 105 | John.addPetsSync(Deco); 106 | } catch (err) { 107 | assert.equal(err.message, 'blocked'); 108 | } 109 | }); 110 | }); 111 | }); -------------------------------------------------------------------------------- /test/integration/association-hasone-required.js: -------------------------------------------------------------------------------- 1 | var ORM = require('../../'); 2 | var helper = require('../support/spec_helper'); 3 | 4 | describe("hasOne", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var setup = function (required) { 9 | return function () { 10 | db.settings.set('instance.identityCache', false); 11 | db.settings.set('instance.returnAllErrors', true); 12 | 13 | Person = db.define('person', { 14 | name: String 15 | }); 16 | Person.hasOne('parent', Person, { 17 | required: required, 18 | field: 'parentId' 19 | }); 20 | 21 | helper.dropSync(Person); 22 | }; 23 | }; 24 | 25 | before(function () { 26 | db = helper.connect(); 27 | }); 28 | 29 | after(function () { 30 | return db.closeSync(); 31 | }); 32 | 33 | describe("required", function () { 34 | before(setup(true)); 35 | 36 | it("should not accept empty association", function () { 37 | var John = new Person({ 38 | name: "John", 39 | parentId: null 40 | }); 41 | try { 42 | John.saveSync(); 43 | } catch (errors) { 44 | assert.equal(errors.length, 1); 45 | assert.equal(errors[0].type, 'validation'); 46 | assert.equal(errors[0].msg, 'required'); 47 | assert.equal(errors[0].property, 'parentId'); 48 | } 49 | }); 50 | 51 | it("should accept association", function () { 52 | var John = new Person({ 53 | name: "John", 54 | parentId: 1 55 | }); 56 | John.saveSync(); 57 | }); 58 | }); 59 | 60 | describe("not required", function () { 61 | before(setup(false)); 62 | 63 | it("should accept empty association", function () { 64 | var John = new Person({ 65 | name: "John" 66 | }); 67 | John.saveSync(); 68 | }); 69 | 70 | it("should accept null association", function () { 71 | var John = new Person({ 72 | name: "John", 73 | parent_id: null 74 | }); 75 | John.saveSync(); 76 | }); 77 | }); 78 | }); -------------------------------------------------------------------------------- /test/integration/association-hasone-reverse.js: -------------------------------------------------------------------------------- 1 | var ORM = require('../../'); 2 | var helper = require('../support/spec_helper'); 3 | 4 | describe("hasOne", function () { 5 | var db = null; 6 | var Person = null; 7 | var Pet = null; 8 | 9 | var setup = function () { 10 | return function () { 11 | Person = db.define('person', { 12 | name: String 13 | }); 14 | Pet = db.define('pet', { 15 | name: String 16 | }); 17 | Person.hasOne('pet', Pet, { 18 | reverse: 'owners', 19 | field: 'pet_id' 20 | }); 21 | 22 | helper.dropSync([Person, Pet], function () { 23 | // Running in series because in-memory sqlite encounters problems 24 | Person.createSync({ 25 | name: "John Doe" 26 | }); 27 | Person.createSync({ 28 | name: "Jane Doe" 29 | }); 30 | Pet.createSync({ 31 | name: "Deco" 32 | }); 33 | Pet.createSync({ 34 | name: "Fido" 35 | }); 36 | }); 37 | }; 38 | }; 39 | 40 | before(function () { 41 | db = helper.connect(); 42 | }); 43 | 44 | after(function () { 45 | return db.closeSync(); 46 | }); 47 | 48 | describe("reverse", function () { 49 | var removeHookRun = false; 50 | 51 | before(setup({ 52 | hooks: { 53 | beforeRemove: function () { 54 | removeHookRun = true; 55 | } 56 | } 57 | })); 58 | 59 | it("should create methods in both models", function () { 60 | var person = Person(1); 61 | var pet = Pet(1); 62 | 63 | assert.isFunction(person.getPet); 64 | assert.isFunction(person.setPet); 65 | assert.isFunction(person.removePet); 66 | assert.isFunction(person.hasPet); 67 | 68 | assert.isFunction(pet.getOwners); 69 | assert.isFunction(pet.setOwners); 70 | assert.isFunction(pet.hasOwners); 71 | }); 72 | 73 | describe(".getAccessor()", function () { 74 | it("should work", function () { 75 | var John = Person.find({ 76 | name: "John Doe" 77 | }).firstSync(); 78 | var Deco = Pet.find({ 79 | name: "Deco" 80 | }).firstSync(); 81 | var has_owner = Deco.hasOwnersSync(); 82 | assert.isFalse(has_owner); 83 | 84 | Deco.setOwnersSync(John); 85 | 86 | var JohnCopy = Deco.getOwnersSync(); 87 | assert.ok(Array.isArray(JohnCopy)); 88 | assert.deepEqual(John, JohnCopy[0]); 89 | }); 90 | 91 | describe("Chain", function () { 92 | before(function () { 93 | var petParams = [{ 94 | name: "Hippo" 95 | }, 96 | { 97 | name: "Finch", 98 | owners: [{ 99 | name: "Harold" 100 | }, { 101 | name: "Hagar" 102 | }] 103 | }, 104 | { 105 | name: "Fox", 106 | owners: [{ 107 | name: "Nelly" 108 | }, { 109 | name: "Narnia" 110 | }] 111 | } 112 | ]; 113 | 114 | var pets = Pet.createSync(petParams); 115 | assert.equal(pets.length, 3); 116 | 117 | var people = Person.findSync({ 118 | name: ["Harold", "Hagar", "Nelly", "Narnia"] 119 | }); 120 | assert.exist(people); 121 | assert.equal(people.length, 4); 122 | }); 123 | 124 | it("should be returned if no callback is passed", function () { 125 | var pet = Pet.oneSync(); 126 | assert.exist(pet); 127 | 128 | var chain = pet.getOwners(); 129 | 130 | assert.equal(typeof chain, 'object'); 131 | assert.equal(typeof chain.run, 'function'); 132 | }); 133 | 134 | it(".remove() should not call hooks", function () { 135 | var pet = Pet.oneSync({ 136 | name: "Finch" 137 | }); 138 | assert.exist(pet); 139 | 140 | assert.equal(removeHookRun, false); 141 | pet.getOwners().removeSync(); 142 | assert.equal(removeHookRun, false); 143 | 144 | var items = Person.findSync({ 145 | name: "Harold" 146 | }); 147 | assert.equal(items.length, 0); 148 | }); 149 | 150 | }); 151 | }); 152 | 153 | it("should be able to set an array of people as the owner", function () { 154 | var owners = Person.findSync({ 155 | name: ["John Doe", "Jane Doe"] 156 | }); 157 | 158 | var Fido = Pet.find({ 159 | name: "Fido" 160 | }).firstSync(); 161 | 162 | var has_owner = Fido.hasOwnersSync(); 163 | assert.isFalse(has_owner); 164 | 165 | Fido.setOwnersSync(owners); 166 | 167 | var ownersCopy = Fido.getOwnersSync(); 168 | assert.ok(Array.isArray(owners)); 169 | assert.equal(owners.length, 2); 170 | 171 | // Don't know which order they'll be in. 172 | var idProp = 'id' 173 | 174 | if (owners[0][idProp] == ownersCopy[0][idProp]) { 175 | assert.deepEqual(owners[0], ownersCopy[0]); 176 | assert.deepEqual(owners[1], ownersCopy[1]); 177 | } else { 178 | assert.deepEqual(owners[0], ownersCopy[1]); 179 | assert.deepEqual(owners[1], ownersCopy[0]); 180 | } 181 | 182 | }); 183 | 184 | // broken in mongo 185 | describe("findBy()", function () { 186 | before(setup()); 187 | 188 | before(function () { 189 | var jane = Person.oneSync({ 190 | name: "Jane Doe" 191 | }); 192 | var deco = Pet.oneSync({ 193 | name: "Deco" 194 | }); 195 | deco.setOwnersSync(jane); 196 | }); 197 | 198 | it("should throw if no conditions passed", function () { 199 | assert.throws(function () { 200 | Pet.findByOwners(function () {}); 201 | }); 202 | }); 203 | 204 | it("should lookup reverse Model based on associated model properties", function () { 205 | var pets = Pet.findByOwnersSync({ 206 | name: "Jane Doe" 207 | }); 208 | assert.equal(Array.isArray(pets), true); 209 | }); 210 | 211 | it("should return a ChainFind if no callback passed", function () { 212 | var ChainFind = Pet.findByOwners({ 213 | name: "John Doe" 214 | }); 215 | assert.isFunction(ChainFind.run); 216 | }); 217 | }); 218 | }); 219 | 220 | xdescribe("reverse find", function () { 221 | it("should be able to find given an association id", function () { 222 | common.retry(setup(), function () { 223 | Person.find({ 224 | name: "John Doe" 225 | }).first(function (err, John) { 226 | assert.notExist(err); 227 | assert.exist(John); 228 | Pet.find({ 229 | name: "Deco" 230 | }).first(function (err, Deco) { 231 | assert.notExist(err); 232 | assert.exist(Deco); 233 | Deco.hasOwners(function (err, has_owner) { 234 | assert.notExist(err); 235 | assert.isFalse(has_owner); 236 | 237 | Deco.setOwners(John, function (err) { 238 | assert.notExist(err); 239 | 240 | Person.find({ 241 | pet_id: Deco[Pet.id[0]] 242 | }).first(function (err, owner) { 243 | assert.notExist(err); 244 | assert.exist(owner); 245 | assert.equal(owner.name, John.name); 246 | done(); 247 | }); 248 | 249 | }); 250 | }); 251 | }); 252 | }); 253 | }, 3); 254 | }); 255 | 256 | xit("should be able to find given an association instance", function () { 257 | common.retry(setup(), function () { 258 | Person.find({ 259 | name: "John Doe" 260 | }).first(function (err, John) { 261 | assert.notExist(err); 262 | assert.exist(John); 263 | Pet.find({ 264 | name: "Deco" 265 | }).first(function (err, Deco) { 266 | assert.notExist(err); 267 | assert.exist(Deco); 268 | Deco.hasOwners(function (err, has_owner) { 269 | assert.notExist(err); 270 | assert.isFalse(has_owner); 271 | 272 | Deco.setOwners(John, function (err) { 273 | assert.notExist(err); 274 | 275 | Person.find({ 276 | pet: Deco 277 | }).first(function (err, owner) { 278 | assert.notExist(err); 279 | assert.exist(owner); 280 | assert.equal(owner.name, John.name); 281 | done(); 282 | }); 283 | 284 | }); 285 | }); 286 | }); 287 | }); 288 | }, 3); 289 | }); 290 | 291 | xit("should be able to find given a number of association instances with a single primary key", function () { 292 | common.retry(setup(), function () { 293 | Person.find({ 294 | name: "John Doe" 295 | }).first(function (err, John) { 296 | assert.notExist(err); 297 | assert.exist(John); 298 | Pet.all(function (err, pets) { 299 | assert.notExist(err); 300 | assert.exist(pets); 301 | assert.equal(pets.length, 2); 302 | 303 | pets[0].hasOwners(function (err, has_owner) { 304 | assert.notExist(err); 305 | assert.isFalse(has_owner); 306 | 307 | pets[0].setOwners(John, function (err) { 308 | assert.notExist(err); 309 | 310 | Person.find({ 311 | pet: pets 312 | }, function (err, owners) { 313 | assert.notExist(err); 314 | assert.exist(owners); 315 | assert.equal(owners.length, 1); 316 | 317 | assert.equal(owners[0].name, John.name); 318 | done(); 319 | }); 320 | }); 321 | }); 322 | }); 323 | }); 324 | }, 3); 325 | }); 326 | }); 327 | }); -------------------------------------------------------------------------------- /test/integration/association-hasone-zeroid.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("hasOne", function () { 5 | var db = null; 6 | var Person = null; 7 | var Pet = null; 8 | 9 | var setup = function (autoFetch) { 10 | return function () { 11 | db.settings.set('instance.identityCache', false); 12 | db.settings.set('instance.returnAllErrors', true); 13 | 14 | Person = db.define('person', { 15 | id: { 16 | type: "integer", 17 | mapsTo: "personID", 18 | key: true 19 | }, 20 | firstName: { 21 | type: "text", 22 | size: "255" 23 | }, 24 | lastName: { 25 | type: "text", 26 | size: "255" 27 | } 28 | }); 29 | 30 | Pet = db.define('pet', { 31 | id: { 32 | type: "integer", 33 | mapsTo: "petID", 34 | key: true 35 | }, 36 | petName: { 37 | type: "text", 38 | size: "255" 39 | }, 40 | ownerID: { 41 | type: "integer", 42 | size: "4" 43 | } 44 | }); 45 | 46 | Pet.hasOne('owner', Person, { 47 | field: 'ownerID', 48 | autoFetch: autoFetch 49 | }); 50 | 51 | helper.dropSync([Person, Pet], function () { 52 | Pet.createSync([{ 53 | id: 10, 54 | petName: 'Muttley', 55 | owner: { 56 | id: 12, 57 | firstName: 'Stuey', 58 | lastName: 'McG' 59 | } 60 | }, { 61 | id: 11, 62 | petName: 'Snagglepuss', 63 | owner: { 64 | id: 0, 65 | firstName: 'John', 66 | lastName: 'Doe' 67 | } 68 | }]); 69 | }); 70 | }; 71 | }; 72 | 73 | before(function () { 74 | db = helper.connect(); 75 | }); 76 | 77 | after(function () { 78 | return db.closeSync(); 79 | }); 80 | 81 | describe("auto fetch", function () { 82 | before(setup(true)); 83 | 84 | it("should work for non-zero ownerID ", function () { 85 | var pets = Pet.findSync({ 86 | petName: "Muttley" 87 | }); 88 | 89 | assert.equal(pets[0].petName, "Muttley"); 90 | assert.property(pets[0], "id"); 91 | assert.equal(pets[0].id, 10); 92 | assert.equal(pets[0].ownerID, 12); 93 | 94 | assert.property(pets[0], "owner"); 95 | assert.equal(pets[0].owner.firstName, "Stuey"); 96 | }); 97 | 98 | it("should work for zero ownerID ", function () { 99 | var pets = Pet.findSync({ 100 | petName: "Snagglepuss" 101 | }); 102 | 103 | assert.equal(pets[0].petName, "Snagglepuss"); 104 | assert.property(pets[0], "id"); 105 | assert.equal(pets[0].id, 11); 106 | 107 | var people = db.models.person.allSync(); 108 | }); 109 | }); 110 | 111 | describe("no auto fetch", function () { 112 | before(setup(false)); 113 | 114 | it("should work for non-zero ownerID ", function () { 115 | var pets = Pet.findSync({ 116 | petName: "Muttley" 117 | }); 118 | 119 | assert.equal(pets[0].petName, "Muttley"); 120 | assert.property(pets[0], "id"); 121 | assert.equal(pets[0].id, 10); 122 | assert.equal(pets[0].ownerID, 12); 123 | 124 | assert.notProperty(pets[0], "owner"); 125 | 126 | // But we should be able to see if its there 127 | var result = pets[0].hasOwnerSync(); 128 | assert.equal(result, true); 129 | 130 | // ...and then get it 131 | var result = pets[0].getOwnerSync(); 132 | assert.equal(result.firstName, "Stuey"); 133 | }); 134 | 135 | it("should work for zero ownerID ", function () { 136 | var pets = Pet.findSync({ 137 | petName: "Snagglepuss" 138 | }); 139 | 140 | assert.equal(pets[0].petName, "Snagglepuss"); 141 | assert.property(pets[0], "id"); 142 | assert.equal(pets[0].id, 11); 143 | assert.equal(pets[0].ownerID, 0); 144 | 145 | assert.notProperty(pets[0], "owner"); 146 | 147 | // But we should be able to see if its there 148 | var result = pets[0].hasOwnerSync(); 149 | assert.equal(result, true); 150 | 151 | // ...and then get it 152 | result = pets[0].getOwnerSync(); 153 | assert.equal(result.firstName, "John"); 154 | }); 155 | }); 156 | }); -------------------------------------------------------------------------------- /test/integration/association-hasone.js: -------------------------------------------------------------------------------- 1 | var ORM = require('../../'); 2 | var helper = require('../support/spec_helper'); 3 | var _ = require('lodash'); 4 | 5 | describe("hasOne", function () { 6 | var db = null; 7 | var Tree = null; 8 | var Stalk = null; 9 | var Leaf = null; 10 | var leafId = null; 11 | var treeId = null; 12 | var stalkId = null; 13 | var holeId = null; 14 | 15 | var setup = function (opts) { 16 | opts = opts || {}; 17 | return function () { 18 | db.settings.set('instance.identityCache', false); 19 | db.settings.set('instance.returnAllErrors', true); 20 | Tree = db.define("tree", { 21 | type: { 22 | type: 'text' 23 | } 24 | }); 25 | Stalk = db.define("stalk", { 26 | length: { 27 | type: 'integer' 28 | } 29 | }); 30 | var Hole = db.define("hole", { 31 | width: { 32 | type: 'integer' 33 | } 34 | }); 35 | Leaf = db.define("leaf", { 36 | size: { 37 | type: 'integer' 38 | }, 39 | holeId: { 40 | type: 'integer', 41 | mapsTo: 'hole_id' 42 | } 43 | }, { 44 | validations: opts.validations 45 | }); 46 | Leaf.hasOne('tree', Tree, { 47 | field: 'treeId', 48 | autoFetch: !!opts.autoFetch 49 | }); 50 | Leaf.hasOne('stalk', Stalk, { 51 | field: 'stalkId', 52 | mapsTo: 'stalk_id' 53 | }); 54 | Leaf.hasOne('hole', Hole, { 55 | field: 'holeId' 56 | }); 57 | 58 | helper.dropSync([Tree, Stalk, Hole, Leaf], function () { 59 | var tree = Tree.createSync({ 60 | type: 'pine' 61 | }); 62 | treeId = tree[Tree.id]; 63 | 64 | var leaf = Leaf.createSync({ 65 | size: 14 66 | }); 67 | leafId = leaf[Leaf.id]; 68 | leaf.setTreeSync(tree); 69 | 70 | var stalk = Stalk.createSync({ 71 | length: 20 72 | }); 73 | assert.exist(stalk); 74 | stalkId = stalk[Stalk.id]; 75 | 76 | var hole = Hole.createSync({ 77 | width: 3 78 | }); 79 | holeId = hole.id; 80 | }); 81 | }; 82 | }; 83 | 84 | before(function () { 85 | db = helper.connect(); 86 | }); 87 | 88 | after(function () { 89 | db.closeSync(); 90 | }); 91 | 92 | describe("accessors", function () { 93 | before(setup()); 94 | 95 | it("get should get the association", function () { 96 | var leaf = Leaf.oneSync({ 97 | size: 14 98 | }); 99 | assert.exist(leaf); 100 | var tree = leaf.getTreeSync(); 101 | assert.exist(tree); 102 | }); 103 | 104 | it("should return proper instance model", function () { 105 | var leaf = Leaf.oneSync({ 106 | size: 14 107 | }); 108 | var tree = leaf.getTreeSync(); 109 | assert.equal(tree.model(), Tree); 110 | }); 111 | 112 | it("get should get the association with a shell model", function () { 113 | var tree = Leaf(leafId).getTreeSync(); 114 | assert.exist(tree); 115 | assert.equal(tree[Tree.id], treeId); 116 | }); 117 | 118 | it("has should indicate if there is an association present", function () { 119 | var leaf = Leaf.oneSync({ 120 | size: 14 121 | }); 122 | assert.exist(leaf); 123 | 124 | var has = leaf.hasTreeSync(); 125 | assert.equal(has, true); 126 | 127 | has = leaf.hasStalkSync(); 128 | assert.equal(has, false); 129 | }); 130 | 131 | it("set should associate another instance", function () { 132 | var stalk = Stalk.oneSync({ 133 | length: 20 134 | }); 135 | assert.exist(stalk); 136 | 137 | var leaf = Leaf.oneSync({ 138 | size: 14 139 | }); 140 | assert.exist(leaf); 141 | assert.notExist(leaf.stalkId); 142 | 143 | leaf.setStalkSync(stalk); 144 | 145 | var leaf = Leaf.oneSync({ 146 | size: 14 147 | }); 148 | assert.equal(leaf.stalkId, stalk[Stalk.id]); 149 | }); 150 | 151 | it("remove should unassociation another instance", function () { 152 | var stalk = Stalk.oneSync({ 153 | length: 20 154 | }); 155 | assert.exist(stalk); 156 | var leaf = Leaf.oneSync({ 157 | size: 14 158 | }); 159 | assert.exist(leaf); 160 | assert.exist(leaf.stalkId); 161 | leaf.removeStalkSync(); 162 | var leaf = Leaf.oneSync({ 163 | size: 14 164 | }); 165 | assert.equal(leaf.stalkId, null); 166 | }); 167 | }); 168 | 169 | [false, true].forEach(function (af) { 170 | describe("with autofetch = " + af, function () { 171 | before(setup({ 172 | autoFetch: af 173 | })); 174 | 175 | describe("autofetching", function () { 176 | it((af ? "should" : "shouldn't") + " be done", function () { 177 | var leaf = Leaf.oneSync({}); 178 | assert.equal(typeof leaf.tree, af ? 'object' : 'undefined'); 179 | }); 180 | }); 181 | 182 | describe("associating by parent id", function () { 183 | var tree = null; 184 | 185 | before(function () { 186 | tree = Tree.createSync({ 187 | type: "cyprus" 188 | }); 189 | }); 190 | 191 | it("should work when calling Instance.save", function () { 192 | var leaf = new Leaf({ 193 | size: 4, 194 | treeId: tree[Tree.id] 195 | }); 196 | leaf.saveSync(); 197 | 198 | var fetchedLeaf = Leaf.getSync(leaf[Leaf.id]); 199 | assert.exist(fetchedLeaf); 200 | assert.equal(fetchedLeaf.treeId, leaf.treeId); 201 | }); 202 | 203 | it("should work when calling Instance.save after initially setting parentId to null", function () { 204 | var leaf = new Leaf({ 205 | size: 4, 206 | treeId: null 207 | }); 208 | leaf.treeId = tree[Tree.id]; 209 | leaf.saveSync(); 210 | 211 | var fetchedLeaf = Leaf.getSync(leaf[Leaf.id]); 212 | assert.exist(fetchedLeaf); 213 | assert.equal(fetchedLeaf.treeId, leaf.treeId); 214 | }); 215 | 216 | it("should work when specifying parentId in the save call", function () { 217 | var leaf = new Leaf({ 218 | size: 4 219 | }); 220 | leaf.saveSync({ 221 | treeId: tree[Tree.id] 222 | }); 223 | 224 | assert.exist(leaf.treeId); 225 | 226 | var fetchedLeaf = Leaf.getSync(leaf[Leaf.id]); 227 | assert.exist(fetchedLeaf); 228 | assert.equal(fetchedLeaf.treeId, leaf.treeId); 229 | }); 230 | 231 | it("should work when calling Model.create", function () { 232 | var leaf = Leaf.createSync({ 233 | size: 4, 234 | treeId: tree[Tree.id] 235 | }); 236 | 237 | var fetchedLeaf = Leaf.getSync(leaf[Leaf.id]); 238 | 239 | assert.exist(fetchedLeaf); 240 | assert.equal(fetchedLeaf.treeId, leaf.treeId); 241 | }); 242 | 243 | it("shouldn't cause an infinite loop when getting and saving with no changes", function () { 244 | var leaf = Leaf.getSync(leafId); 245 | leaf.saveSync(); 246 | }); 247 | 248 | it("shouldn't cause an infinite loop when getting and saving with changes", function () { 249 | var leaf = Leaf.getSync(leafId); 250 | leaf.saveSync({ 251 | size: 14 252 | }); 253 | }); 254 | }); 255 | }); 256 | }); 257 | 258 | describe("validations", function () { 259 | before(setup({ 260 | validations: { 261 | stalkId: ORM.validators.rangeNumber(undefined, 50) 262 | } 263 | })); 264 | 265 | it("should allow validating parentId", function () { 266 | var leaf = Leaf.oneSync({ 267 | size: 14 268 | }); 269 | assert.exist(leaf); 270 | 271 | try { 272 | leaf.saveSync({ 273 | stalkId: 51 274 | }); 275 | } catch (err) { 276 | assert.ok(Array.isArray(err)); 277 | assert.equal(err.length, 1); 278 | assert.equal(err[0].msg, 'out-of-range-number'); 279 | } 280 | }); 281 | }); 282 | 283 | describe("if not passing another Model", function () { 284 | it("should use same model", function () { 285 | db.settings.set('instance.identityCache', false); 286 | db.settings.set('instance.returnAllErrors', true); 287 | 288 | var Person = db.define("person", { 289 | name: String 290 | }); 291 | Person.hasOne("parent", { 292 | autoFetch: true 293 | }); 294 | 295 | helper.dropSync(Person, function () { 296 | var child = new Person({ 297 | name: "Child" 298 | }); 299 | child.setParentSync(new Person({ 300 | name: "Parent" 301 | })); 302 | }); 303 | }); 304 | }); 305 | 306 | describe("association name letter case", function () { 307 | it("should be kept", function () { 308 | db.settings.set('instance.identityCache', false); 309 | db.settings.set('instance.returnAllErrors', true); 310 | 311 | var Person = db.define("person", { 312 | name: String 313 | }); 314 | Person.hasOne("topParent", Person); 315 | 316 | helper.dropSync(Person, function () { 317 | var person = Person.createSync({ 318 | name: "Child" 319 | }); 320 | 321 | person = Person.getSync(person[Person.id]); 322 | 323 | assert.isFunction(person.setTopParent); 324 | assert.isFunction(person.removeTopParent); 325 | assert.isFunction(person.hasTopParent); 326 | }); 327 | }); 328 | }); 329 | 330 | describe("findBy()", function () { 331 | before(setup()); 332 | 333 | it("should throw if no conditions passed", function () { 334 | assert.throws(function () { 335 | Leaf.findByTreeSync(); 336 | }); 337 | }); 338 | 339 | it("should lookup in Model based on associated model properties", function () { 340 | var leafs = Leaf.findByTreeSync({ 341 | type: "pine" 342 | }); 343 | 344 | assert.ok(Array.isArray(leafs)); 345 | assert.ok(leafs.length == 1); 346 | }); 347 | 348 | it("should return a ChainFind if no callback passed", function () { 349 | var ChainFind = Leaf.findByTree({ 350 | type: "pine" 351 | }); 352 | assert.isFunction(ChainFind.run); 353 | assert.isFunction(ChainFind.runSync); 354 | }); 355 | }); 356 | 357 | 358 | describe("mapsTo", function () { 359 | describe("with `mapsTo` set via `hasOne`", function () { 360 | var leaf = null; 361 | 362 | before(setup()); 363 | 364 | before(function () { 365 | var lf = Leaf.createSync({ 366 | size: 444, 367 | stalkId: stalkId, 368 | holeId: holeId 369 | }); 370 | leaf = lf; 371 | }); 372 | 373 | it("should have correct fields in the DB", function () { 374 | var sql = db.driver.query.select() 375 | .from('leaf') 376 | .select('size', 'stalk_id') 377 | .where({ 378 | size: 444 379 | }) 380 | .build(); 381 | 382 | var rows = db.driver.execQuerySync(sql); 383 | 384 | assert.equal(rows[0].size, 444); 385 | assert.equal(rows[0].stalk_id, 1); 386 | }); 387 | 388 | it("should get parent", function () { 389 | var stalk = leaf.getStalkSync(); 390 | 391 | assert.exist(stalk); 392 | assert.equal(stalk.id, stalkId); 393 | assert.equal(stalk.length, 20); 394 | }); 395 | }); 396 | 397 | describe("with `mapsTo` set via property definition", function () { 398 | var leaf = null; 399 | 400 | before(setup()); 401 | 402 | before(function () { 403 | var lf = Leaf.createSync({ 404 | size: 444, 405 | stalkId: stalkId, 406 | holeId: holeId 407 | }); 408 | leaf = lf; 409 | }); 410 | 411 | it("should have correct fields in the DB", function () { 412 | var sql = db.driver.query.select() 413 | .from('leaf') 414 | .select('size', 'hole_id') 415 | .where({ 416 | size: 444 417 | }) 418 | .build(); 419 | 420 | var rows = db.driver.execQuerySync(sql); 421 | 422 | assert.equal(rows[0].size, 444); 423 | assert.equal(rows[0].hole_id, 1); 424 | }); 425 | 426 | it("should get parent", function () { 427 | var hole = leaf.getHoleSync(); 428 | 429 | assert.exist(hole); 430 | assert.equal(hole.id, stalkId); 431 | assert.equal(hole.width, 3); 432 | }); 433 | }); 434 | }); 435 | }); -------------------------------------------------------------------------------- /test/integration/date-type.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | var util = require('util'); 4 | 5 | describe("Date Type", function () { 6 | var db = null; 7 | var Person = null; 8 | 9 | var setup = function (hooks) { 10 | return function () { 11 | Person = db.define("person", { 12 | name: { 13 | type: "text", 14 | required: true 15 | }, 16 | birthday: Date 17 | }); 18 | 19 | return helper.dropSync(Person); 20 | }; 21 | }; 22 | 23 | before(function () { 24 | db = helper.connect(); 25 | }); 26 | 27 | after(function () { 28 | return db.closeSync(); 29 | }); 30 | 31 | describe("opt", function () { 32 | before(setup()); 33 | 34 | it("insert", function () { 35 | var John = Person.createSync({ 36 | name: "John Doe", 37 | birthday: '1971-08-28T00:00:00Z' 38 | }); 39 | 40 | var who = Person.oneSync({ 41 | name: "John Doe" 42 | }); 43 | 44 | assert.equal(who.birthday.getTime(), 45 | new Date('1971-08-28T00:00:00Z').getTime()); 46 | }); 47 | 48 | it("update", function () { 49 | var John = Person.oneSync({ 50 | name: "John Doe" 51 | }); 52 | 53 | John.birthday = '1971-08-29T00:00:00Z'; 54 | John.saveSync(); 55 | 56 | var who = Person.oneSync({ 57 | name: "John Doe" 58 | }); 59 | 60 | assert.equal(who.birthday.getTime(), 61 | new Date('1971-08-29T00:00:00Z').getTime()); 62 | }); 63 | 64 | it("find", function () { 65 | var who = Person.oneSync({ 66 | birthday: '1971-08-29T00:00:00Z' 67 | }); 68 | 69 | assert.equal(who.name, 'John Doe'); 70 | }); 71 | }); 72 | }); -------------------------------------------------------------------------------- /test/integration/event.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Event", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var triggeredHooks = {}; 9 | 10 | var checkHook = function (hook) { 11 | triggeredHooks[hook] = false; 12 | 13 | return function () { 14 | triggeredHooks[hook] = Date.now(); 15 | }; 16 | }; 17 | 18 | var setup = function (hooks) { 19 | return function () { 20 | Person = db.define("person", { 21 | name: { 22 | type: "text", 23 | required: true 24 | } 25 | }); 26 | 27 | return helper.dropSync(Person); 28 | }; 29 | }; 30 | 31 | before(function () { 32 | db = helper.connect(); 33 | }); 34 | 35 | after(function () { 36 | return db.closeSync(); 37 | }); 38 | 39 | describe("save", function () { 40 | before(setup()); 41 | 42 | it("should trigger when saving an instance", function () { 43 | var triggered = false; 44 | var John = new Person({ 45 | name: "John Doe" 46 | }); 47 | 48 | John.on("save", function () { 49 | triggered = true; 50 | }); 51 | 52 | assert.isFalse(triggered); 53 | 54 | John.saveSync(); 55 | assert.isTrue(triggered); 56 | }); 57 | 58 | it("should trigger when saving an instance even if it fails", function () { 59 | var triggered = false; 60 | var John = new Person(); 61 | 62 | John.on("save", function (err) { 63 | triggered = true; 64 | 65 | assert.isObject(err); 66 | assert.propertyVal(err, "msg", "required"); 67 | }); 68 | 69 | assert.isFalse(triggered); 70 | 71 | assert.throws(function () { 72 | John.saveSync(); 73 | }) 74 | 75 | assert.isTrue(triggered); 76 | }); 77 | 78 | it("should be writable for mocking", function () { 79 | var triggered = false; 80 | var John = new Person(); 81 | 82 | John.on = function (event, cb) { 83 | triggered = true; 84 | }; 85 | assert.isFalse(triggered); 86 | 87 | John.on("mocked", function (err) {}); 88 | assert.isTrue(triggered); 89 | }); 90 | }); 91 | }); -------------------------------------------------------------------------------- /test/integration/instance.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model instance", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var setup = function () { 9 | db.settings.set('instance.returnAllErrors', true); 10 | 11 | Person = db.define("person", { 12 | name: String, 13 | age: { 14 | type: 'integer', 15 | required: false 16 | }, 17 | height: { 18 | type: 'integer', 19 | required: false 20 | }, 21 | weight: { 22 | type: 'number', 23 | required: false, 24 | enumerable: true 25 | }, 26 | secret: { 27 | type: 'text', 28 | required: false, 29 | enumerable: false 30 | }, 31 | data: { 32 | type: 'object', 33 | required: false 34 | } 35 | }, { 36 | identityCache: false, 37 | validations: { 38 | age: ORM.validators.rangeNumber(0, 150) 39 | } 40 | }); 41 | 42 | helper.dropSync(Person, function () { 43 | Person.createSync([{ 44 | name: "Jeremy Doe" 45 | }, { 46 | name: "John Doe" 47 | }, { 48 | name: "Jane Doe" 49 | }]); 50 | }); 51 | }; 52 | 53 | before(function () { 54 | db = helper.connect(); 55 | setup(); 56 | }); 57 | 58 | after(function () { 59 | return db.closeSync(); 60 | }); 61 | 62 | describe("#save", function () { 63 | var main_item, item; 64 | 65 | before(function () { 66 | main_item = db.define("main_item", { 67 | name: String 68 | }, { 69 | auteFetch: true 70 | }); 71 | 72 | item = db.define("item", { 73 | name: String 74 | }, { 75 | identityCache: false 76 | }); 77 | 78 | item.hasOne("main_item", main_item, { 79 | reverse: "items", 80 | autoFetch: true 81 | }); 82 | 83 | helper.dropSync([main_item, item], function () { 84 | var mainItem = main_item.createSync({ 85 | name: "Main Item" 86 | }); 87 | 88 | var Item = item.createSync({ 89 | name: "Item" 90 | }); 91 | 92 | var r = mainItem.setItemsSync(Item); 93 | }); 94 | }); 95 | 96 | it("should have a saving state to avoid loops", function () { 97 | var mainItem = main_item.find({ 98 | name: "Main Item" 99 | }).firstSync(); 100 | mainItem.saveSync({ 101 | name: "new name" 102 | }); 103 | }); 104 | }); 105 | 106 | describe("#isInstance", function () { 107 | it("should always return true for instances", function () { 108 | assert.equal((new Person).isInstance, true); 109 | assert.equal((Person(4)).isInstance, true); 110 | 111 | var item = Person.find().firstSync(); 112 | assert.equal(item.isInstance, true); 113 | }); 114 | 115 | it("should be false for all other objects", function () { 116 | assert.notEqual({}.isInstance, true); 117 | assert.notEqual([].isInstance, true); 118 | }); 119 | }); 120 | 121 | describe("#isPersisted", function () { 122 | it("should return true for persisted instances", function () { 123 | var item = Person.find().firstSync(); 124 | assert.equal(item.isPersisted(), true); 125 | }); 126 | 127 | it("should return true for shell instances", function () { 128 | assert.equal(Person(4).isPersisted(), true); 129 | }); 130 | 131 | it("should return false for new instances", function () { 132 | assert.equal((new Person).isPersisted(), false); 133 | }); 134 | 135 | it("should be writable for mocking", function () { 136 | var person = new Person() 137 | var triggered = false; 138 | person.isPersisted = function () { 139 | triggered = true; 140 | }; 141 | person.isPersisted() 142 | assert.isTrue(triggered); 143 | }); 144 | }); 145 | 146 | describe("#set", function () { 147 | var person = null; 148 | var data = null; 149 | 150 | function clone(obj) { 151 | return JSON.parse(JSON.stringify(obj)) 152 | }; 153 | 154 | beforeEach(function () { 155 | data = { 156 | a: { 157 | b: { 158 | c: 3, 159 | d: 4 160 | } 161 | }, 162 | e: 5 163 | }; 164 | var p = Person.createSync({ 165 | name: 'Dilbert', 166 | data: data 167 | }); 168 | person = p; 169 | }); 170 | 171 | it("should do nothing with flat paths when setting to same value", function () { 172 | assert.equal(person.saved(), true); 173 | person.set('name', 'Dilbert'); 174 | assert.equal(person.name, 'Dilbert'); 175 | assert.equal(person.saved(), true); 176 | }); 177 | 178 | it("should mark as dirty with flat paths when setting to different value", function () { 179 | assert.equal(person.saved(), true); 180 | person.set('name', 'Dogbert'); 181 | assert.equal(person.name, 'Dogbert'); 182 | assert.equal(person.saved(), false); 183 | assert.equal(person.__opts.changes.join(','), 'name'); 184 | }); 185 | 186 | it("should do nothin with deep paths when setting to same value", function () { 187 | assert.equal(person.saved(), true); 188 | person.set('data.e', 5); 189 | 190 | var expected = clone(data); 191 | expected.e = 5; 192 | 193 | assert.equal(JSON.stringify(person.data), JSON.stringify(expected)); 194 | assert.equal(person.saved(), true); 195 | }); 196 | 197 | it("should mark as dirty with deep paths when setting to different value", function () { 198 | assert.equal(person.saved(), true); 199 | person.set('data.e', 6); 200 | 201 | var expected = clone(data); 202 | expected.e = 6; 203 | 204 | assert.equal(JSON.stringify(person.data), JSON.stringify(expected)); 205 | assert.equal(person.saved(), false); 206 | assert.equal(person.__opts.changes.join(','), 'data'); 207 | }); 208 | 209 | it("should do nothing with deeper paths when setting to same value", function () { 210 | assert.equal(person.saved(), true); 211 | person.set('data.a.b.d', 4); 212 | 213 | var expected = clone(data); 214 | expected.a.b.d = 4; 215 | 216 | assert.equal(JSON.stringify(person.data), JSON.stringify(expected)); 217 | assert.equal(person.saved(), true); 218 | }); 219 | 220 | it("should mark as dirty with deeper paths when setting to different value", function () { 221 | assert.equal(person.saved(), true); 222 | person.set('data.a.b.d', 6); 223 | 224 | var expected = clone(data); 225 | expected.a.b.d = 6; 226 | 227 | assert.equal(JSON.stringify(person.data), JSON.stringify(expected)); 228 | assert.equal(person.saved(), false); 229 | assert.equal(person.__opts.changes.join(','), 'data'); 230 | }); 231 | 232 | it("should mark as dirty with array path when setting to different value", function () { 233 | assert.equal(person.saved(), true); 234 | person.set(['data', 'a', 'b', 'd'], 6); 235 | 236 | var expected = clone(data); 237 | expected.a.b.d = 6; 238 | 239 | assert.equal(JSON.stringify(person.data), JSON.stringify(expected)); 240 | assert.equal(person.saved(), false); 241 | assert.equal(person.__opts.changes.join(','), 'data'); 242 | }); 243 | 244 | it("should do nothing with invalid paths", function () { 245 | assert.equal(person.saved(), true); 246 | person.set('data.a.b.d.y.z', 1); 247 | person.set('data.y.z', 1); 248 | person.set('z', 1); 249 | person.set(4, 1); 250 | person.set(null, 1); 251 | person.set(undefined, 1); 252 | assert.equal(person.saved(), true); 253 | }); 254 | }); 255 | 256 | describe("#markAsDirty", function () { 257 | var person = null; 258 | 259 | beforeEach(function () { 260 | var p = Person.createSync({ 261 | name: 'John', 262 | age: 44, 263 | data: { 264 | a: 1 265 | } 266 | }); 267 | person = p; 268 | }); 269 | 270 | it("should mark individual properties as dirty", function () { 271 | assert.equal(person.saved(), true); 272 | person.markAsDirty('name'); 273 | assert.equal(person.saved(), false); 274 | assert.equal(person.__opts.changes.join(','), 'name'); 275 | person.markAsDirty('data'); 276 | assert.equal(person.__opts.changes.join(','), 'name,data'); 277 | }); 278 | }); 279 | 280 | describe("#dirtyProperties", function () { 281 | var person = null; 282 | 283 | beforeEach(function () { 284 | var p = Person.createSync({ 285 | name: 'John', 286 | age: 44, 287 | data: { 288 | a: 1 289 | } 290 | }); 291 | person = p; 292 | }); 293 | 294 | it("should mark individual properties as dirty", function () { 295 | assert.equal(person.saved(), true); 296 | person.markAsDirty('name'); 297 | person.markAsDirty('data'); 298 | assert.equal(person.saved(), false); 299 | assert.equal(person.dirtyProperties.join(','), 'name,data'); 300 | }); 301 | }); 302 | 303 | describe("#isShell", function () { 304 | it("should return true for shell models", function () { 305 | assert.equal(Person(4).isShell(), true); 306 | }); 307 | 308 | it("should return false for new models", function () { 309 | assert.equal((new Person).isShell(), false); 310 | }); 311 | 312 | it("should return false for existing models", function () { 313 | var item = Person.find().firstSync(); 314 | assert.equal(item.isShell(), false); 315 | }); 316 | }); 317 | 318 | describe("#validate", function () { 319 | it("should return validation errors if invalid", function () { 320 | var person = new Person({ 321 | age: -1 322 | }); 323 | 324 | var validationErrors = person.validateSync(); 325 | assert.equal(Array.isArray(validationErrors), true); 326 | }); 327 | 328 | it("should return false if valid", function () { 329 | var person = new Person({ 330 | name: 'Janette' 331 | }); 332 | 333 | var validationErrors = person.validateSync(); 334 | assert.equal(validationErrors, false); 335 | }); 336 | }); 337 | 338 | describe("properties", function () { 339 | describe("Number", function () { 340 | it("should be saved for valid numbers, using both save & create", function () { 341 | var person1 = new Person({ 342 | height: 190 343 | }); 344 | 345 | person1.saveSync(); 346 | 347 | var person2 = Person.createSync({ 348 | height: 170 349 | }); 350 | 351 | var item = Person.getSync(person1[Person.id]); 352 | 353 | assert.equal(item.height, 190); 354 | 355 | item = Person.getSync(person2[Person.id]); 356 | assert.equal(item.height, 170); 357 | }); 358 | }); 359 | 360 | describe("Enumerable", function () { 361 | it("should not stringify properties marked as not enumerable", function () { 362 | var p = Person.createSync({ 363 | name: 'Dilbert', 364 | secret: 'dogbert', 365 | weight: 100, 366 | data: { 367 | data: 3 368 | } 369 | }); 370 | 371 | var result = JSON.parse(JSON.stringify(p)); 372 | assert.notExist(result.secret); 373 | assert.exist(result.weight); 374 | assert.exist(result.data); 375 | assert.exist(result.name); 376 | }); 377 | }); 378 | }); 379 | }); -------------------------------------------------------------------------------- /test/integration/model-aggregate.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.aggregate()", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var setup = function () { 9 | return function () { 10 | Person = db.define("person", { 11 | name: String 12 | }); 13 | 14 | helper.dropSync(Person, function () { 15 | Person.createSync([{ 16 | id: 1, 17 | name: "John Doe" 18 | }, { 19 | id: 2, 20 | name: "Jane Doe" 21 | }, { 22 | id: 3, 23 | name: "John Doe" 24 | }]); 25 | }); 26 | }; 27 | }; 28 | 29 | before(function () { 30 | db = helper.connect(); 31 | }); 32 | 33 | after(function () { 34 | return db.closeSync(); 35 | }); 36 | 37 | xdescribe("with multiple methods", function () { 38 | before(setup()); 39 | 40 | it("should return value for everyone of them", function () { 41 | Person.aggregate().count('id').min('id').max('id').get(function (err, count, min, max) { 42 | assert.equal(err, null); 43 | 44 | assert.equal(count, 3); 45 | assert.equal(min, 1); 46 | assert.equal(max, 3); 47 | }); 48 | }); 49 | }); 50 | 51 | describe("with call()", function () { 52 | before(setup()); 53 | 54 | it("should accept a function", function () { 55 | var count = Person.aggregate().call('COUNT').getSync(); 56 | assert.equal(count, 3); 57 | }); 58 | 59 | it("should accept arguments to the funciton as an Array", function () { 60 | var count = Person.aggregate().call('COUNT', ['id']).getSync(); 61 | assert.equal(count, 3); 62 | }); 63 | 64 | describe("if function is DISTINCT", function () { 65 | it("should work as calling .distinct() directly", function () { 66 | var rows = Person.aggregate().call('DISTINCT', ['name']).as('name').order('name').getSync(); 67 | 68 | assert.ok(Array.isArray(rows)); 69 | assert.equal(rows.length, 2); 70 | 71 | assert.equal(rows[0], 'Jane Doe'); 72 | assert.equal(rows[1], 'John Doe'); 73 | }); 74 | }); 75 | }); 76 | 77 | xdescribe("with as() without previous aggregates", function () { 78 | before(setup()); 79 | 80 | it("should throw", function () { 81 | Person.aggregate().as.should.throw(); 82 | }); 83 | }); 84 | 85 | xdescribe("with select() without arguments", function () { 86 | before(setup()); 87 | 88 | it("should throw", function () { 89 | Person.aggregate().select.should.throw(); 90 | 91 | return done(); 92 | }); 93 | }); 94 | 95 | describe("with select() with arguments", function () { 96 | before(setup()); 97 | 98 | it("should use them as properties if 1st argument is Array", function () { 99 | var people = Person.aggregate().select(['id']).count('id').groupBy('id').getSync(); 100 | 101 | // assert.ok(Array.isArray(people)); 102 | assert.greaterThan(people.length, 0); 103 | 104 | assert.isObject(people[0]); 105 | assert.property(people[0], "id"); 106 | assert.notProperty(people[0], "name"); 107 | }); 108 | 109 | it("should use them as properties", function () { 110 | var people = Person.aggregate().select('id').count().groupBy('id').getSync(); 111 | // assert.ok(Array.isArray(people)); 112 | assert.greaterThan(people.length, 0); 113 | 114 | assert.isObject(people[0]); 115 | assert.property(people[0], "id"); 116 | assert.notProperty(people[0], "name"); 117 | }); 118 | }); 119 | 120 | xdescribe("with get() without callback", function () { 121 | before(setup()); 122 | 123 | it("should throw", function () { 124 | Person.aggregate().count('id').get.should.throw(); 125 | 126 | return done(); 127 | }); 128 | }); 129 | 130 | describe("with get() without aggregates", function () { 131 | before(setup()); 132 | 133 | it("should throw", function () { 134 | assert.throws(function () { 135 | Person.aggregate().get(function () {}); 136 | }); 137 | }); 138 | }); 139 | 140 | describe("with distinct()", function () { 141 | before(setup()); 142 | 143 | it("should return a list of distinct properties", function () { 144 | var names = Person.aggregate().distinct('name').getSync(); 145 | assert.isObject(names); 146 | assert.property(names, "length", 2); 147 | }); 148 | 149 | describe("with limit(1)", function () { 150 | it("should return only one value", function () { 151 | var names = Person.aggregate().distinct('name').limit(1).order("name").getSync(); 152 | assert.isObject(names); 153 | assert.property(names, "length", 1); 154 | assert.equal(names[0], "Jane Doe"); 155 | }); 156 | }); 157 | 158 | describe("with limit(1, 1)", function () { 159 | it("should return only one value", function () { 160 | var names = Person.aggregate().distinct('name').limit(1, 1).order("name").getSync(); 161 | assert.isObject(names); 162 | assert.property(names, "length", 1); 163 | assert.equal(names[0], "John Doe"); 164 | }); 165 | }); 166 | }); 167 | 168 | describe("with groupBy()", function () { 169 | before(setup()); 170 | 171 | it("should return items grouped by property", function () { 172 | var rows = Person.aggregate().count().groupBy('name').getSync(); 173 | assert.isObject(rows); 174 | assert.property(rows, "length", 2); 175 | 176 | assert.equal((rows[0].count + rows[1].count), 3); // 1 + 2 177 | }); 178 | 179 | describe("with order()", function () { 180 | before(setup()); 181 | 182 | it("should order items", function () { 183 | var rows = Person.aggregate().count().groupBy('name').order('-count').getSync(); 184 | assert.isObject(rows); 185 | assert.property(rows, "length", 2); 186 | 187 | assert.equal(rows[0].count, 2); 188 | assert.equal(rows[1].count, 1); 189 | }); 190 | }); 191 | }); 192 | 193 | describe("using as()", function () { 194 | before(setup()); 195 | 196 | it("should use as an alias", function () { 197 | var people = Person.aggregate().count().as('total').groupBy('name').getSync(); 198 | // assert.ok(Array.isArray(people)); 199 | assert.greaterThan(people.length, 0); 200 | 201 | assert.isObject(people[0]); 202 | assert.property(people[0], "total"); 203 | }); 204 | 205 | it("should throw if no aggregates defined", function () { 206 | assert.throws(function () { 207 | Person.aggregate().as('total'); 208 | }); 209 | }); 210 | }); 211 | }); -------------------------------------------------------------------------------- /test/integration/model-clear.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.clear()", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var setup = function () { 9 | Person = db.define("person", { 10 | name: String 11 | }); 12 | 13 | ORM.singleton.clear(); 14 | 15 | return helper.dropSync(Person, function () { 16 | Person.createSync([{ 17 | name: "John Doe" 18 | }, { 19 | name: "Jane Doe" 20 | }]); 21 | }); 22 | }; 23 | 24 | before(function () { 25 | db = helper.connect(); 26 | }); 27 | 28 | after(function () { 29 | db.closeSync(); 30 | }); 31 | 32 | describe("with sync", function () { 33 | before(setup); 34 | 35 | it("should call when done", function () { 36 | Person.clearSync(); 37 | 38 | var count = Person.find().countSync(); 39 | assert.equal(count, 0); 40 | }); 41 | }); 42 | 43 | }); -------------------------------------------------------------------------------- /test/integration/model-count.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.count()", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var setup = function () { 9 | Person = db.define("person", { 10 | name: String 11 | }); 12 | 13 | return helper.dropSync(Person, function () { 14 | Person.createSync([{ 15 | id: 1, 16 | name: "John Doe" 17 | }, { 18 | id: 2, 19 | name: "Jane Doe" 20 | }, { 21 | id: 3, 22 | name: "John Doe" 23 | }]); 24 | }); 25 | }; 26 | 27 | before(function () { 28 | db = helper.connect(); 29 | }); 30 | 31 | after(function () { 32 | return db.closeSync(); 33 | }); 34 | 35 | describe("without conditions", function () { 36 | before(setup); 37 | 38 | it("should return all items in model", function () { 39 | var count = Person.countSync(); 40 | assert.equal(count, 3); 41 | }); 42 | }); 43 | 44 | describe("with conditions", function () { 45 | before(setup); 46 | 47 | it("should return only matching items", function () { 48 | var count = Person.countSync({ 49 | name: "John Doe" 50 | }); 51 | assert.equal(count, 2); 52 | }); 53 | }); 54 | }); -------------------------------------------------------------------------------- /test/integration/model-create.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.create()", function () { 5 | var db = null; 6 | var Pet = null; 7 | var Person = null; 8 | 9 | function setup() { 10 | Person = db.define("person", { 11 | name: String 12 | }); 13 | Pet = db.define("pet", { 14 | name: { 15 | type: "text", 16 | defaultValue: "Mutt" 17 | } 18 | }); 19 | Person.hasMany("pets", Pet); 20 | 21 | Person.dropSync(); 22 | Pet.dropSync(); 23 | 24 | db.syncSync(); 25 | }; 26 | 27 | before(function () { 28 | db = helper.connect(); 29 | }); 30 | 31 | after(function () { 32 | db.closeSync(); 33 | }); 34 | 35 | describe("if passing an object", function () { 36 | before(setup); 37 | 38 | it("should accept it as the only item to create", function () { 39 | var John = Person.createSync({ 40 | name: "John Doe" 41 | }); 42 | 43 | assert.propertyVal(John, "name", "John Doe"); 44 | }); 45 | }); 46 | 47 | describe("if passing an array", function () { 48 | before(setup); 49 | 50 | it("should accept it as a list of items to create", function () { 51 | var people = Person.createSync([{ 52 | name: "John Doe" 53 | }, { 54 | name: "Jane Doe" 55 | }]); 56 | 57 | assert.ok(Array.isArray(people)); 58 | 59 | assert.propertyVal(people, "length", 2); 60 | assert.propertyVal(people[0], "name", "John Doe"); 61 | assert.propertyVal(people[1], "name", "Jane Doe"); 62 | }); 63 | }); 64 | 65 | describe("if element has an association", function () { 66 | before(setup); 67 | 68 | it("should also create it or save it", function () { 69 | var John = Person.createSync({ 70 | name: "John Doe", 71 | pets: [new Pet({ 72 | name: "Deco" 73 | })] 74 | }); 75 | 76 | assert.propertyVal(John, "name", "John Doe"); 77 | 78 | assert.ok(Array.isArray(John.pets)); 79 | 80 | assert.propertyVal(John.pets[0], "name", "Deco"); 81 | assert.property(John.pets[0], Pet.id[0]); 82 | assert.ok(John.pets[0].saved()); 83 | }); 84 | 85 | it("should also create it or save it even if it's an object and not an instance", function () { 86 | var John = Person.createSync({ 87 | name: "John Doe", 88 | pets: [{ 89 | name: "Deco" 90 | }] 91 | }); 92 | 93 | assert.propertyVal(John, "name", "John Doe"); 94 | 95 | assert.ok(Array.isArray(John.pets)); 96 | 97 | assert.propertyVal(John.pets[0], "name", "Deco"); 98 | assert.property(John.pets[0], Pet.id[0]); 99 | assert.ok(John.pets[0].saved()); 100 | }); 101 | }); 102 | 103 | describe("when not passing a property", function () { 104 | before(setup); 105 | 106 | it("should use defaultValue if defined", function () { 107 | var Mutt = Pet.createSync({}); 108 | assert.propertyVal(Mutt, "name", "Mutt"); 109 | }); 110 | }); 111 | }); -------------------------------------------------------------------------------- /test/integration/model-exists.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.exists()", function () { 5 | var db = null; 6 | var Person = null; 7 | var good_id, bad_id; 8 | 9 | var setup = function () { 10 | return function () { 11 | Person = db.define("person", { 12 | name: String 13 | }); 14 | 15 | return helper.dropSync(Person, function () { 16 | var people = Person.createSync([{ 17 | name: "Jeremy Doe" 18 | }, { 19 | name: "John Doe" 20 | }, { 21 | name: "Jane Doe" 22 | }]); 23 | good_id = people[0][Person.id]; 24 | 25 | if (typeof good_id == "number") { 26 | // numeric ID 27 | bad_id = good_id * 100; 28 | } else { 29 | // string ID, keep same length.. 30 | bad_id = good_id.split('').reverse().join(''); 31 | } 32 | }); 33 | }; 34 | }; 35 | 36 | before(function () { 37 | db = helper.connect(); 38 | }); 39 | 40 | after(function () { 41 | db.closeSync(); 42 | }); 43 | 44 | describe("with an id", function () { 45 | before(setup()); 46 | 47 | it("should return true if found", function () { 48 | var exists = Person.existsSync(good_id); 49 | assert.ok(exists); 50 | }); 51 | 52 | it("should return false if not found", function () { 53 | var exists = Person.existsSync(bad_id); 54 | assert.notOk(exists); 55 | }); 56 | }); 57 | 58 | describe("with a list of ids", function () { 59 | before(setup()); 60 | 61 | it("should return true if found", function () { 62 | var exists = Person.existsSync([good_id]); 63 | assert.ok(exists); 64 | }); 65 | 66 | it("should return false if not found", function () { 67 | var exists = Person.existsSync([bad_id]); 68 | assert.notOk(exists); 69 | }); 70 | }); 71 | 72 | describe("with a conditions object", function () { 73 | before(setup()); 74 | 75 | it("should return true if found", function () { 76 | var exists = Person.existsSync({ 77 | name: "John Doe" 78 | }); 79 | assert.ok(exists); 80 | }); 81 | 82 | it("should return false if not found", function () { 83 | var exists = Person.existsSync({ 84 | name: "Jack Doe" 85 | }); 86 | assert.notOk(exists); 87 | }); 88 | }); 89 | }); -------------------------------------------------------------------------------- /test/integration/model-find-mapsto.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.pkMapTo.find()", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var setup = function () { 9 | return function () { 10 | 11 | // The fact that we've applied mapsTo to the key 12 | // property of the model - will break the cache. 13 | 14 | // Without Stuart's little bugfix, 2nd (and subsequent) calls to find() 15 | // will return the repeats of the first obect retrieved and placed in the cache. 16 | Person = db.define("person", { 17 | personId: { 18 | type: "integer", 19 | key: true, 20 | mapsTo: "id" 21 | }, 22 | name: String, 23 | surname: String, 24 | age: Number, 25 | male: Boolean 26 | }); 27 | 28 | return helper.dropSync(Person, function () { 29 | Person.createSync([{ 30 | personId: 1001, 31 | name: "John", 32 | surname: "Doe", 33 | age: 18, 34 | male: true 35 | }, { 36 | personId: 1002, 37 | name: "Jane", 38 | surname: "Doe", 39 | age: 16, 40 | male: false 41 | }, { 42 | personId: 1003, 43 | name: "Jeremy", 44 | surname: "Dean", 45 | age: 18, 46 | male: true 47 | }, { 48 | personId: 1004, 49 | name: "Jack", 50 | surname: "Dean", 51 | age: 20, 52 | male: true 53 | }, { 54 | personId: 1005, 55 | name: "Jasmine", 56 | surname: "Doe", 57 | age: 20, 58 | male: false 59 | }]); 60 | }); 61 | }; 62 | }; 63 | 64 | before(function () { 65 | db = helper.connect(); 66 | }); 67 | 68 | after(function () { 69 | return db.closeSync(); 70 | }); 71 | 72 | 73 | describe("Cache should work with mapped key field", function () { 74 | before(setup()); 75 | 76 | it("1st find should work", function () { 77 | var people = Person.findSync({ 78 | surname: "Dean" 79 | }); 80 | assert.isObject(people); 81 | assert.propertyVal(people, "length", 2); 82 | assert.equal(people[0].surname, "Dean"); 83 | }); 84 | 85 | it("2nd find should should also work", function () { 86 | var people = Person.findSync({ 87 | surname: "Doe" 88 | }); 89 | assert.isObject(people); 90 | assert.propertyVal(people, "length", 3); 91 | assert.equal(people[0].surname, "Doe"); 92 | }); 93 | }); 94 | }); -------------------------------------------------------------------------------- /test/integration/model-find.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.findSync()", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var setup = function () { 9 | Person = db.define("person", { 10 | name: String, 11 | surname: String, 12 | age: Number, 13 | male: Boolean 14 | }); 15 | 16 | return helper.dropSync(Person, function () { 17 | Person.createSync([{ 18 | name: "John", 19 | surname: "Doe", 20 | age: 18, 21 | male: true 22 | }, { 23 | name: "Jane", 24 | surname: "Doe", 25 | age: 16, 26 | male: false 27 | }, { 28 | name: "Jeremy", 29 | surname: "Dean", 30 | age: 18, 31 | male: true 32 | }, { 33 | name: "Jack", 34 | surname: "Dean", 35 | age: 20, 36 | male: true 37 | }, { 38 | name: "Jasmine", 39 | surname: "Doe", 40 | age: 20, 41 | male: false 42 | }]); 43 | }); 44 | }; 45 | 46 | before(function () { 47 | db = helper.connect(); 48 | }); 49 | 50 | after(function () { 51 | return db.closeSync(); 52 | }); 53 | 54 | describe("without arguments", function () { 55 | before(setup); 56 | 57 | it("should return all items", function () { 58 | var people = Person.findSync(); 59 | 60 | assert.isObject(people); 61 | assert.propertyVal(people, "length", 5); 62 | }); 63 | }); 64 | 65 | describe("with a number as argument", function () { 66 | before(setup); 67 | 68 | it("should use it as limit", function () { 69 | var people = Person.findSync(2); 70 | 71 | assert.isObject(people); 72 | assert.propertyVal(people, "length", 2); 73 | }); 74 | }); 75 | 76 | describe("with a string argument", function () { 77 | before(setup); 78 | 79 | it("should use it as property ascending order", function () { 80 | var people = Person.findSync("age"); 81 | 82 | assert.isObject(people); 83 | assert.propertyVal(people, "length", 5); 84 | assert.equal(people[0].age, 16); 85 | assert.equal(people[4].age, 20); 86 | }); 87 | 88 | it("should use it as property descending order if starting with '-'", function () { 89 | var people = Person.findSync("-age"); 90 | 91 | assert.isObject(people); 92 | assert.propertyVal(people, "length", 5); 93 | assert.equal(people[0].age, 20); 94 | assert.equal(people[4].age, 16); 95 | }); 96 | }); 97 | 98 | describe("with an Array as argument", function () { 99 | before(setup); 100 | 101 | it("should use it as property ascending order", function () { 102 | var people = Person.findSync(["age"]); 103 | 104 | assert.isObject(people); 105 | assert.propertyVal(people, "length", 5); 106 | assert.equal(people[0].age, 16); 107 | assert.equal(people[4].age, 20); 108 | }); 109 | 110 | it("should use it as property descending order if starting with '-'", function () { 111 | var people = Person.findSync(["-age"]); 112 | 113 | assert.isObject(people); 114 | assert.propertyVal(people, "length", 5); 115 | assert.equal(people[0].age, 20); 116 | assert.equal(people[4].age, 16); 117 | }); 118 | 119 | it("should use it as property descending order if element is 'Z'", function () { 120 | var people = Person.findSync(["age", "Z"]); 121 | 122 | assert.isObject(people); 123 | assert.propertyVal(people, "length", 5); 124 | assert.equal(people[0].age, 20); 125 | assert.equal(people[4].age, 16); 126 | }); 127 | 128 | it("should accept multiple ordering", function () { 129 | var people = Person.findSync(["age", "name", "Z"]); 130 | 131 | assert.isObject(people); 132 | assert.propertyVal(people, "length", 5); 133 | assert.equal(people[0].age, 16); 134 | assert.equal(people[4].age, 20); 135 | }); 136 | 137 | it("should accept multiple ordering using '-' instead of 'Z'", function () { 138 | var people = Person.findSync(["age", "-name"]); 139 | 140 | assert.isObject(people); 141 | assert.propertyVal(people, "length", 5); 142 | assert.equal(people[0].age, 16); 143 | assert.equal(people[4].age, 20); 144 | }); 145 | }); 146 | 147 | describe("with an Object as argument", function () { 148 | before(setup); 149 | 150 | it("should use it as conditions", function () { 151 | var people = Person.findSync({ 152 | age: 16 153 | }); 154 | 155 | assert.isObject(people); 156 | assert.propertyVal(people, "length", 1); 157 | assert.equal(people[0].age, 16); 158 | }); 159 | 160 | it("should accept comparison objects", function () { 161 | var people = Person.findSync({ 162 | age: ORM.gt(18) 163 | }); 164 | 165 | assert.isObject(people); 166 | assert.propertyVal(people, "length", 2); 167 | assert.equal(people[0].age, 20); 168 | assert.equal(people[1].age, 20); 169 | }); 170 | 171 | describe("with another Object as argument", function () { 172 | before(setup); 173 | 174 | it("should use it as options", function () { 175 | var people = Person.findSync({ 176 | age: 18 177 | }, 1, { 178 | cache: false 179 | }); 180 | assert.isObject(people); 181 | assert.propertyVal(people, "length", 1); 182 | assert.equal(people[0].age, 18); 183 | }); 184 | 185 | describe("if a limit is passed", function () { 186 | before(setup); 187 | 188 | it("should use it", function () { 189 | var people = Person.findSync({ 190 | age: 18 191 | }, { 192 | limit: 1 193 | }); 194 | 195 | assert.isObject(people); 196 | assert.propertyVal(people, "length", 1); 197 | assert.equal(people[0].age, 18); 198 | }); 199 | }); 200 | 201 | describe("if an offset is passed", function () { 202 | before(setup); 203 | 204 | it("should use it", function () { 205 | var people = Person.findSync({}, { 206 | offset: 1 207 | }, "age"); 208 | 209 | assert.isObject(people); 210 | assert.propertyVal(people, "length", 4); 211 | assert.equal(people[0].age, 18); 212 | }); 213 | }); 214 | 215 | describe("if an order is passed", function () { 216 | before(setup); 217 | 218 | it("should use it", function () { 219 | var people = Person.findSync({ 220 | surname: "Doe" 221 | }, { 222 | order: "age" 223 | }); 224 | 225 | assert.isObject(people); 226 | assert.propertyVal(people, "length", 3); 227 | assert.equal(people[0].age, 16); 228 | }); 229 | 230 | it("should use it and ignore previously defined order", function () { 231 | var people = Person.findSync({ 232 | surname: "Doe" 233 | }, "-age", { 234 | order: "age" 235 | }); 236 | 237 | assert.isObject(people); 238 | assert.propertyVal(people, "length", 3); 239 | assert.equal(people[0].age, 16); 240 | }); 241 | }); 242 | }); 243 | }); 244 | 245 | describe("if defined static methods", function () { 246 | before(setup); 247 | 248 | it("should be rechainable", function () { 249 | Person.over18 = function () { 250 | return this.find({ 251 | age: ORM.gt(18) 252 | }); 253 | }; 254 | Person.family = function (family) { 255 | return this.find({ 256 | surname: family 257 | }); 258 | }; 259 | 260 | var people = Person.over18().family("Doe").runSync(); 261 | 262 | assert.isObject(people); 263 | assert.propertyVal(people, "length", 1); 264 | assert.equal(people[0].name, "Jasmine"); 265 | assert.equal(people[0].surname, "Doe"); 266 | }); 267 | }); 268 | 269 | describe("with identityCache disabled", function () { 270 | it("should not return singletons", function () { 271 | var people = Person.findSync({ 272 | name: "Jasmine" 273 | }, { 274 | identityCache: false 275 | }); 276 | 277 | assert.isObject(people); 278 | assert.propertyVal(people, "length", 1); 279 | assert.equal(people[0].name, "Jasmine"); 280 | assert.equal(people[0].surname, "Doe"); 281 | 282 | people[0].surname = "Dux"; 283 | 284 | people = Person.findSync({ 285 | name: "Jasmine" 286 | }, { 287 | identityCache: false 288 | }); 289 | 290 | assert.isObject(people); 291 | assert.propertyVal(people, "length", 1); 292 | assert.equal(people[0].name, "Jasmine"); 293 | assert.equal(people[0].surname, "Doe"); 294 | }); 295 | }); 296 | 297 | describe("when using Model.all()", function () { 298 | it("should work exactly the same", function () { 299 | var people = Person.allSync({ 300 | surname: "Doe" 301 | }, "-age", 1); 302 | 303 | assert.isObject(people); 304 | assert.propertyVal(people, "length", 1); 305 | assert.equal(people[0].name, "Jasmine"); 306 | assert.equal(people[0].surname, "Doe"); 307 | }); 308 | }); 309 | 310 | describe("when using Model.where()", function () { 311 | it("should work exactly the same", function () { 312 | var people = Person.whereSync({ 313 | surname: "Doe" 314 | }, "-age", 1); 315 | 316 | assert.isObject(people); 317 | assert.propertyVal(people, "length", 1); 318 | assert.equal(people[0].name, "Jasmine"); 319 | assert.equal(people[0].surname, "Doe"); 320 | }); 321 | }); 322 | }); -------------------------------------------------------------------------------- /test/integration/model-get.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var helper = require('../support/spec_helper'); 3 | var ORM = require('../../'); 4 | var coroutine = require('coroutine'); 5 | 6 | describe("Model.get()", function () { 7 | var db = null; 8 | var Person = null; 9 | var John; 10 | 11 | var setup = function (identityCache) { 12 | return function () { 13 | Person = db.define("person", { 14 | name: { 15 | type: 'text', 16 | mapsTo: 'fullname' 17 | } 18 | }, { 19 | identityCache: identityCache, 20 | methods: { 21 | UID: function () { 22 | return this[Person.id]; 23 | } 24 | } 25 | }); 26 | 27 | ORM.singleton.clear(); // clear identityCache cache 28 | 29 | return helper.dropSync(Person, function () { 30 | var people = Person.createSync([{ 31 | name: "John Doe" 32 | }, { 33 | name: "Jane Doe" 34 | }]); 35 | John = people[0]; 36 | }); 37 | }; 38 | }; 39 | 40 | before(function () { 41 | db = helper.connect(); 42 | }); 43 | 44 | after(function () { 45 | return db.closeSync(); 46 | }); 47 | 48 | describe("mapsTo", function () { 49 | before(setup(true)); 50 | 51 | it("should create the table with a different column name than property name", function () { 52 | var sql; 53 | var protocol = db.driver.db.conn.type; 54 | 55 | if (protocol == 'SQLite') { 56 | sql = "PRAGMA table_info(?)"; 57 | } else { 58 | sql = "SELECT column_name FROM information_schema.columns WHERE table_name = ?"; 59 | } 60 | 61 | var data = db.driver.execQuerySync(sql, [Person.table]); 62 | var names = _.map(data, protocol == 'SQLite' ? 'name' : 'column_name') 63 | 64 | assert.equal(typeof Person.properties.name, 'object'); 65 | assert.notEqual(names.indexOf('fullname'), -1); 66 | }); 67 | }); 68 | 69 | describe("with identityCache cache", function () { 70 | before(setup(true)); 71 | 72 | it("should return item with id 1", function () { 73 | var John1 = Person.getSync(John[Person.id]); 74 | 75 | assert.isObject(John1); 76 | assert.propertyVal(John1, Person.id[0], John[Person.id]); 77 | assert.propertyVal(John1, "name", "John Doe"); 78 | }); 79 | 80 | it("should have an UID method", function () { 81 | var John1 = Person.getSync(John[Person.id]); 82 | 83 | assert.isFunction(John1.UID); 84 | assert.equal(John1.UID(), John[Person.id]); 85 | }); 86 | 87 | describe("changing name and getting id 1 again", function () { 88 | it("should return the original object with unchanged name", function () { 89 | var John1 = Person.getSync(John[Person.id]); 90 | 91 | John1.name = "James"; 92 | 93 | var John2 = Person.getSync(John[Person.id]); 94 | 95 | assert.equal(John1[Person.id], John2[Person.id]); 96 | assert.equal(John2.name, "John Doe"); 97 | }); 98 | }); 99 | 100 | describe("changing instance.identityCacheSaveCheck = false", function () { 101 | before(function () { 102 | Person.settings.set("instance.identityCacheSaveCheck", false); 103 | }); 104 | 105 | it("should return the same object with the changed name", function () { 106 | var John1 = Person.getSync(John[Person.id]); 107 | 108 | John1.name = "James"; 109 | 110 | var John2 = Person.getSync(John[Person.id]); 111 | 112 | assert.equal(John1[Person.id], John2[Person.id]); 113 | assert.equal(John2.name, "James"); 114 | }); 115 | }); 116 | }); 117 | 118 | describe("with no identityCache cache", function () { 119 | before(setup(false)); 120 | 121 | describe("fetching several times", function () { 122 | it("should return different objects", function () { 123 | var John1 = Person.getSync(John[Person.id]); 124 | var John2 = Person.getSync(John[Person.id]); 125 | 126 | assert.equal(John1[Person.id], John2[Person.id]); 127 | assert.notEqual(John1, John2); 128 | }); 129 | }); 130 | }); 131 | 132 | describe("with identityCache cache = 0.5 secs", function () { 133 | before(setup(0.5)); 134 | 135 | describe("fetching again after 0.2 secs", function () { 136 | it("should return same objects", function () { 137 | var John1 = Person.getSync(John[Person.id]); 138 | 139 | coroutine.sleep(200); 140 | 141 | var John2 = Person.getSync(John[Person.id]); 142 | 143 | assert.equal(John1[Person.id], John2[Person.id]); 144 | assert.equal(John1, John2); 145 | }); 146 | }); 147 | 148 | describe("fetching again after 0.7 secs", function () { 149 | it("should return different objects", function () { 150 | var John1 = Person.getSync(John[Person.id]); 151 | 152 | coroutine.sleep(700); 153 | 154 | var John2 = Person.getSync(John[Person.id]); 155 | assert.notEqual(John1, John2); 156 | }); 157 | }); 158 | }); 159 | 160 | describe("with empty object as options", function () { 161 | before(setup()); 162 | 163 | it("should return item with id 1 like previously", function () { 164 | var John1 = Person.getSync(John[Person.id], {}); 165 | 166 | assert.isObject(John1); 167 | assert.propertyVal(John1, Person.id[0], John[Person.id]); 168 | assert.propertyVal(John1, "name", "John Doe"); 169 | }); 170 | }); 171 | 172 | describe("when not found", function () { 173 | before(setup(true)); 174 | 175 | it("should return an error", function () { 176 | try { 177 | Person.getSync(999); 178 | } catch (err) { 179 | assert.equal(err.message, "Not found"); 180 | } 181 | }); 182 | }); 183 | 184 | describe("if passed an Array with ids", function () { 185 | before(setup(true)); 186 | 187 | it("should accept and try to fetch", function () { 188 | var John1 = Person.getSync([John[Person.id]]); 189 | 190 | assert.isObject(John1); 191 | assert.propertyVal(John1, Person.id[0], John[Person.id]); 192 | assert.propertyVal(John1, "name", "John Doe"); 193 | }); 194 | }); 195 | 196 | describe("if primary key name is changed", function () { 197 | before(function () { 198 | Person = db.define("person", { 199 | name: String 200 | }); 201 | 202 | ORM.singleton.clear(); 203 | 204 | return helper.dropSync(Person, function () { 205 | Person.createSync([{ 206 | name: "John Doe" 207 | }, { 208 | name: "Jane Doe" 209 | }]); 210 | }); 211 | }); 212 | 213 | it("should search by key name and not 'id'", function () { 214 | db.settings.set('properties.primary_key', 'name'); 215 | 216 | var OtherPerson = db.define("person", { 217 | id: Number 218 | }); 219 | 220 | var person = OtherPerson.getSync("Jane Doe"); 221 | assert.equal(person.name, "Jane Doe"); 222 | }); 223 | }); 224 | 225 | xdescribe("with a point property type", function () { 226 | // if (common.protocol() == 'sqlite' || common.protocol() == 'mongodb') return; 227 | 228 | it("should deserialize the point to an array", function () { 229 | db.settings.set('properties.primary_key', 'id'); 230 | 231 | Person = db.define("person", { 232 | name: String, 233 | location: { 234 | type: "point" 235 | } 236 | }); 237 | 238 | ORM.singleton.clear(); 239 | 240 | return helper.dropSync(Person, function () { 241 | Person.create({ 242 | name: "John Doe", 243 | location: { 244 | x: 51.5177, 245 | y: -0.0968 246 | } 247 | }, function (err, person) { 248 | assert.equal(err, null); 249 | 250 | person.location.should.be.an.instanceOf(Object); 251 | assert.propertyVal(person.location, 'x', 51.5177); 252 | assert.propertyVal(person.location, 'y', -0.0968); 253 | return done(); 254 | }); 255 | }); 256 | }); 257 | }); 258 | }); -------------------------------------------------------------------------------- /test/integration/model-keys.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model keys option", function () { 5 | var db = null; 6 | 7 | before(function () { 8 | db = helper.connect(); 9 | }); 10 | 11 | after(function () { 12 | return db.closeSync(); 13 | }); 14 | 15 | describe("if model id is a property", function () { 16 | var Person = null; 17 | 18 | before(function () { 19 | Person = db.define("person", { 20 | uid: String, 21 | name: String, 22 | surname: String 23 | }, { 24 | id: "uid" 25 | }); 26 | 27 | return helper.dropSync(Person); 28 | }); 29 | 30 | it("should not auto increment IDs", function () { 31 | var JohnDoe = Person.createSync({ 32 | uid: "john-doe", 33 | name: "John", 34 | surname: "Doe" 35 | }); 36 | 37 | assert.equal(JohnDoe.uid, "john-doe"); 38 | assert.notProperty(JohnDoe, "id"); 39 | }); 40 | }); 41 | 42 | describe("if model defines several keys", function () { 43 | var DoorAccessHistory = null; 44 | 45 | before(function () { 46 | DoorAccessHistory = db.define("door_access_history", { 47 | year: { 48 | type: 'integer' 49 | }, 50 | month: { 51 | type: 'integer' 52 | }, 53 | day: { 54 | type: 'integer' 55 | }, 56 | user: String, 57 | action: ["in", "out"] 58 | }, { 59 | id: ["year", "month", "day"] 60 | }); 61 | 62 | return helper.dropSync(DoorAccessHistory, function () { 63 | DoorAccessHistory.createSync([{ 64 | year: 2013, 65 | month: 7, 66 | day: 11, 67 | user: "dresende", 68 | action: "in" 69 | }, 70 | { 71 | year: 2013, 72 | month: 7, 73 | day: 12, 74 | user: "dresende", 75 | action: "out" 76 | } 77 | ]); 78 | }); 79 | }); 80 | 81 | it("should make possible to get instances based on all keys", function () { 82 | var HistoryItem = DoorAccessHistory.getSync(2013, 7, 11); 83 | 84 | assert.equal(HistoryItem.year, 2013); 85 | assert.equal(HistoryItem.month, 7); 86 | assert.equal(HistoryItem.day, 11); 87 | assert.equal(HistoryItem.user, "dresende"); 88 | assert.equal(HistoryItem.action, "in"); 89 | }); 90 | 91 | it("should make possible to remove instances based on all keys", function () { 92 | var HistoryItem = DoorAccessHistory.getSync(2013, 7, 12); 93 | 94 | HistoryItem.removeSync(); 95 | 96 | var exists = DoorAccessHistory.existsSync(2013, 7, 12); 97 | assert.isFalse(exists); 98 | }); 99 | }); 100 | }); -------------------------------------------------------------------------------- /test/integration/model-one.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.one()", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var setup = function () { 9 | return function () { 10 | Person = db.define("person", { 11 | name: String 12 | }); 13 | 14 | return helper.dropSync(Person, function () { 15 | Person.createSync([{ 16 | id: 1, 17 | name: "Jeremy Doe" 18 | }, { 19 | id: 2, 20 | name: "John Doe" 21 | }, { 22 | id: 3, 23 | name: "Jane Doe" 24 | }]); 25 | }); 26 | }; 27 | }; 28 | 29 | before(function () { 30 | db = helper.connect(); 31 | }); 32 | 33 | after(function () { 34 | return db.closeSync(); 35 | }); 36 | 37 | describe("without arguments", function () { 38 | before(setup()); 39 | 40 | it("should return first item in model", function () { 41 | var person = Person.oneSync(); 42 | assert.equal(person.name, "Jeremy Doe"); 43 | }); 44 | }); 45 | 46 | describe("with order", function () { 47 | before(setup()); 48 | 49 | it("should return first item in model based on order", function () { 50 | var person = Person.oneSync("-name"); 51 | assert.equal(person.name, "John Doe"); 52 | }); 53 | }); 54 | 55 | describe("with conditions", function () { 56 | before(setup()); 57 | 58 | it("should return first item in model based on conditions", function () { 59 | var person = Person.oneSync({ 60 | name: "Jane Doe" 61 | }); 62 | assert.equal(person.name, "Jane Doe"); 63 | }); 64 | 65 | describe("if no match", function () { 66 | before(setup()); 67 | 68 | it("should return null", function () { 69 | var person = Person.oneSync({ 70 | name: "Jack Doe" 71 | }); 72 | assert.equal(person, null); 73 | }); 74 | }); 75 | }); 76 | }); -------------------------------------------------------------------------------- /test/integration/model-save.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.save()", function () { 5 | var db = null; 6 | var Person = null; 7 | 8 | var setup = function (nameDefinition, opts) { 9 | opts = opts || {}; 10 | 11 | return function () { 12 | Person = db.define("person", { 13 | name: nameDefinition || String 14 | }, opts || {}); 15 | 16 | Person.hasOne("parent", Person, opts.hasOneOpts); 17 | if ('saveAssociationsByDefault' in opts) { 18 | Person.settings.set( 19 | 'instance.saveAssociationsByDefault', opts.saveAssociationsByDefault 20 | ); 21 | } 22 | 23 | return helper.dropSync(Person); 24 | }; 25 | }; 26 | 27 | before(function () { 28 | db = helper.connect(); 29 | 30 | }); 31 | 32 | after(function () { 33 | return db.closeSync(); 34 | }); 35 | 36 | describe("if properties have default values", function () { 37 | before(setup({ 38 | type: "text", 39 | defaultValue: "John" 40 | })); 41 | 42 | it("should use it if not defined", function () { 43 | var John = new Person(); 44 | 45 | John.saveSync(); 46 | assert.equal(John.name, "John"); 47 | }); 48 | }); 49 | 50 | describe("with callback", function () { 51 | before(setup()); 52 | 53 | it("should save item and return id", function () { 54 | var John = new Person({ 55 | name: "John" 56 | }); 57 | John.saveSync(); 58 | 59 | assert.exist(John[Person.id]); 60 | 61 | var JohnCopy = Person.getSync(John[Person.id]); 62 | 63 | assert.equal(JohnCopy[Person.id], John[Person.id]); 64 | assert.equal(JohnCopy.name, John.name); 65 | }); 66 | }); 67 | 68 | xdescribe("without callback", function () { 69 | before(setup()); 70 | 71 | it("should still save item and return id", function () { 72 | var John = new Person({ 73 | name: "John" 74 | }); 75 | John.save(); 76 | John.on("save", function (err) { 77 | assert.equal(err, null); 78 | assert.exist(John[Person.id]); 79 | 80 | Person.get(John[Person.id], function (err, JohnCopy) { 81 | assert.equal(err, null); 82 | 83 | assert.equal(JohnCopy[Person.id], John[Person.id]); 84 | assert.equal(JohnCopy.name, John.name); 85 | 86 | return done(); 87 | }); 88 | }); 89 | }); 90 | }); 91 | 92 | describe("with properties object", function () { 93 | before(setup()); 94 | 95 | it("should update properties, save item and return id", function () { 96 | var John = new Person({ 97 | name: "Jane" 98 | }); 99 | John.saveSync({ 100 | name: "John" 101 | }); 102 | 103 | assert.exist(John[Person.id]); 104 | assert.equal(John.name, "John"); 105 | 106 | var JohnCopy = Person.getSync(John[Person.id]); 107 | 108 | assert.equal(JohnCopy[Person.id], John[Person.id]); 109 | assert.equal(JohnCopy.name, John.name); 110 | }); 111 | }); 112 | 113 | describe("with unknown argument type", function () { 114 | before(setup()); 115 | 116 | it("should should throw", function () { 117 | var John = new Person({ 118 | name: "Jane" 119 | }); 120 | 121 | assert.throws(function () { 122 | John.saveSync("will-fail"); 123 | }); 124 | }); 125 | }); 126 | 127 | describe("if passed an association instance", function () { 128 | before(setup()); 129 | 130 | it("should save association first and then save item and return id", function () { 131 | var Jane = new Person({ 132 | name: "Jane" 133 | }); 134 | var John = new Person({ 135 | name: "John", 136 | parent: Jane 137 | }); 138 | John.saveSync(); 139 | 140 | assert.isTrue(John.saved()); 141 | assert.isTrue(Jane.saved()); 142 | 143 | assert.exist(John[Person.id]); 144 | assert.exist(Jane[Person.id]); 145 | }); 146 | }); 147 | 148 | describe("if passed an association object", function () { 149 | before(setup()); 150 | 151 | it("should save association first and then save item and return id", function () { 152 | var John = new Person({ 153 | name: "John", 154 | parent: { 155 | name: "Jane" 156 | } 157 | }); 158 | John.saveSync(); 159 | 160 | assert.isTrue(John.saved()); 161 | assert.isTrue(John.parent.saved()); 162 | 163 | assert.exist(John[Person.id]); 164 | assert.exist(John.parent[Person.id]); 165 | assert.equal(John.parent.name, "Jane"); 166 | }); 167 | }); 168 | 169 | xdescribe("if autoSave is on", function () { 170 | before(setup(null, { 171 | autoSave: true 172 | })); 173 | 174 | it("should save the instance as soon as a property is changed", function () { 175 | var John = new Person({ 176 | name: "Jhon" 177 | }); 178 | John.save(function (err) { 179 | assert.equal(err, null); 180 | 181 | John.on("save", function () { 182 | return done(); 183 | }); 184 | 185 | John.name = "John"; 186 | }); 187 | }); 188 | }); 189 | 190 | describe("with saveAssociations", function () { 191 | var afterSaveCalled = false; 192 | 193 | describe("default on in settings", function () { 194 | beforeEach(function () { 195 | function afterSave() { 196 | afterSaveCalled = true; 197 | } 198 | var hooks = { 199 | afterSave: afterSave 200 | }; 201 | 202 | setup(null, { 203 | hooks: hooks, 204 | cache: false, 205 | hasOneOpts: { 206 | autoFetch: true 207 | } 208 | })(); 209 | var olga = Person.createSync({ 210 | name: 'Olga' 211 | }); 212 | 213 | assert.exist(olga); 214 | var hagar = Person.createSync({ 215 | name: 'Hagar', 216 | parent_id: olga.id 217 | }); 218 | assert.exist(hagar); 219 | afterSaveCalled = false; 220 | }); 221 | 222 | it("should be on", function () { 223 | assert.equal(Person.settings.get('instance.saveAssociationsByDefault'), true); 224 | }); 225 | 226 | it("off should not save associations but save itself", function () { 227 | var hagar = Person.oneSync({ 228 | name: 'Hagar' 229 | }); 230 | 231 | assert.exist(hagar.parent); 232 | 233 | hagar.parent.name = 'Olga2'; 234 | hagar.saveSync({ 235 | name: 'Hagar2' 236 | }, { 237 | saveAssociations: false 238 | }); 239 | 240 | assert.equal(afterSaveCalled, true); 241 | 242 | var olga = Person.getSync(hagar.parent.id) 243 | assert.equal(olga.name, 'Olga'); 244 | }); 245 | 246 | it("off should not save associations or itself if there are no changes", function () { 247 | var hagar = Person.oneSync({ 248 | name: 'Hagar' 249 | }); 250 | 251 | hagar.saveSync({}, { 252 | saveAssociations: false 253 | }); 254 | 255 | assert.equal(afterSaveCalled, false); 256 | 257 | var olga = Person.getSync(hagar.parent.id); 258 | assert.equal(olga.name, 'Olga'); 259 | }); 260 | 261 | it("unspecified should save associations and itself", function () { 262 | var hagar = Person.oneSync({ 263 | name: 'Hagar' 264 | }); 265 | assert.exist(hagar.parent); 266 | 267 | hagar.parent.name = 'Olga2'; 268 | hagar.saveSync({ 269 | name: 'Hagar2' 270 | }); 271 | 272 | var olga = Person.getSync(hagar.parent.id); 273 | assert.equal(olga.name, 'Olga2'); 274 | 275 | var person = Person.getSync(hagar.id); 276 | 277 | assert.equal(person.name, 'Hagar2'); 278 | }); 279 | 280 | it("on should save associations and itself", function () { 281 | var hagar = Person.oneSync({ 282 | name: 'Hagar' 283 | }); 284 | assert.exist(hagar.parent); 285 | 286 | hagar.parent.name = 'Olga2'; 287 | hagar.saveSync({ 288 | name: 'Hagar2' 289 | }, { 290 | saveAssociations: true 291 | }); 292 | 293 | var olga = Person.getSync(hagar.parent.id); 294 | assert.equal(olga.name, 'Olga2'); 295 | 296 | var person = Person.getSync(hagar.id); 297 | assert.equal(person.name, 'Hagar2'); 298 | }); 299 | }); 300 | 301 | describe("turned off in settings", function () { 302 | beforeEach(function () { 303 | function afterSave() { 304 | afterSaveCalled = true; 305 | } 306 | var hooks = { 307 | afterSave: afterSave 308 | }; 309 | 310 | setup(null, { 311 | hooks: hooks, 312 | cache: false, 313 | hasOneOpts: { 314 | autoFetch: true 315 | }, 316 | saveAssociationsByDefault: false 317 | })(); 318 | 319 | var olga = Person.createSync({ 320 | name: 'Olga' 321 | }); 322 | 323 | assert.exist(olga); 324 | var hagar = Person.createSync({ 325 | name: 'Hagar', 326 | parent_id: olga.id 327 | }); 328 | assert.exist(hagar); 329 | afterSaveCalled = false; 330 | }); 331 | 332 | it("should be off", function () { 333 | assert.equal(Person.settings.get('instance.saveAssociationsByDefault'), false); 334 | }); 335 | 336 | it("unspecified should not save associations but save itself", function () { 337 | var hagar = Person.oneSync({ 338 | name: 'Hagar' 339 | }); 340 | 341 | assert.exist(hagar.parent); 342 | 343 | hagar.parent.name = 'Olga2'; 344 | hagar.saveSync({ 345 | name: 'Hagar2' 346 | }); 347 | 348 | var olga = Person.getSync(hagar.parent.id); 349 | 350 | assert.equal(olga.name, 'Olga'); 351 | 352 | var person = Person.getSync(hagar.id); 353 | assert.equal(person.name, 'Hagar2'); 354 | }); 355 | 356 | it("off should not save associations but save itself", function () { 357 | var hagar = Person.oneSync({ 358 | name: 'Hagar' 359 | }); 360 | 361 | assert.exist(hagar.parent); 362 | 363 | hagar.parent.name = 'Olga2'; 364 | hagar.saveSync({ 365 | name: 'Hagar2' 366 | }, { 367 | saveAssociations: false 368 | }); 369 | assert.equal(afterSaveCalled, true); 370 | 371 | var olga = Person.getSync(hagar.parent.id); 372 | assert.equal(olga.name, 'Olga'); 373 | }); 374 | 375 | it("on should save associations and itself", function () { 376 | var hagar = Person.oneSync({ 377 | name: 'Hagar' 378 | }); 379 | 380 | assert.exist(hagar.parent); 381 | 382 | hagar.parent.name = 'Olga2'; 383 | hagar.saveSync({ 384 | name: 'Hagar2' 385 | }, { 386 | saveAssociations: true 387 | }); 388 | 389 | var olga = Person.getSync(hagar.parent.id); 390 | assert.equal(olga.name, 'Olga2'); 391 | 392 | var person = Person.getSync(hagar.id); 393 | assert.equal(person.name, 'Hagar2'); 394 | }); 395 | }); 396 | }); 397 | 398 | xdescribe("with a point property", function () { 399 | // if (common.protocol() == 'sqlite' || common.protocol() == 'mongodb') return; 400 | 401 | it("should save the instance as a geospatial point", function () { 402 | setup({ 403 | type: "point" 404 | }, null)(function () { 405 | var John = new Person({ 406 | name: { 407 | x: 51.5177, 408 | y: -0.0968 409 | } 410 | }); 411 | John.save(function (err) { 412 | assert.equal(err, null); 413 | 414 | John.name.should.be.an.instanceOf(Object); 415 | assert.property(John.name, 'x', 51.5177); 416 | assert.property(John.name, 'y', -0.0968); 417 | return done(); 418 | }); 419 | }); 420 | }); 421 | }); 422 | 423 | xdescribe("mockable", function () { 424 | before(setup()); 425 | 426 | it("save should be writable", function () { 427 | var John = new Person({ 428 | name: "John" 429 | }); 430 | var saveCalled = false; 431 | John.save = function (cb) { 432 | saveCalled = true; 433 | cb(null); 434 | }; 435 | John.save(function (err) { 436 | assert.equal(saveCalled, true); 437 | return done(); 438 | }); 439 | }); 440 | 441 | it("saved should be writable", function () { 442 | var John = new Person({ 443 | name: "John" 444 | }); 445 | var savedCalled = false; 446 | John.saved = function () { 447 | savedCalled = true; 448 | return true; 449 | }; 450 | 451 | John.saved() 452 | assert.isTrue(savedCalled); 453 | done(); 454 | }) 455 | }); 456 | }); -------------------------------------------------------------------------------- /test/integration/model-sync.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Model.sync", function () { 5 | var db = null; 6 | 7 | before(function () { 8 | db = helper.connect(); 9 | }); 10 | 11 | after(function () { 12 | db.closeSync(); 13 | }); 14 | 15 | // SQLite scopes index names to a database and NOT a table, so 16 | // index name collisions were possible. This tests the workaround. 17 | it("should work with multiple same-named indexes", function () { 18 | var A, B, C; 19 | 20 | A = db.define('a', { 21 | name: String 22 | }); 23 | B = db.define('b', { 24 | name: String 25 | }); 26 | C = db.define('c', { 27 | name: String 28 | }); 29 | 30 | A.hasMany('bees', B, {}, { 31 | reverse: 'eighs' 32 | }); 33 | A.hasMany('cees', C, {}, { 34 | reverse: 'eighs' 35 | }); 36 | 37 | helper.dropSync([A, B, C]); 38 | }); 39 | }); -------------------------------------------------------------------------------- /test/integration/predefined-validators.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var validators = require('../../').validators; 3 | var undef = undefined; 4 | 5 | function checkValidation(expected) { 6 | return function (returned) { 7 | assert.equal(returned, expected); 8 | }; 9 | } 10 | 11 | describe("Predefined Validators", function () { 12 | 13 | describe("equalToProperty('name')", function () { 14 | it("should pass if equal", function () { 15 | validators.equalToProperty('name').call({ 16 | name: "John Doe" 17 | }, 'John Doe', checkValidation()); 18 | }); 19 | it("should not pass if not equal", function () { 20 | validators.equalToProperty('name').call({ 21 | name: "John" 22 | }, 'John Doe', checkValidation('not-equal-to-property')); 23 | }); 24 | it("should not pass even if equal to other property", function () { 25 | validators.equalToProperty('name').call({ 26 | surname: "John Doe" 27 | }, 'John Doe', checkValidation('not-equal-to-property')); 28 | }); 29 | }); 30 | 31 | describe("unique()", function () { 32 | var db = null; 33 | var Person = null; 34 | 35 | var setup = function () { 36 | return function () { 37 | Person = db.define("person", { 38 | name: String, 39 | surname: String 40 | }, { 41 | validations: { 42 | surname: validators.unique() 43 | } 44 | }); 45 | 46 | Person.settings.set("instance.returnAllErrors", false); 47 | 48 | return helper.dropSync(Person, function () { 49 | Person.createSync([{ 50 | name: "John", 51 | surname: "Doe" 52 | }]); 53 | }); 54 | }; 55 | }; 56 | 57 | before(function () { 58 | db = helper.connect(); 59 | setup()(); 60 | }); 61 | 62 | after(function () { 63 | return db.closeSync(); 64 | }); 65 | 66 | it("should not pass if more elements with that property exist", function () { 67 | var janeDoe = new Person({ 68 | name: "Jane", 69 | surname: "Doe" // <-- in table already! 70 | }); 71 | try { 72 | janeDoe.saveSync(); 73 | } catch (err) { 74 | assert.propertyVal(err, "property", "surname"); 75 | assert.propertyVal(err, "value", "Doe"); 76 | assert.propertyVal(err, "msg", "not-unique"); 77 | } 78 | }); 79 | 80 | it("should pass if no more elements with that property exist", function () { 81 | var janeDean = new Person({ 82 | name: "Jane", 83 | surname: "Dean" // <-- not in table 84 | }); 85 | janeDean.saveSync(); 86 | }); 87 | 88 | it("should pass if resaving the same instance", function () { 89 | var Johns = Person.findSync({ 90 | name: "John", 91 | surname: "Doe" 92 | }); 93 | assert.propertyVal(Johns, "length", 1); 94 | Johns[0].surname = "Doe"; // forcing resave 95 | Johns[0].saveSync(); 96 | }); 97 | }); 98 | 99 | }); -------------------------------------------------------------------------------- /test/integration/property-custom.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("custom types", function () { 5 | var db = null; 6 | 7 | before(function () { 8 | db = helper.connect(); 9 | }); 10 | 11 | after(function () { 12 | db.closeSync(); 13 | }); 14 | 15 | describe("simple", function () { 16 | var LottoTicket = null; 17 | 18 | before(function () { 19 | db.defineType('numberArray', { 20 | datastoreType: function (prop) { 21 | return 'TEXT' 22 | }, 23 | valueToProperty: function (value, prop) { 24 | if (Array.isArray(value)) { 25 | return value; 26 | } else { 27 | if (Buffer.isBuffer(value)) 28 | value = value.toString(); 29 | return value.split(',').map(function (v) { 30 | return Number(v); 31 | }); 32 | } 33 | }, 34 | propertyToValue: function (value, prop) { 35 | return value.join(',') 36 | } 37 | }); 38 | 39 | LottoTicket = db.define('lotto_ticket', { 40 | numbers: { 41 | type: 'numberArray' 42 | } 43 | }); 44 | 45 | return helper.dropSync(LottoTicket); 46 | }); 47 | 48 | it("should create the table", function () { 49 | assert.ok(true); 50 | }); 51 | 52 | it("should store data in the table", function () { 53 | var ticket = new LottoTicket({ 54 | numbers: [4, 23, 6, 45, 9, 12, 3, 29] 55 | }); 56 | 57 | ticket.saveSync(); 58 | 59 | var items = LottoTicket.find().allSync(); 60 | assert.equal(items.length, 1); 61 | assert.ok(Array.isArray(items[0].numbers)); 62 | 63 | assert.deepEqual([4, 23, 6, 45, 9, 12, 3, 29], items[0].numbers); 64 | }); 65 | 66 | describe("hasMany extra properties", function () { 67 | it("should work", function () { 68 | db.defineType('customDate', { 69 | datastoreType: function (prop) { 70 | return 'TEXT'; 71 | } 72 | }); 73 | var Person = db.define('person', { 74 | name: String, 75 | surname: String, 76 | age: Number 77 | }); 78 | var Pet = db.define('pet', { 79 | name: String 80 | }); 81 | Person.hasMany('pets', Pet, { 82 | date: { 83 | type: 'customDate' 84 | } 85 | }, { 86 | autoFetch: true 87 | }); 88 | 89 | return helper.dropSync([Person, Pet], function () { 90 | var person = Person.createSync({ 91 | name: "John", 92 | surname: "Doe", 93 | age: 20 94 | }); 95 | 96 | var pet = Pet.createSync({ 97 | name: 'Fido' 98 | }); 99 | 100 | person.addPetsSync(pet, { 101 | date: '2014-05-20' 102 | }); 103 | 104 | var freshPerson = Person.getSync(person.id); 105 | assert.equal(freshPerson.pets.length, 1); 106 | assert.equal(freshPerson.pets[0].extra.date, '2014-05-20'); 107 | }); 108 | }); 109 | }); 110 | }); 111 | 112 | describe("complex", function () { 113 | var WonkyTotal = null; 114 | 115 | before(function () { 116 | db.defineType('wonkyNumber', { 117 | datastoreType: function (prop) { 118 | return 'INTEGER'; 119 | }, 120 | datastoreGet: function (prop, helper) { 121 | return helper.escape('?? - 1', [prop.mapsTo]); 122 | }, 123 | valueToProperty: function (value, prop) { 124 | return value + 7; 125 | }, 126 | propertyToValue: function (value, prop) { 127 | if (value == null) { 128 | return value; 129 | } else { 130 | return function (helper) { 131 | return helper.escape('(? - 2)', [value]); 132 | }; 133 | } 134 | } 135 | }); 136 | 137 | WonkyTotal = db.define('wonky', { 138 | name: String, 139 | total: { 140 | type: 'wonkyNumber', 141 | mapsTo: 'blah_total' 142 | } 143 | }); 144 | 145 | return helper.dropSync(WonkyTotal); 146 | }); 147 | 148 | it("should store wonky total in a differently named field", function () { 149 | var item = new WonkyTotal(); 150 | 151 | item.name = "cabbages"; 152 | item.total = 8; 153 | 154 | item.saveSync(); 155 | assert.equal(item.total, 15); 156 | 157 | var item = WonkyTotal.getSync(item.id); 158 | 159 | assert.equal(item.total, 19); // (15 - 2) - 1 + 7 160 | }); 161 | }); 162 | 163 | }); -------------------------------------------------------------------------------- /test/integration/property-lazyload.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("LazyLoad properties", function () { 5 | var db = null; 6 | var Person = null; 7 | var PersonPhoto = Buffer.alloc(1024); // fake photo 8 | var OtherPersonPhoto = Buffer.alloc(1024); // other fake photo 9 | 10 | var setup = function () { 11 | return function () { 12 | Person = db.define("person", { 13 | name: String, 14 | photo: { 15 | type: "binary", 16 | lazyload: true 17 | } 18 | }); 19 | 20 | ORM.singleton.clear(); 21 | 22 | helper.dropSync(Person, function () { 23 | Person.createSync({ 24 | name: "John Doe", 25 | photo: PersonPhoto 26 | }); 27 | }); 28 | }; 29 | }; 30 | 31 | before(function () { 32 | db = helper.connect(); 33 | }); 34 | 35 | after(function () { 36 | return db.closeSync(); 37 | }); 38 | 39 | describe("when defined", function () { 40 | before(setup()); 41 | 42 | it("should not be available when fetching an instance", function () { 43 | var John = Person.find().firstSync(); 44 | 45 | assert.isObject(John); 46 | 47 | assert.propertyVal(John, "name", "John Doe"); 48 | assert.propertyVal(John, "photo", null); 49 | }); 50 | 51 | it("should have apropriate accessors", function () { 52 | var John = Person.find().firstSync(); 53 | 54 | assert.isObject(John); 55 | assert.isFunction(John.getPhoto); 56 | assert.isFunction(John.setPhoto); 57 | assert.isFunction(John.removePhoto); 58 | }); 59 | 60 | it("getAccessor should return property", function () { 61 | var John = Person.find().firstSync(); 62 | 63 | assert.isObject(John); 64 | 65 | var photo = John.getPhotoSync(); 66 | 67 | assert.equal(photo.toString(), PersonPhoto.toString()); 68 | }); 69 | 70 | it("setAccessor should change property", function () { 71 | var John = Person.find().firstSync(); 72 | 73 | assert.isObject(John); 74 | 75 | John.setPhotoSync(OtherPersonPhoto); 76 | 77 | 78 | var John = Person.find().firstSync(); 79 | 80 | assert.isObject(John); 81 | 82 | var photo = John.getPhotoSync(); 83 | assert.equal(photo.toString(), OtherPersonPhoto.toString()); 84 | }); 85 | 86 | it("removeAccessor should change property", function () { 87 | var John = Person.find().firstSync(); 88 | 89 | assert.isObject(John); 90 | 91 | John.removePhotoSync(); 92 | 93 | var John = Person.getSync(John[Person.id]); 94 | 95 | assert.isObject(John); 96 | 97 | var photo = John.getPhotoSync(); 98 | assert.equal(photo, null); 99 | }); 100 | }); 101 | }); -------------------------------------------------------------------------------- /test/integration/property-maps-to.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var helper = require('../support/spec_helper'); 3 | var ORM = require('../../'); 4 | 5 | describe("Property.mapsTo", function () { 6 | var db = null; 7 | 8 | before(function () { 9 | db = helper.connect(); 10 | }); 11 | 12 | after(function () { 13 | return db.closeSync(); 14 | }); 15 | 16 | describe("normal", function () { 17 | var Book = null; 18 | var id1 = null, 19 | id2 = null; 20 | 21 | before(function () { 22 | Book = db.define("book", { 23 | title: { 24 | type: 'text', 25 | mapsTo: 'book_title', 26 | required: true 27 | }, 28 | pages: { 29 | type: 'integer', 30 | required: false 31 | } 32 | }); 33 | return helper.dropSync(Book); 34 | }); 35 | 36 | it("should create", function () { 37 | var book = Book.createSync({ 38 | title: "History of the wheel", 39 | pages: 297 40 | }); 41 | assert.exist(book); 42 | assert.equal(book.title, "History of the wheel"); 43 | id1 = book.id; 44 | }); 45 | 46 | it("should save new", function () { 47 | var book = new Book({ 48 | title: "Stuff", 49 | pages: 33 50 | }) 51 | book = book.saveSync(); 52 | 53 | assert.exist(book); 54 | assert.equal(book.title, "Stuff"); 55 | id2 = book.id; 56 | }); 57 | 58 | it("should get book1", function () { 59 | var book = Book.getSync(id1); 60 | assert.exist(book); 61 | assert.equal(book.title, "History of the wheel"); 62 | }); 63 | 64 | it("should get book2", function () { 65 | var book = Book.getSync(id2); 66 | assert.exist(book); 67 | assert.equal(book.title, "Stuff"); 68 | }); 69 | 70 | it("should find", function () { 71 | var book = Book.oneSync({ 72 | title: "History of the wheel" 73 | }); 74 | assert.exist(book); 75 | assert.equal(book.title, "History of the wheel"); 76 | }); 77 | 78 | it("should update", function () { 79 | var book = Book.oneSync(); 80 | assert.exist(book); 81 | assert.equal(book.title, "History of the wheel"); 82 | 83 | book.title = "Quantum theory"; 84 | book.pages = 5; 85 | 86 | book.saveSync(); 87 | assert.equal(book.title, "Quantum theory"); 88 | 89 | var freshBook = Book.getSync(book.id); 90 | assert.exist(freshBook); 91 | assert.equal(book.title, "Quantum theory"); 92 | }); 93 | 94 | it("should order", function () { 95 | Book.createSync({ 96 | title: "Zzz", 97 | pages: 2 98 | }); 99 | Book.createSync({ 100 | title: "Aaa", 101 | pages: 3 102 | }); 103 | 104 | var items = Book.find().order("-title").allSync(); 105 | assert.equal( 106 | _.map(items, 'title').join(','), 107 | "Zzz,Stuff,Quantum theory,Aaa" 108 | ) 109 | items = Book.find().order("title").allSync(); 110 | assert.equal( 111 | _.map(items, 'title').join(','), 112 | "Aaa,Quantum theory,Stuff,Zzz" 113 | ) 114 | }); 115 | }); 116 | 117 | describe("keys", function () { 118 | var Person = null; 119 | var id1 = null, 120 | id2 = null; 121 | 122 | before(function () { 123 | Person = db.define("person", { 124 | firstName: { 125 | type: 'text', 126 | mapsTo: 'first_name', 127 | key: true 128 | }, 129 | lastName: { 130 | type: 'text', 131 | mapsTo: 'last_name', 132 | key: true 133 | }, 134 | age: { 135 | type: 'integer' 136 | } 137 | }); 138 | 139 | return helper.dropSync(Person); 140 | }); 141 | 142 | it("should throw an error if invalid keys are specified", function () { 143 | assert.throws(function () { 144 | db.define("blah", { 145 | name: { 146 | type: 'text' 147 | } 148 | }, { 149 | id: ['banana'] 150 | }); 151 | }, "Model defined without any keys"); 152 | }); 153 | 154 | it("should create", function () { 155 | var person = Person.createSync({ 156 | firstName: 'John', 157 | lastName: 'Smith', 158 | age: 48 159 | }); 160 | assert.exist(person); 161 | assert.equal(person.firstName, 'John'); 162 | assert.equal(person.lastName, 'Smith'); 163 | id1 = [person.firstName, person.lastName]; 164 | }); 165 | 166 | it("should save new", function () { 167 | var person = new Person({ 168 | firstName: 'Jane', 169 | lastName: 'Doe', 170 | age: 50 171 | }); 172 | 173 | person.saveSync(); 174 | assert.exist(person); 175 | assert.equal(person.firstName, 'Jane'); 176 | assert.equal(person.lastName, 'Doe'); 177 | id2 = [person.firstName, person.lastName]; 178 | }); 179 | 180 | it("should get person1", function () { 181 | var person = Person.getSync(id1[0], id1[1]); 182 | assert.exist(person); 183 | assert.equal(person.firstName, 'John'); 184 | assert.equal(person.lastName, 'Smith'); 185 | }); 186 | 187 | it("should get person2", function () { 188 | var person = Person.getSync(id2[0], id2[1]); 189 | assert.exist(person); 190 | assert.equal(person.firstName, 'Jane'); 191 | assert.equal(person.lastName, 'Doe'); 192 | }); 193 | 194 | it("should find", function () { 195 | var person = Person.oneSync({ 196 | firstName: 'Jane' 197 | }); 198 | assert.exist(person); 199 | assert.equal(person.firstName, 'Jane'); 200 | assert.equal(person.lastName, 'Doe'); 201 | }); 202 | 203 | it("should update", function () { 204 | var person = Person.oneSync({ 205 | firstName: 'Jane' 206 | }); 207 | assert.exist(person); 208 | 209 | person.firstName = 'Jeniffer'; 210 | person.saveSync(); 211 | 212 | assert.equal(person.firstName, 'Jeniffer'); 213 | assert.equal(person.lastName, 'Doe'); 214 | 215 | var freshPerson = Person.getSync(person.firstName, person.lastName); 216 | assert.exist(freshPerson); 217 | 218 | assert.equal(freshPerson.firstName, 'Jeniffer'); 219 | assert.equal(freshPerson.lastName, 'Doe'); 220 | 221 | freshPerson.lastName = 'Dee'; 222 | freshPerson.saveSync(); 223 | 224 | assert.equal(freshPerson.firstName, 'Jeniffer'); 225 | assert.equal(freshPerson.lastName, 'Dee'); 226 | 227 | var jennifer = Person.getSync(freshPerson.firstName, freshPerson.lastName); 228 | 229 | assert.equal(jennifer.firstName, 'Jeniffer'); 230 | assert.equal(jennifer.lastName, 'Dee'); 231 | }); 232 | 233 | it("should count", function () { 234 | var person = Person.createSync({ 235 | firstName: 'Greg', 236 | lastName: 'McDoofus', 237 | age: 30 238 | }); 239 | 240 | var count = Person.find({ 241 | firstName: 'Greg', 242 | lastName: 'McDoofus' 243 | }).countSync(); 244 | assert.equal(count, 1); 245 | }); 246 | 247 | it("should chain delete", function () { 248 | var person = Person.createSync({ 249 | firstName: 'Alfred', 250 | lastName: 'McDoogle', 251 | age: 50 252 | }); 253 | 254 | var count = Person.find({ 255 | firstName: 'Alfred', 256 | lastName: 'McDoogle' 257 | }).countSync(); 258 | assert.equal(count, 1); 259 | 260 | Person.find({ 261 | firstName: 'Alfred', 262 | lastName: 'McDoogle' 263 | }).removeSync(); 264 | 265 | var count = Person.find({ 266 | firstName: 'Alfred', 267 | lastName: 'McDoogle' 268 | }).countSync(); 269 | assert.equal(count, 0); 270 | }); 271 | }); 272 | }); -------------------------------------------------------------------------------- /test/integration/property.js: -------------------------------------------------------------------------------- 1 | var ORM = require("../.."); 2 | var Property = ORM.Property; 3 | 4 | describe("Property", function () { 5 | it("passing String should return type: 'text'", function () { 6 | assert.equal(Property.normalize({ 7 | prop: String, 8 | customTypes: {}, 9 | settings: ORM.settings, 10 | name: 'abc' 11 | }).type, "text"); 12 | }); 13 | it("passing Number should return type: 'number'", function () { 14 | assert.equal(Property.normalize({ 15 | prop: Number, 16 | customTypes: {}, 17 | settings: ORM.settings, 18 | name: 'abc' 19 | }).type, "number"); 20 | }); 21 | it("passing deprecated rational: false number should return type: 'integer'", function () { 22 | assert.equal(Property.normalize({ 23 | prop: { 24 | type: 'number', 25 | rational: false 26 | }, 27 | customTypes: {}, 28 | settings: ORM.settings, 29 | name: 'abc' 30 | }).type, "integer"); 31 | }); 32 | 33 | it("passing Boolean should return type: 'boolean'", function () { 34 | assert.equal(Property.normalize({ 35 | prop: Boolean, 36 | customTypes: {}, 37 | settings: ORM.settings, 38 | name: 'abc' 39 | }).type, "boolean"); 40 | }); 41 | it("passing Date should return type: 'date'", function () { 42 | assert.equal(Property.normalize({ 43 | prop: Date, 44 | customTypes: {}, 45 | settings: ORM.settings, 46 | name: 'abc' 47 | }).type, "date"); 48 | }); 49 | it("passing Object should return type: 'object'", function () { 50 | assert.equal(Property.normalize({ 51 | prop: Object, 52 | customTypes: {}, 53 | settings: ORM.settings, 54 | name: 'abc' 55 | }).type, "object"); 56 | }); 57 | it("passing Buffer should return type: 'binary'", function () { 58 | assert.equal(Property.normalize({ 59 | prop: Buffer, 60 | customTypes: {}, 61 | settings: ORM.settings, 62 | name: 'abc' 63 | }).type, "binary"); 64 | }); 65 | it("passing an Array of items should return type: 'enum' with list of items", function () { 66 | var prop = Property.normalize({ 67 | prop: [1, 2, 3], 68 | customTypes: {}, 69 | settings: ORM.settings, 70 | name: 'abc' 71 | }) 72 | 73 | assert.equal(prop.type, "enum"); 74 | assert.propertyVal(prop.values, "length", 3); 75 | }); 76 | describe("passing a string type", function () { 77 | it("should return type: ", function () { 78 | assert.equal(Property.normalize({ 79 | prop: "text", 80 | customTypes: {}, 81 | settings: ORM.settings, 82 | name: 'abc' 83 | }).type, "text"); 84 | }); 85 | it("should accept: 'point'", function () { 86 | assert.equal(Property.normalize({ 87 | prop: "point", 88 | customTypes: {}, 89 | settings: ORM.settings, 90 | name: 'abc' 91 | }).type, "point"); 92 | }); 93 | 94 | describe("if not valid", function () { 95 | it("should throw", function () { 96 | assert.throws(function () { 97 | Property.normalize({ 98 | prop: "string", 99 | customTypes: {}, 100 | settings: ORM.settings, 101 | name: 'abc' 102 | }) 103 | }); 104 | }); 105 | }); 106 | }); 107 | it("should not modify the original property object", function () { 108 | var original = { 109 | type: 'text', 110 | required: true 111 | }; 112 | 113 | var normalized = Property.normalize({ 114 | prop: original, 115 | customTypes: {}, 116 | settings: ORM.settings, 117 | name: 'abc' 118 | }); 119 | 120 | original.test = 3; 121 | assert.strictEqual(normalized.test, undefined); 122 | }); 123 | }); -------------------------------------------------------------------------------- /test/integration/settings.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | var Settings = ORM.Settings; 4 | 5 | describe("Settings", function () { 6 | describe("changed on connection instance", function () { 7 | it("should not change global defaults", function () { 8 | var setting = 'instance.returnAllErrors'; 9 | var defaultValue = ORM.settings.get(setting); 10 | 11 | var db = helper.connect(); 12 | db.settings.set(setting, !defaultValue); 13 | db.closeSync(); 14 | 15 | db = helper.connect(); 16 | assert.equal(db.settings.get(setting), defaultValue); 17 | db.closeSync(); 18 | }); 19 | }); 20 | 21 | describe("#get", function () { 22 | var settings, returned; 23 | 24 | beforeEach(function () { 25 | settings = new Settings.Container({ 26 | a: [1, 2] 27 | }); 28 | returned = null; 29 | }); 30 | 31 | it("should clone everything it returns", function () { 32 | returned = settings.get('*'); 33 | returned.a = 123; 34 | 35 | assert.deepEqual(settings.get('a'), [1, 2]); 36 | }); 37 | 38 | it("should deep clone everything it returns", function () { 39 | returned = settings.get('*'); 40 | returned.a.push(3); 41 | 42 | assert.deepEqual(settings.get('a'), [1, 2]); 43 | }); 44 | }); 45 | 46 | describe("manipulating:", function () { 47 | var testFunction = function testFunction() { 48 | return "test"; 49 | }; 50 | var settings = new Settings.Container({}); 51 | 52 | describe("some.sub.object = 123.45", function () { 53 | before(function () { 54 | settings.set("some.sub.object", 123.45); 55 | }); 56 | 57 | it("should be 123.45", function () { 58 | assert.equal(settings.get("some.sub.object"), 123.45); 59 | }); 60 | }); 61 | 62 | describe("some....object = testFunction", function () { 63 | before(function () { 64 | settings.set("some....object", testFunction); 65 | }); 66 | 67 | it("should be testFunction", function () { 68 | assert.equal(settings.get("some....object"), testFunction); 69 | }); 70 | }); 71 | 72 | describe("not setting some.unknown.object", function () { 73 | it("should be undefined", function () { 74 | assert.equal(settings.get("some.unknown.object"), undefined); 75 | }); 76 | }); 77 | 78 | describe("unsetting some.sub.object", function () { 79 | before(function () { 80 | settings.unset("some.sub.object"); 81 | }); 82 | 83 | it("should be undefined", function () { 84 | assert.equal(settings.get("some.sub.object"), undefined); 85 | }); 86 | }); 87 | 88 | describe("unsetting some....object", function () { 89 | before(function () { 90 | settings.unset("some....object"); 91 | }); 92 | 93 | it("should be undefined", function () { 94 | assert.equal(settings.get("some....object"), undefined); 95 | }); 96 | }); 97 | 98 | describe("unsetting some.*", function () { 99 | before(function () { 100 | settings.unset("some.*"); 101 | }); 102 | 103 | it("should return undefined for any 'some' sub-element", function () { 104 | assert.equal(settings.get("some.other.stuff"), undefined); 105 | }); 106 | it("should return an empty object for some.*", function () { 107 | assert.isObject(settings.get("some.*")); 108 | assert.propertyVal(Object.keys(settings.get("some.*")), 'length', 0); 109 | }); 110 | it("should return an empty object for some", function () { 111 | assert.isObject(settings.get("some")); 112 | assert.propertyVal(Object.keys(settings.get("some")), 'length', 0); 113 | }); 114 | }); 115 | }); 116 | }); -------------------------------------------------------------------------------- /test/integration/smart-types.js: -------------------------------------------------------------------------------- 1 | var helper = require('../support/spec_helper'); 2 | var ORM = require('../../'); 3 | 4 | describe("Smart types", function () { 5 | var db = null; 6 | var User = null; 7 | var Profile = null; 8 | var Post = null; 9 | var Group = null; 10 | 11 | var setup = function () { 12 | return function () { 13 | User = db.define("user", { 14 | username: { 15 | type: 'text', 16 | size: 64 17 | }, 18 | password: { 19 | type: 'text', 20 | size: 128 21 | } 22 | }, { 23 | id: 'username' 24 | }); 25 | 26 | Profile = User.extendsTo("profile", { 27 | firstname: String, 28 | lastname: String 29 | }, { 30 | reverse: 'user', 31 | required: true 32 | }); 33 | 34 | Group = db.define("group", { 35 | name: { 36 | type: 'text', 37 | size: 64 38 | } 39 | }, { 40 | id: 'name' 41 | }); 42 | Group.hasMany('users', User, {}, { 43 | reverse: 'groups' 44 | }); 45 | 46 | Post = db.define("post", { 47 | content: String 48 | }, { 49 | 50 | }); 51 | Post.hasOne('user', User, { 52 | reverse: 'posts' 53 | }); 54 | 55 | ORM.singleton.clear(); 56 | return helper.dropSync([User, Profile, Group, Post], function () { 57 | var billy = User.createSync({ 58 | username: 'billy', 59 | password: 'hashed password' 60 | }); 61 | 62 | var profile = billy.setProfileSync(new Profile({ 63 | firstname: 'William', 64 | lastname: 'Franklin' 65 | })); 66 | var groups = billy.addGroupsSync([new Group({ 67 | name: 'admins' 68 | }), new Group({ 69 | name: 'developers' 70 | })]); 71 | var posts = billy.setPostsSync(new Post({ 72 | content: 'Hello world!' 73 | })); 74 | }); 75 | }; 76 | }; 77 | 78 | before(function () { 79 | db = helper.connect(); 80 | }); 81 | 82 | after(function () { 83 | return db.closeSync(); 84 | }); 85 | 86 | describe("extends", function () { 87 | before(setup()); 88 | 89 | it("should be able to get extendsTo with custom id", function () { 90 | var billy = User.getSync('billy'); 91 | assert.exist(billy); 92 | 93 | var profile = billy.getProfileSync(); 94 | assert.exist(profile); 95 | assert.equal(profile.firstname, 'William'); 96 | assert.equal(profile.lastname, 'Franklin'); 97 | }); 98 | 99 | it("should be able to get hasOne with custom id", function () { 100 | var billy = User.getSync('billy'); 101 | assert.exist(billy); 102 | 103 | var posts = billy.getPostsSync(); 104 | assert.exist(posts); 105 | assert.equal(posts.length, 1); 106 | assert.equal(posts[0].content, 'Hello world!'); 107 | }); 108 | 109 | it("should be able to get hasMany with custom id", function () { 110 | var billy = User.getSync('billy'); 111 | 112 | assert.exist(billy); 113 | 114 | var groups = billy.getGroupsSync(); 115 | 116 | assert.exist(groups); 117 | assert.equal(groups.length, 2); 118 | }); 119 | 120 | }); 121 | }); -------------------------------------------------------------------------------- /test/support/opts.js: -------------------------------------------------------------------------------- 1 | module.exports = 'sqlite:test.db'; -------------------------------------------------------------------------------- /test/support/spec_helper.js: -------------------------------------------------------------------------------- 1 | var ORM = require('../../'); 2 | 3 | module.exports.connect = function () { 4 | return ORM.connectSync("sqlite:test.db"); 5 | // return ORM.connectSync("mysql://root@localhost/test"); 6 | }; 7 | 8 | module.exports.dropSync = function (models, done) { 9 | if (!Array.isArray(models)) { 10 | models = [models]; 11 | } 12 | 13 | models.forEach(function (item) { 14 | item.dropSync(); 15 | item.syncSync(); 16 | }); 17 | 18 | if (done) 19 | done(); 20 | }; --------------------------------------------------------------------------------