Comparing version 0.0.1 to 0.1.0
{ | ||
"name": "nothinkdb", | ||
"version": "0.0.1", | ||
"version": "0.1.0", | ||
"description": "ORM: Object-Rethinkdb-Mapping", | ||
@@ -21,3 +21,3 @@ "main": "lib/nothinkdb.js", | ||
], | ||
"author": "ironhee", | ||
"author": "ironhee <leechulhee95@gmail.com>", | ||
"license": "MIT", | ||
@@ -24,0 +24,0 @@ "bugs": { |
@@ -9,17 +9,17 @@ import { expect } from 'chai'; | ||
describe('constructor', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
foo: Joi.string(), | ||
bar: Joi.string(), | ||
}); | ||
} | ||
}), | ||
}); | ||
it('should create link', () => { | ||
const link = new Link({ | ||
linker: { Table: Foo, key: 'foo' }, | ||
linkee: { Table: Foo, key: 'bar' }, | ||
left: { table: fooTable, field: 'foo' }, | ||
right: { table: fooTable, field: 'bar' }, | ||
}); | ||
expect(link.linker).to.deep.equal({ Table: Foo, key: 'foo' }); | ||
expect(link.linkee).to.deep.equal({ Table: Foo, key: 'bar' }); | ||
expect(link.left).to.deep.equal({ table: fooTable, field: 'foo' }); | ||
expect(link.right).to.deep.equal({ table: fooTable, field: 'bar' }); | ||
}); | ||
@@ -30,20 +30,20 @@ | ||
expect(() => new Link({ | ||
linker: {}, | ||
linkee: {}, | ||
left: {}, | ||
right: {}, | ||
})).to.throw(Error); | ||
expect(() => new Link({ | ||
linker: { Table: {}, key: null }, | ||
linkee: { Table: {}, key: null }, | ||
left: { table: {}, field: null }, | ||
right: { table: {}, field: null }, | ||
})).to.throw(Error); | ||
expect(() => new Link({ | ||
linker: { Table: Foo, key: null }, | ||
linkee: { Table: Foo, key: null }, | ||
left: { table: fooTable, field: null }, | ||
right: { table: fooTable, field: null }, | ||
})).to.throw(Error); | ||
expect(() => new Link({ | ||
linker: { Table: Foo, key: 'noop' }, | ||
linkee: { Table: Foo, key: 'noop' }, | ||
left: { table: fooTable, field: 'noop' }, | ||
right: { table: fooTable, field: 'noop' }, | ||
})).to.throw(Error); | ||
expect(() => new Link({ | ||
linker: { Table: Foo, key: 'foo' }, | ||
linkee: { Table: Foo, key: 'noop' }, | ||
left: { table: fooTable, field: 'foo' }, | ||
right: { table: fooTable, field: 'noop' }, | ||
})).to.throw(Error); | ||
@@ -50,0 +50,0 @@ }); |
@@ -0,302 +1,688 @@ | ||
import r from 'rethinkdb'; | ||
import Joi from 'joi'; | ||
import uuid from 'node-uuid'; | ||
import { expect } from 'chai'; | ||
import uuid from 'node-uuid'; | ||
import Joi from 'joi'; | ||
import Table from '../Table'; | ||
import Link from '../Link'; | ||
import { hasOne, belongsTo, hasMany, belongsToMany } from '../relations'; | ||
describe('Table', () => { | ||
describe('constructor', () => { | ||
it('should throw Error if \'table\' is not overrided', () => { | ||
expect(() => new Table()).to.throw(Error); | ||
}); | ||
let connection; | ||
before(async () => { | ||
connection = await r.connect({}); | ||
await r.branch(r.dbList().contains('test').not(), r.dbCreate('test'), null).run(connection); | ||
r.dbCreate('test'); | ||
}); | ||
it('should not throw Error if \'table\' is overrided', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
} | ||
expect(() => new Foo()).to.not.throw(Error); | ||
}); | ||
beforeEach(async () => { | ||
await r.branch(r.tableList().contains('foo').not(), r.tableCreate('foo'), null).run(connection); | ||
await r.branch(r.tableList().contains('bar').not(), r.tableCreate('bar'), null).run(connection); | ||
await r.branch(r.tableList().contains('foobar').not(), r.tableCreate('foobar'), null).run(connection); | ||
}); | ||
describe('static', () => { | ||
after(async () => { | ||
await connection.close(); | ||
}); | ||
describe('staic', () => { | ||
describe('schema', () => { | ||
it('has default property', () => { | ||
class Base extends Table { | ||
static table = 'base'; | ||
static schema = () => ({ | ||
...Table.schema(), | ||
name: Joi.string().default('hello'), | ||
}); | ||
} | ||
expect(Base.schema()).to.have.property('id'); | ||
expect(Base.schema()).to.have.property('createdAt'); | ||
expect(Base.schema()).to.have.property('updatedAt'); | ||
expect(Base.schema()).to.have.property('name'); | ||
expect(Table.schema).to.have.property('id'); | ||
expect(Table.schema).to.have.property('createdAt'); | ||
expect(Table.schema).to.have.property('updatedAt'); | ||
}); | ||
}); | ||
}); | ||
it('could be extended', () => { | ||
class Base extends Table { | ||
static table = 'base'; | ||
static schema = () => ({ | ||
...Table.schema(), | ||
name: Joi.string().default('hello'), | ||
}); | ||
} | ||
expect(Base.schema()).to.have.property('id'); | ||
expect(Base.schema()).to.have.property('createdAt'); | ||
expect(Base.schema()).to.have.property('updatedAt'); | ||
expect(Base.schema()).to.have.property('name'); | ||
describe('constructor', () => { | ||
it('schema could be extended', () => { | ||
const baseTable = new Table({ | ||
table: 'base', | ||
schema: () => ({ | ||
...Table.schema, | ||
name: Joi.string().default('hello'), | ||
}), | ||
}); | ||
expect(baseTable.schema()).to.have.property('id'); | ||
expect(baseTable.schema()).to.have.property('createdAt'); | ||
expect(baseTable.schema()).to.have.property('updatedAt'); | ||
expect(baseTable.schema()).to.have.property('name'); | ||
}); | ||
}); | ||
describe('validate', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
name: Joi.string().required(), | ||
}); | ||
} | ||
describe('validate', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
name: Joi.string().required(), | ||
}), | ||
}); | ||
it('should return true when data is valid', () => { | ||
expect(Foo.validate({ name: 'foo' })).to.be.true; | ||
it('should return true when data is valid', () => { | ||
expect(fooTable.validate({ name: 'foo' })).to.be.true; | ||
}); | ||
it('should throw error when invalid', () => { | ||
expect(fooTable.validate({})).to.be.false; | ||
}); | ||
}); | ||
describe('attempt', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
foo: Joi.string().default('foo'), | ||
bar: Joi.string().required(), | ||
}), | ||
}); | ||
it('should return with default properties', () => { | ||
const result = fooTable.attempt({ bar: 'bar' }); | ||
expect(result).to.have.property('foo', 'foo'); | ||
expect(result).to.have.property('bar', 'bar'); | ||
}); | ||
it('should throw error when invalid', () => { | ||
expect(() => fooTable.attempt({})).to.throw(Error); | ||
}); | ||
}); | ||
describe('create', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
foo: Joi.string().default('foo'), | ||
bar: Joi.string().required(), | ||
}), | ||
}); | ||
it('should return with default properties', () => { | ||
const result = fooTable.create({ bar: 'bar' }); | ||
expect(result).to.have.property('foo', 'foo'); | ||
expect(result).to.have.property('bar', 'bar'); | ||
}); | ||
it('should throw error when invalid', () => { | ||
expect(() => fooTable.create({})).to.throw(Error); | ||
}); | ||
}); | ||
describe('hasField', () => { | ||
it('should return true when specified fieldName is given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
name: Joi.string(), | ||
}), | ||
}); | ||
expect(fooTable.hasField('name')).to.be.true; | ||
}); | ||
it('should throw error when invalid', () => { | ||
expect(Foo.validate({})).to.be.false; | ||
it('should return false when unspecified fieldName is given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({}), | ||
}); | ||
expect(fooTable.hasField('name')).to.be.false; | ||
}); | ||
}); | ||
describe('attempt', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
foo: Joi.string().default('foo'), | ||
bar: Joi.string().required(), | ||
}); | ||
} | ||
describe('assertField', () => { | ||
it('should not throw error when specified fieldName is given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
name: Joi.string(), | ||
}), | ||
}); | ||
expect(() => fooTable.assertField('name')).to.not.throw(Error); | ||
}); | ||
it('should update default properties', () => { | ||
const result = Foo.attempt({ bar: 'bar' }); | ||
expect(result).to.have.property('foo', 'foo'); | ||
expect(result).to.have.property('bar', 'bar'); | ||
it('should throw error when unspecified fieldName is given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({}), | ||
}); | ||
expect(() => fooTable.assertField('name')).to.throw(Error); | ||
}); | ||
}); | ||
it('should throw error when invalid', () => { | ||
expect(() => Foo.attempt({})).to.throw(Error); | ||
describe('getField', () => { | ||
it('should return field schema when specified fieldName is given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
name: Joi.string(), | ||
}), | ||
}); | ||
const field = fooTable.getField('name'); | ||
expect(field).to.be.ok; | ||
expect(() => Joi.assert('string', field)).to.not.throw(Error); | ||
}); | ||
describe('hasColumn', () => { | ||
it('should return true when specified columnName is given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
name: Joi.string(), | ||
}); | ||
} | ||
expect(Foo.hasColumn('name')).to.be.true; | ||
it('should throw error when unspecified fieldName is given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({}), | ||
}); | ||
expect(() => fooTable.getField('name')).to.throw(Error); | ||
}); | ||
}); | ||
it('should return false when unspecified columnName is given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({}); | ||
} | ||
expect(Foo.hasColumn('name')).to.be.false; | ||
describe('getForeignKey', () => { | ||
it('should return primary key schema when any argument is not given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
pk: 'name', | ||
schema: () => ({ | ||
name: Joi.string().default(() => uuid.v4(), 'pk'), | ||
}), | ||
}); | ||
const field = fooTable.getForeignKey(); | ||
expect(field).to.be.ok; | ||
expect(() => Joi.assert('string', field)).to.not.throw(Error); | ||
}); | ||
describe('assertColumn', () => { | ||
it('should not throw error when specified columnName is given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
name: Joi.string(), | ||
}); | ||
} | ||
expect(() => Foo.assertColumn('name')).to.not.throw(Error); | ||
it('should return field schema when options.fieldName is given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
name: Joi.string().default(() => uuid.v4(), 'pk'), | ||
}), | ||
}); | ||
it('should throw error when unspecified columnName is given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({}); | ||
} | ||
expect(() => Foo.assertColumn('name')).to.throw(Error); | ||
const field = fooTable.getForeignKey({ fieldName: 'name' }); | ||
expect(field).to.be.ok; | ||
expect(() => Joi.assert('string', field)).to.not.throw(Error); | ||
}); | ||
it('should return and default(null) schema when options.isManyToMany is not given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
}); | ||
const field = fooTable.getForeignKey(); | ||
expect(field).to.be.ok; | ||
expect(Joi.attempt(undefined, field)).to.be.null; | ||
}); | ||
describe('getColumn', () => { | ||
it('should return column schema when specified columnName is given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
name: Joi.string(), | ||
}); | ||
} | ||
it('should return required() field schema when options.isManyToMany is given', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
}); | ||
const column = Foo.getColumn('name'); | ||
expect(column).to.be.ok; | ||
expect(() => Joi.assert('string', column)).to.not.throw(Error); | ||
const field = fooTable.getForeignKey({ isManyToMany: true }); | ||
expect(field).to.be.ok; | ||
expect(() => Joi.assert(undefined, field)).to.throw(Error); | ||
}); | ||
}); | ||
describe('linkTo', () => { | ||
it('should return link', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
barId: barTable.getForeignKey(), | ||
}), | ||
}); | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
}); | ||
it('should throw error when unspecified columnName is given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({}); | ||
} | ||
expect(() => Foo.getColumn('name')).to.throw(Error); | ||
const foo2bar = fooTable.linkTo(barTable, 'barId'); | ||
expect(foo2bar).to.be.ok; | ||
expect(foo2bar.constructor).to.equal(Link); | ||
expect(foo2bar.left).to.deep.equal({ | ||
table: fooTable, field: 'barId', | ||
}); | ||
expect(foo2bar.right).to.deep.equal({ | ||
table: barTable, field: 'id', | ||
}); | ||
}); | ||
}); | ||
describe('getForeignKey', () => { | ||
it('should return primary key schema when any argument is not given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static pk = 'name'; | ||
static schema = () => ({ | ||
name: Joi.string().default(() => uuid.v4(), 'pk'), | ||
}); | ||
} | ||
describe('linkedBy', () => { | ||
it('should return reverse link', () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
}); | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
fooId: fooTable.getForeignKey(), | ||
}), | ||
}); | ||
const column = Foo.getForeignKey(); | ||
expect(column).to.be.ok; | ||
expect(() => Joi.assert('string', column)).to.not.throw(Error); | ||
const foo2bar = fooTable.linkedBy(barTable, 'fooId'); | ||
expect(foo2bar).to.be.ok; | ||
expect(foo2bar.constructor).to.equal(Link); | ||
expect(foo2bar.left).to.deep.equal({ | ||
table: barTable, field: 'fooId', | ||
}); | ||
expect(foo2bar.right).to.deep.equal({ | ||
table: fooTable, field: 'id', | ||
}); | ||
}); | ||
}); | ||
it('should return column schema when options.columnName is given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
name: Joi.string().default(() => uuid.v4(), 'pk'), | ||
}); | ||
} | ||
describe('query', () => { | ||
it('should return table query', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
}); | ||
const config = await fooTable.query().config().run(connection); | ||
expect(config).to.have.property('name', 'foo'); | ||
}); | ||
}); | ||
const column = Foo.getForeignKey({ columnName: 'name' }); | ||
expect(column).to.be.ok; | ||
expect(() => Joi.assert('string', column)).to.not.throw(Error); | ||
describe('insert', () => { | ||
it('should insert data into database', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
name: Joi.string().required(), | ||
}), | ||
}); | ||
const foo = fooTable.attempt({ name: 'foo' }); | ||
await fooTable.insert(foo).run(connection); | ||
const fetchedfooTable = await fooTable.query().get(foo.id).run(connection); | ||
expect(foo).to.deep.equal(fetchedfooTable); | ||
}); | ||
}); | ||
it('should return default(null) schema when options.isManyToMany is not given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
...Table.schema(), | ||
}); | ||
} | ||
describe('get', () => { | ||
it('should get data from database', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
name: Joi.string().required(), | ||
}), | ||
}); | ||
const foo = fooTable.attempt({ name: 'foo' }); | ||
await fooTable.insert(foo).run(connection); | ||
const fetchedfooTable = await fooTable.get(foo.id).run(connection); | ||
expect(foo).to.deep.equal(fetchedfooTable); | ||
}); | ||
}); | ||
const column = Foo.getForeignKey(); | ||
expect(Joi.attempt(undefined, column)).to.be.null; | ||
describe('update', () => { | ||
it('should update data into database', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
name: Joi.string().required(), | ||
}), | ||
}); | ||
const foo = fooTable.attempt({ name: 'foo' }); | ||
await fooTable.insert(foo).run(connection); | ||
await fooTable.update(foo.id, { name: 'bar' }).run(connection); | ||
const fetchedfooTable = await fooTable.get(foo.id).run(connection); | ||
expect(fetchedfooTable).to.have.property('name', 'bar'); | ||
}); | ||
}); | ||
it('should return required() column schema when options.isManyToMany is given', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
...Table.schema(), | ||
}); | ||
} | ||
describe('delete', () => { | ||
it('should delete data from database', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
name: Joi.string().required(), | ||
}), | ||
}); | ||
const foo = fooTable.attempt({ name: 'foo' }); | ||
await fooTable.insert(foo).run(connection); | ||
await fooTable.delete(foo.id).run(connection); | ||
const fetchedfooTable = await fooTable.query().get(foo.id).run(connection); | ||
expect(fetchedfooTable).to.be.null; | ||
}); | ||
}); | ||
const column = Foo.getForeignKey({ isManyToMany: true }); | ||
expect(column).to.be.ok; | ||
expect(() => Joi.assert(undefined, column)).to.throw(Error); | ||
describe('withJoin', () => { | ||
it('should query hasOne relation', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
bar: hasOne(fooTable.linkedBy(barTable, 'fooId')), | ||
}), | ||
}); | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
fooId: fooTable.getForeignKey(), | ||
}), | ||
}); | ||
await fooTable.sync(connection); | ||
await barTable.sync(connection); | ||
const foo = fooTable.create({}); | ||
const bar = barTable.create({ fooId: foo.id }); | ||
await fooTable.insert(foo).run(connection); | ||
await barTable.insert(bar).run(connection); | ||
let query = fooTable.get(foo.id); | ||
query = await fooTable.withJoin(query, { bar: true }); | ||
const fetchedfooTable = await query.run(connection); | ||
expect(bar).to.deep.equal(fetchedfooTable.bar); | ||
}); | ||
describe('linkTo', () => { | ||
it('should return link', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
...Table.schema(), | ||
barId: Bar.getForeignKey(), | ||
}); | ||
} | ||
class Bar extends Table { | ||
static table = 'bar'; | ||
static schema = () => ({ | ||
...Table.schema(), | ||
}); | ||
} | ||
const foo2bar = Foo.linkTo(Bar, 'barId'); | ||
expect(foo2bar).to.be.ok; | ||
expect(foo2bar.constructor).to.equal(Link); | ||
expect(foo2bar.linker).to.deep.equal({ | ||
Table: Foo, key: 'barId', | ||
}); | ||
expect(foo2bar.linkee).to.deep.equal({ | ||
Table: Bar, key: 'id', | ||
}); | ||
it('should query belongsTo relation', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
barId: barTable.getForeignKey(), | ||
}), | ||
relations: () => ({ | ||
bar: belongsTo(fooTable.linkTo(barTable, 'barId')), | ||
}), | ||
}); | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
}); | ||
await fooTable.sync(connection); | ||
await barTable.sync(connection); | ||
const bar = barTable.create({}); | ||
const foo = fooTable.create({ barId: bar.id }); | ||
await fooTable.insert(foo).run(connection); | ||
await barTable.insert(bar).run(connection); | ||
let query = fooTable.get(foo.id); | ||
query = fooTable.withJoin(query, { bar: true }); | ||
const fetchedfooTable = await query.run(connection); | ||
expect(bar).to.deep.equal(fetchedfooTable.bar); | ||
}); | ||
describe('linkedBy', () => { | ||
it('should return reverse link', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
...Table.schema(), | ||
}); | ||
} | ||
class Bar extends Table { | ||
static table = 'bar'; | ||
static schema = () => ({ | ||
...Table.schema(), | ||
fooId: Foo.getForeignKey(), | ||
}); | ||
} | ||
const foo2bar = Foo.linkedBy(Bar, 'fooId'); | ||
expect(foo2bar).to.be.ok; | ||
expect(foo2bar.constructor).to.equal(Link); | ||
expect(foo2bar.linker).to.deep.equal({ | ||
Table: Bar, key: 'fooId', | ||
}); | ||
expect(foo2bar.linkee).to.deep.equal({ | ||
Table: Foo, key: 'id', | ||
}); | ||
it('should query hasMany relation', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
bars: hasMany(fooTable.linkedBy(barTable, 'fooId')), | ||
}), | ||
}); | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
fooId: fooTable.getForeignKey(), | ||
}), | ||
}); | ||
await fooTable.sync(connection); | ||
await barTable.sync(connection); | ||
const foo = fooTable.create({}); | ||
const bar = barTable.create({ fooId: foo.id }); | ||
await fooTable.insert(foo).run(connection); | ||
await barTable.insert(bar).run(connection); | ||
let query = fooTable.get(foo.id); | ||
query = fooTable.withJoin(query, { bars: true }); | ||
const fetchedfooTable = await query.run(connection); | ||
expect(fetchedfooTable.bars).to.have.length(1); | ||
expect(bar).to.deep.equal(fetchedfooTable.bars[0]); | ||
}); | ||
it('should query belongsToMany relation', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
bars: belongsToMany([fooTable.linkedBy(foobarTable, 'fooId'), foobarTable.linkTo(barTable, 'barId')]), | ||
}), | ||
}); | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
foos: belongsToMany([barTable.linkedBy(foobarTable, 'barId'), foobarTable.linkTo(fooTable, 'fooId')]), | ||
}), | ||
}); | ||
const foobarTable = new Table({ | ||
table: 'foobar', | ||
schema: () => ({ | ||
...Table.schema, | ||
fooId: fooTable.getForeignKey({ isManyToMany: true }), | ||
barId: barTable.getForeignKey({ isManyToMany: true }), | ||
}), | ||
}); | ||
await fooTable.sync(connection); | ||
await barTable.sync(connection); | ||
await foobarTable.sync(connection); | ||
const foo = fooTable.create({}); | ||
const bar = barTable.create({}); | ||
const foobar = foobarTable.create({ fooId: foo.id, barId: bar.id }); | ||
await fooTable.insert(foo).run(connection); | ||
await barTable.insert(bar).run(connection); | ||
await foobarTable.insert(foobar).run(connection); | ||
let query = fooTable.get(foo.id); | ||
query = fooTable.withJoin(query, { bars: true }); | ||
const fetchedfooTable = await query.run(connection); | ||
expect(fetchedfooTable.bars).to.have.length(1); | ||
expect(bar).to.deep.equal(fetchedfooTable.bars[0]); | ||
query = barTable.get(bar.id); | ||
query = barTable.withJoin(query, { foos: true }); | ||
const fetchedbarTable = await query.run(connection); | ||
expect(fetchedbarTable.foos).to.have.length(1); | ||
expect(foo).to.deep.equal(fetchedbarTable.foos[0]); | ||
}); | ||
}); | ||
describe('isValid', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
name: Joi.string().required(), | ||
describe('createRelation', () => { | ||
it('should add hasMany relation', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
bars: hasMany(fooTable.linkedBy(barTable, 'fooId')), | ||
}), | ||
}); | ||
} | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
fooId: fooTable.getForeignKey(), | ||
}), | ||
}); | ||
await fooTable.sync(connection); | ||
await barTable.sync(connection); | ||
it('should return true when data is valid', () => { | ||
const foo = new Foo({ name: 'foo' }); | ||
expect(foo.isValid()).to.be.true; | ||
const foo = fooTable.create({}); | ||
const bar = barTable.create({}); | ||
await fooTable.insert(foo).run(connection); | ||
await barTable.insert(bar).run(connection); | ||
await fooTable.createRelation('bars', foo.id, bar.id).run(connection); | ||
let fooQuery = fooTable.get(foo.id); | ||
fooQuery = fooTable.withJoin(fooQuery, { bars: true }); | ||
const fetchedfooTable = await fooQuery.run(connection); | ||
expect(fetchedfooTable.bars).to.have.length(1); | ||
expect(fetchedfooTable.bars[0]).to.have.property('fooId', foo.id); | ||
}); | ||
it('should throw error when invalid', () => { | ||
const foo = new Foo({}); | ||
expect(foo.isValid()).to.be.false; | ||
it('should add belongsToMany relation', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
bars: belongsToMany([fooTable.linkedBy(foobarTable, 'fooId'), foobarTable.linkTo(barTable, 'barId')]), | ||
}), | ||
}); | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
foos: belongsToMany([barTable.linkedBy(foobarTable, 'barId'), foobarTable.linkTo(fooTable, 'fooId')]), | ||
}), | ||
}); | ||
const foobarTable = new Table({ | ||
table: 'foobar', | ||
schema: () => ({ | ||
...Table.schema, | ||
fooId: fooTable.getForeignKey({ isManyToMany: true }), | ||
barId: barTable.getForeignKey({ isManyToMany: true }), | ||
}), | ||
}); | ||
await fooTable.sync(connection); | ||
await barTable.sync(connection); | ||
await foobarTable.sync(connection); | ||
const foo = fooTable.create({}); | ||
const bar = barTable.create({}); | ||
await fooTable.insert(foo).run(connection); | ||
await barTable.insert(bar).run(connection); | ||
await fooTable.createRelation('bars', foo.id, bar.id).run(connection); | ||
const fooQuery = fooTable.get(foo.id); | ||
const fetchedfooTable = await fooTable.withJoin(fooQuery, { bars: true }).run(connection); | ||
expect(bar.id).to.equal(fetchedfooTable.bars[0].id); | ||
const barQuery = barTable.get(bar.id); | ||
const fetchedbarTable = await barTable.withJoin(barQuery, { foos: true }).run(connection); | ||
expect(foo.id).to.equal(fetchedbarTable.foos[0].id); | ||
}); | ||
}); | ||
describe('attempt', () => { | ||
class Foo extends Table { | ||
static table = 'foo'; | ||
static schema = () => ({ | ||
foo: Joi.string().default('foo'), | ||
bar: Joi.string().required(), | ||
describe('removeRelation', () => { | ||
it('should remove hasMany relation', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
bars: hasMany(fooTable.linkedBy(barTable, 'fooId')), | ||
}), | ||
}); | ||
} | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
fooId: fooTable.getForeignKey(), | ||
}), | ||
}); | ||
await fooTable.sync(connection); | ||
await barTable.sync(connection); | ||
it('should update default properties', () => { | ||
const foo = new Foo({ bar: 'bar' }); | ||
foo.attempt(); | ||
expect(foo.data).to.have.property('foo', 'foo'); | ||
expect(foo.data).to.have.property('bar', 'bar'); | ||
const foo = fooTable.create({}); | ||
const bar1 = barTable.create({}); | ||
const bar2 = barTable.create({}); | ||
await fooTable.insert(foo).run(connection); | ||
await barTable.insert(bar1).run(connection); | ||
await barTable.insert(bar2).run(connection); | ||
await fooTable.createRelation('bars', foo.id, bar1.id).run(connection); | ||
await fooTable.createRelation('bars', foo.id, bar2.id).run(connection); | ||
await fooTable.removeRelation('bars', foo.id, bar1.id).run(connection); | ||
let fooQuery = fooTable.get(foo.id); | ||
fooQuery = fooTable.withJoin(fooQuery, { bars: true }); | ||
const fetchedfooTable = await fooQuery.run(connection); | ||
expect(fetchedfooTable.bars).to.have.length(1); | ||
expect(bar2.id).to.equal(fetchedfooTable.bars[0].id); | ||
}); | ||
it('should throw error when invalid', () => { | ||
const foo = new Foo({}); | ||
expect(() => foo.attempt()).to.throw(Error); | ||
it('should remove belongsToMany relation', async () => { | ||
const fooTable = new Table({ | ||
table: 'foo', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
bars: belongsToMany([fooTable.linkedBy(foobarTable, 'fooId'), foobarTable.linkTo(barTable, 'barId')]), | ||
}), | ||
}); | ||
const barTable = new Table({ | ||
table: 'bar', | ||
schema: () => ({ | ||
...Table.schema, | ||
}), | ||
relations: () => ({ | ||
foos: belongsToMany([barTable.linkedBy(foobarTable, 'barId'), foobarTable.linkTo(fooTable, 'fooId')]), | ||
}), | ||
}); | ||
const foobarTable = new Table({ | ||
table: 'foobar', | ||
schema: () => ({ | ||
...Table.schema, | ||
fooId: fooTable.getForeignKey({ isManyToMany: true }), | ||
barId: barTable.getForeignKey({ isManyToMany: true }), | ||
}), | ||
}); | ||
await fooTable.sync(connection); | ||
await barTable.sync(connection); | ||
await foobarTable.sync(connection); | ||
const foo = fooTable.create({}); | ||
const bar1 = barTable.create({}); | ||
const bar2 = barTable.create({}); | ||
await fooTable.insert(foo).run(connection); | ||
await barTable.insert(bar1).run(connection); | ||
await barTable.insert(bar2).run(connection); | ||
await fooTable.createRelation('bars', foo.id, bar1.id).run(connection); | ||
await fooTable.createRelation('bars', foo.id, bar2.id).run(connection); | ||
await fooTable.removeRelation('bars', foo.id, bar1.id).run(connection); | ||
const fooQuery = fooTable.get(foo.id); | ||
const fetchedfooTable = await fooTable.withJoin(fooQuery, { bars: true }).run(connection); | ||
expect(fetchedfooTable.bars).to.have.length(1); | ||
expect(bar2.id).to.equal(fetchedfooTable.bars[0].id); | ||
const barQuery = barTable.get(bar2.id); | ||
const fetchedbarTable = await barTable.withJoin(barQuery, { foos: true }).run(connection); | ||
expect(fetchedbarTable.foos).to.have.length(1); | ||
expect(foo.id).to.equal(fetchedbarTable.foos[0].id); | ||
}); | ||
}); | ||
}); |
@@ -7,18 +7,28 @@ import assert from 'assert'; | ||
return linkData && | ||
linkData.Table && | ||
linkData.key && | ||
linkData.Table.hasColumn(linkData.key); | ||
linkData.table && | ||
linkData.field && | ||
linkData.table.hasField(linkData.field); | ||
} | ||
static assertLinkData(linkData) { | ||
assert.ok(this.validateLinkData(linkData)); | ||
assert.ok(this.validateLinkData(linkData), 'invalid link data'); | ||
} | ||
async sync(connection) { | ||
await this.syncTable(connection, this.right); | ||
await this.syncTable(connection, this.left); | ||
} | ||
async syncTable(connection, { table, field }) { | ||
await table.ensureTable(connection); | ||
await table.ensureIndex(connection, field); | ||
} | ||
constructor(options = {}) { | ||
const { linker, linkee } = options; | ||
Link.assertLinkData(linker); | ||
Link.assertLinkData(linkee); | ||
this.linker = linker; | ||
this.linkee = linkee; | ||
const { left, right } = options; | ||
Link.assertLinkData(left); | ||
Link.assertLinkData(right); | ||
this.left = left; | ||
this.right = right; | ||
} | ||
} |
@@ -1,11 +0,2 @@ | ||
import Model from './Model'; | ||
import Link from './Link'; | ||
import Table from './Table'; | ||
const nothinkdb = { | ||
Model, | ||
Link, | ||
Table, | ||
}; | ||
export default nothinkdb; | ||
export Table from './Table'; | ||
export * from './relations'; |
168
src/Table.js
@@ -0,1 +1,3 @@ | ||
/* eslint no-shadow: 0, no-param-reassign: 0 */ | ||
import r from 'rethinkdb'; | ||
import Joi from 'joi'; | ||
@@ -9,67 +11,167 @@ import _ from 'lodash'; | ||
export default class Table { | ||
static table = null; | ||
static pk = 'id'; | ||
static schema = () => ({ | ||
id: Joi.string().max(32).default(() => uuid.v4(), 'primary key').meta({ index: true }), | ||
static schema = { | ||
id: Joi.string().max(36).default(() => uuid.v4(), 'primary key').meta({ index: true }), | ||
createdAt: Joi.date().default(() => new Date(), 'time of creation'), | ||
updatedAt: Joi.date().default(() => new Date(), 'time of updated'), | ||
}); | ||
}; | ||
static validate(data = null) { | ||
constructor(options = {}) { | ||
const { table, pk, schema, relations } = Joi.attempt(options, { | ||
table: Joi.string().required(), | ||
pk: Joi.string().default(Table.pk), | ||
schema: Joi.func().required(), | ||
relations: Joi.func().default(() => () => ({}), 'relation'), | ||
}); | ||
// assert.equal(_.has(schema(), pk), true, `'${pk}' is not specified in schema`); | ||
this.table = table; | ||
this.pk = pk; | ||
this.schema = schema; | ||
this.relations = relations; | ||
} | ||
validate(data = null) { | ||
return !Joi.validate(data, this.schema()).error; | ||
} | ||
static attempt(data = null) { | ||
attempt(data = null) { | ||
return Joi.attempt(data, this.schema()); | ||
} | ||
static hasColumn(columnName) { | ||
return _.has(this.schema(), columnName); | ||
create(data = null) { | ||
return this.attempt(data); | ||
} | ||
static assertColumn(columnName) { | ||
return assert.ok(this.hasColumn(columnName), `Column '${columnName}' is unspecified in table '${this.table}'.`); | ||
hasField(fieldName) { | ||
return _.has(this.schema(), fieldName); | ||
} | ||
static getColumn(columnName) { | ||
this.assertColumn(columnName); | ||
return this.schema()[columnName]; | ||
assertField(fieldName) { | ||
return assert.ok(this.hasField(fieldName), `Field '${fieldName}' is unspecified in table '${this.table}'.`); | ||
} | ||
static getForeignKey(options = {}) { | ||
const { columnName = this.pk, isManyToMany = false } = options; | ||
const column = this.getColumn(columnName); | ||
getField(fieldName) { | ||
this.assertField(fieldName); | ||
return this.schema()[fieldName]; | ||
} | ||
getForeignKey(options = {}) { | ||
const { fieldName = this.pk, isManyToMany = false } = options; | ||
const field = this.getField(fieldName); | ||
if (isManyToMany) { | ||
return column.required(); | ||
return field.required(); | ||
} | ||
return column.default(null); | ||
return field.allow(null).default(null); | ||
} | ||
static linkTo(OtherTable, foreignKey, targetKey = OtherTable.pk) { | ||
linkTo(rightTable, leftField, options = {}) { | ||
const { index = rightTable.pk } = options; | ||
return new Link({ | ||
linker: { Table: this, key: foreignKey }, | ||
linkee: { Table: OtherTable, key: targetKey }, | ||
left: { table: this, field: leftField }, | ||
right: { table: rightTable, field: index }, | ||
}); | ||
} | ||
static linkedBy(OtherTable, foreignKey, targetKey = OtherTable.pk) { | ||
return OtherTable.linkTo(this, foreignKey, targetKey); | ||
linkedBy(leftTable, leftField, options) { | ||
return leftTable.linkTo(this, leftField, options); | ||
} | ||
constructor(data = {}) { | ||
assert.ok(this.constructor.table, 'Table should have static property \'table\'.'); | ||
assert.ok(this.constructor.pk, 'Table should have static property \'pk\'.'); | ||
assert.ok(_.isObject(data), 'data should be object type.'); | ||
this.data = data; | ||
async sync(connection) { | ||
await this.ensureTable(connection); | ||
await this.syncRelations(connection); | ||
} | ||
isValid() { | ||
return this.constructor.validate(this.data); | ||
async ensureTable(connection) { | ||
await r.branch( | ||
r.tableList().contains(this.table).not(), | ||
r.tableCreate(this.table), | ||
null | ||
).run(connection); | ||
} | ||
attempt() { | ||
this.data = this.constructor.attempt(this.data); | ||
async ensureIndex(connection, field) { | ||
if (this.pk === field) return; | ||
await r.branch( | ||
this.query().indexList().contains(field).not(), | ||
this.query().indexCreate(field), | ||
null | ||
).run(connection); | ||
await this.query().indexWait(field).run(connection); | ||
} | ||
async syncRelations(connection) { | ||
await _.reduce(this.relations(), (promise, relation) => { | ||
return promise.then(() => relation.sync(connection)); | ||
}, Promise.resolve()); | ||
} | ||
query() { | ||
assert.ok(this.table, 'Table should have property \'table\'.'); | ||
return r.table(this.table); | ||
} | ||
insert(data) { | ||
return this.query().insert(this.attempt(data)); | ||
} | ||
get(pk) { | ||
return this.query().get(pk); | ||
} | ||
update(pk, data) { | ||
return this.query().get(pk).update(data); | ||
} | ||
delete(pk) { | ||
return this.query().get(pk).delete(); | ||
} | ||
getRelation(relation) { | ||
const relationObj = this.relations()[relation]; | ||
assert.ok(relationObj, `Relation '${this.table}.${relation}' is not exist.`); | ||
return relationObj; | ||
} | ||
_withJoinOne(query, key, options) { | ||
const relation = this.getRelation(key); | ||
return relation.join(key, query, options); | ||
} | ||
withJoin(query, relations) { | ||
return _.reduce(relations, (query, value, key) => { | ||
let options = {}; | ||
if (_.isObject(value)) { | ||
options = _.chain(value) | ||
.omitBy((value, key) => !_.startsWith(key, '_')) | ||
.reduce((memo, value, key) => { | ||
return { [key.slice(1)]: value }; | ||
}, {}) | ||
.value(); | ||
} | ||
query = this._withJoinOne(query, key, options); | ||
if (_.isObject(value)) { | ||
const relations = _.omitBy(value, (value, key) => _.startsWith(key, '_')); | ||
const { targetTable } = this.getRelation(key); | ||
query = query.merge(function(row) { | ||
return { [key]: targetTable.withJoin(row(key), relations) }; | ||
}); | ||
} | ||
return query; | ||
}, query); | ||
} | ||
createRelation(as, onePk, otherPk) { | ||
const relation = this.getRelation(as); | ||
assert.ok(relation.create, 'unsupported relation.'); | ||
return relation.create(onePk, otherPk); | ||
} | ||
removeRelation(as, onePk, otherPk) { | ||
const relation = this.getRelation(as); | ||
assert.ok(relation.remove, 'unsupported relation.'); | ||
return relation.remove(onePk, otherPk); | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
36721
1028
15