-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an example of a foreign key relationship.
This adds a `DB` example that contains a number of tables each of which have rows that link to each other. This uses the ability to link anywhere in the store to create a factory method that creates entities elswhere in the store. In this example, the `Blog` record has a reference to a `Person` which is the author. The factory for author attribute of a blog, creates that person in the database, and then returns that person.
- Loading branch information
Showing
7 changed files
with
249 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { append, map, foldl } from 'funcadelic'; | ||
import { NumberType, StringType } from './types'; | ||
import { create as _create } from '../index'; | ||
import { view, At } from '../src/lens'; | ||
import { link, valueOf, pathOf, atomOf, typeOf, metaOf, ownerOf } from '../src/meta'; | ||
|
||
import faker from 'faker'; | ||
|
||
class Person { | ||
firstName = StringType; | ||
lastName = StringType; | ||
} | ||
|
||
class Blog { | ||
title = StringType; | ||
author = belongsTo(Person, "people"); | ||
} | ||
|
||
class Comment {} | ||
|
||
function Table(Type, factory = {}) { | ||
|
||
return class Table { | ||
static Type = Type; | ||
static name = `Table<${Type.name}>`; | ||
|
||
nextId = NumberType; | ||
|
||
get length() { return Object.keys(this.records).length; } | ||
|
||
get latest() { return this.records[this.latestId]; } | ||
|
||
get latestId() { return this.nextId.state - 1; } | ||
|
||
get records() { | ||
return map((value, key) => ln(Type, pathOf(this).concat(["records", key]), this), this.state.records); | ||
} | ||
|
||
get state() { | ||
return valueOf(this) || { nextId: 0, records: {}}; | ||
} | ||
|
||
create(overrides = {}) { | ||
let id = this.nextId.state; | ||
let record = createAt(this.nextId.increment(), Type, ["records", id], { id }); | ||
|
||
let created = foldl((record, { key, value: attr }) => { | ||
if (record[key]) { | ||
let attrFn = typeof attr === 'function' ? attr : () => attr; | ||
|
||
// create a local link of the DB that returns itself. to pass into | ||
// the factory function. | ||
let db = link(_create(DB), DB, pathOf(this).slice(0, -1), atomOf(record)); | ||
|
||
let result = attrFn(overrides[key], db); | ||
|
||
let next = metaOf(result) ? link(record, Type, pathOf(record), atomOf(result)) : record; | ||
|
||
return next[key].set(result); | ||
} else { | ||
return record; | ||
} | ||
}, record, append(factory, overrides)); | ||
|
||
let owner = ownerOf(this); | ||
return link(this, typeOf(this), pathOf(this), atomOf(created), owner.Type, owner.path); | ||
} | ||
}; | ||
} | ||
|
||
|
||
|
||
class DB { | ||
people = Table(Person, { | ||
firstName: () => faker.name.firstName(), | ||
lastName: () => faker.name.lastName() | ||
}); | ||
blogs = Table(Blog, { | ||
title: () => faker.random.words(), | ||
author: (attrs, db) => { | ||
return db.people.create(attrs) | ||
.people.latest; | ||
} | ||
}); | ||
comments = Table(Comment); | ||
} | ||
|
||
function createAt(parent, Type, path, value) { | ||
return link(_create(Type), Type, pathOf(parent).concat(path), atomOf(parent)).set(value); | ||
} | ||
|
||
function ln(Type, path, owner) { | ||
return link(_create(Type), Type, path, atomOf(owner), typeOf(owner), pathOf(owner)); | ||
} | ||
|
||
import Relationship from '../src/relationship'; | ||
|
||
function linkTo(Type, path) { | ||
return new Relationship(resolve); | ||
|
||
function resolve(origin, originType, originPath /*, relationshipName */) { | ||
|
||
return { | ||
Type, | ||
path: path.reduce((path, element) => { | ||
if (element === '..') { | ||
return path.slice(0, -1); | ||
} else if (element === '.') { | ||
return path; | ||
} else { | ||
return path.concat(element); | ||
} | ||
}, originPath) | ||
}; | ||
} | ||
} | ||
|
||
function belongsTo(T, tableName) { | ||
return new Relationship(resolve); | ||
|
||
function BelongsTo(originType, originPath, foreignKey) { | ||
return class BelongsTo extends T { | ||
set(value) { | ||
let origin = ln(originType, originPath, value); | ||
let id = valueOf(value).id; | ||
return origin.set(append(valueOf(origin), { | ||
[foreignKey]: id | ||
})); | ||
} | ||
}; | ||
} | ||
|
||
function resolve(origin, originType, originPath, relationshipName) { | ||
let foreignKey = `${relationshipName}Id`; | ||
let id = view(At(foreignKey), valueOf(origin)); | ||
let Type = BelongsTo(originType, originPath, foreignKey); | ||
let { resolve } = linkTo(Type, ["..", "..", "..", tableName, "records", id]); | ||
return resolve(origin, originType, originPath, relationshipName); | ||
} | ||
} | ||
|
||
export default _create(DB, {}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { valueOf } from '../index'; | ||
|
||
export class NumberType { | ||
|
||
get state() { | ||
return valueOf(this) || 0; | ||
} | ||
|
||
initialize(value) { | ||
if (value == null) { | ||
return 0; | ||
} else if (isNaN(value)) { | ||
return this; | ||
} else { | ||
return Number(value); | ||
} | ||
} | ||
|
||
increment() { | ||
return this.state + 1; | ||
} | ||
} | ||
|
||
export class StringType { | ||
get state() { | ||
return valueOf(this) || ''; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import expect from 'expect'; | ||
|
||
import db from '../examples/db'; | ||
import { valueOf } from '../index'; | ||
|
||
describe.only('a referential DB', ()=> { | ||
it('starts out empty', ()=> { | ||
expect(db.people.length).toEqual(0); | ||
expect(db.blogs.length).toEqual(0); | ||
expect(db.comments.length).toEqual(0); | ||
}); | ||
|
||
describe('creating a person with static attributes', ()=> { | ||
let next; | ||
let person; | ||
beforeEach(()=> { | ||
next = db.people.create({ | ||
firstName: 'Bob', | ||
lastName: 'Dobalina' | ||
}); | ||
person = next.people.latest; | ||
}); | ||
it('contains the newly created person', ()=> { | ||
expect(next.people.length).toEqual(1); | ||
expect(person).toBeDefined(); | ||
expect(person.firstName.state).toEqual('Bob'); | ||
expect(person.lastName.state).toEqual('Dobalina'); | ||
}); | ||
}); | ||
|
||
describe('creating a person with higher order attributes', ()=> { | ||
let next; | ||
let person; | ||
beforeEach(()=> { | ||
next = db.people.create(); | ||
person = next.people.latest; | ||
}); | ||
|
||
it('creates them with generated attributes', ()=> { | ||
expect(person.firstName.state).not.toBe(''); | ||
expect(person.lastName.state).not.toBe(''); | ||
}); | ||
}); | ||
|
||
describe('creating a blog post with related author', ()=> { | ||
let next; | ||
let blog; | ||
|
||
beforeEach(()=> { | ||
next = db.blogs.create(); | ||
blog = next.blogs.latest; | ||
}); | ||
it('has a related author', ()=> { | ||
expect(blog.author).toBeDefined(); | ||
}); | ||
it('is the same as a person created in the people table', ()=> { | ||
expect(next.people.latest).toBeDefined(); | ||
expect(valueOf(next.people.latest)).toBe(valueOf(next.blogs.latest.author)); | ||
}); | ||
}); | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1503,6 +1503,11 @@ extsprintf@^1.2.0: | |
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" | ||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= | ||
|
||
faker@^4.1.0: | ||
version "4.1.0" | ||
resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" | ||
integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8= | ||
|
||
fast-deep-equal@^1.0.0: | ||
version "1.1.0" | ||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" | ||
|
@@ -1889,7 +1894,7 @@ inquirer@^6.1.0: | |
strip-ansi "^4.0.0" | ||
through "^2.3.6" | ||
|
||
[email protected], invariant@^2.2.2: | ||
[email protected], invariant@^2.2.2, invariant@^2.2.4: | ||
version "2.2.4" | ||
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" | ||
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== | ||
|