Skip to content

Commit

Permalink
feat: return a promise from relationships (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
zacharygolba authored Jun 30, 2016
1 parent 38d7a9b commit 5aeb903
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 138 deletions.
11 changes: 10 additions & 1 deletion src/packages/database/model/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import omit from '../../../utils/omit';
import entries from '../../../utils/entries';
import underscore from '../../../utils/underscore';

import type { options as relationshipOptions } from '../related/interfaces';

class Model {
/**
* @private
Expand Down Expand Up @@ -535,6 +537,13 @@ class Model {
return Boolean(this.scopes[name]);
}

/**
* Check if a value is an instance of a Model.
*/
static isInstance(obj: mixed): boolean {
return obj instanceof this;
}

static columnFor(key): Object {
const {
attributes: {
Expand All @@ -553,7 +562,7 @@ class Model {
}
}

static relationshipFor(key): Object {
static relationshipFor(key): relationshipOptions {
const {
relationships: {
[key]: relationship
Expand Down
138 changes: 61 additions & 77 deletions src/packages/database/model/initialize-class.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { camelize, dasherize, singularize } from 'inflection';

import { line } from '../../logger';

import {
get as getRelationship,
set as setRelationship
} from '../relationship';

import entries from '../../../utils/entries';
import underscore from '../../../utils/underscore';

Expand Down Expand Up @@ -35,99 +40,78 @@ function refsFor(instance) {
}

function initializeProps(prototype, attributes, relationships) {
const props = Object.create(null);

entries(attributes)
.reduce((hash, [key, { type, nullable, defaultValue }]) => {
if (/^(boolean|tinyint)$/.test(type)) {
defaultValue = Boolean(
typeof defaultValue === 'string' ?
parseInt(defaultValue, 10) : defaultValue
);
} else if (type === 'datetime' && typeof defaultValue === 'number') {
defaultValue = new Date(defaultValue);
}

hash[key] = {
get() {
const refs = refsFor(this);

return refs[key] || defaultValue;
},

set(nextValue) {
const refs = refsFor(this);
const currentValue = refs[key] || defaultValue;

if (nextValue !== currentValue) {
const { initialized, initialValues } = this;

if (/^(boolean|tinyint)$/.test(type)) {
nextValue = Boolean(
typeof nextValue === 'string' ?
parseInt(nextValue, 10) : nextValue
);
} else if (type === 'datetime' && typeof nextValue === 'number') {
nextValue = new Date(nextValue);
} else if (!nextValue && !nullable) {
return;
}

refs[key] = nextValue;

if (initialized) {
const { dirtyAttributes } = this;
const initialValue = initialValues.get(key) || defaultValue;

if (nextValue !== initialValue) {
dirtyAttributes.add(key);
} else {
dirtyAttributes.delete(key);
}
} else {
initialValues.set(key, nextValue);
}
}
Object.defineProperties(prototype, {
...entries(attributes)
.reduce((hash, [key, { type, nullable, defaultValue }]) => {
if (/^(boolean|tinyint)$/.test(type)) {
defaultValue = Boolean(
typeof defaultValue === 'string' ?
parseInt(defaultValue, 10) : defaultValue
);
} else if (type === 'datetime' && typeof defaultValue === 'number') {
defaultValue = new Date(defaultValue);
}
};

return hash;
}, props);

entries(relationships)
.reduce((hash, [key, { type, model }]) => {
if (type === 'hasMany') {
hash[key] = {
get() {
const refs = refsFor(this);

return refs[key] || [];
return refs[key] || defaultValue;
},

set(value) {
set(nextValue) {
const refs = refsFor(this);
const currentValue = refs[key] || defaultValue;

if (nextValue !== currentValue) {
const { initialized, initialValues } = this;

if (/^(boolean|tinyint)$/.test(type)) {
nextValue = Boolean(
typeof nextValue === 'string' ?
parseInt(nextValue, 10) : nextValue
);
} else if (type === 'datetime' && typeof nextValue === 'number') {
nextValue = new Date(nextValue);
} else if (!nextValue && !nullable) {
return;
}

if (Array.isArray(value)) {
refs[key] = value;
refs[key] = nextValue;

if (initialized) {
const { dirtyAttributes } = this;
const initialValue = initialValues.get(key) || defaultValue;

if (nextValue !== initialValue) {
dirtyAttributes.add(key);
} else {
dirtyAttributes.delete(key);
}
} else {
initialValues.set(key, nextValue);
}
}
}
};
} else {
hash[key] = {
get() {
return refsFor(this)[key] || null;
},

set(record) {
refsFor(this)[key] = record ? new model(record) : undefined;
}
};
}
return hash;
}, {}),

return hash;
}, props);
...Object.keys(relationships).reduce((hash, key) => ({
...hash,

[key]: {
get() {
return getRelationship(this, key);
},

Object.defineProperties(prototype, props);
set(val) {
setRelationship(this, key, val);
}
}
}), {})
});
}

function initializeHooks({
Expand Down
82 changes: 82 additions & 0 deletions src/packages/database/relationship/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// @flow
import { camelize } from 'inflection';

import relatedFor from './utils/related-for';

import type Model from '../model';

export async function get(
owner: Model,
key: string
): Array<Model> | ?Model {
const options = owner.constructor.relationshipFor(key);
let relationship;

const {
type,
model
} = options;

if (model) {
const related = relatedFor(owner);
let { foreignKey } = options;

foreignKey = camelize(foreignKey, true);
relationship = related.get(key);

if (!relationship) {
switch (type) {
case 'hasOne':
case 'hasMany':
relationship = model.where({
[foreignKey]: owner[owner.constructor.primaryKey]
});

if (type === 'hasOne') {
relationship = relationship.first();
}

relationship = await relationship;
break;

case 'belongsTo':
const foreignVal = owner[foreignKey];

if (foreignVal) {
relationship = await model.find(foreignVal);
}
break;
}

set(owner, key, relationship);
}
}

return relationship;
}

export function set(
owner: Model,
key: string,
val: Array<Model> | ?Model
): void {
const { type, model } = owner.constructor.relationshipFor(key);
const related = relatedFor(owner);

switch (type) {
case 'hasMany':
if (Array.isArray(val)) {
related.set(key, val);
}
break;

case 'hasOne':
case 'belongsTo':
if (val && typeof val === 'object' && !model.isInstance(val)) {
val = new model(val);
}

related.set(key, val);
break;
}
}
9 changes: 9 additions & 0 deletions src/packages/database/relationship/interfaces.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// @flow
import type Model from '../model';

export type options = {
type: 'hasOne' | 'hasMany' | 'belongsTo';
model: Model;
inverse: string;
foreignKey: string;
};
15 changes: 15 additions & 0 deletions src/packages/database/relationship/utils/related-for.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @flow
import type Model from '../../model';

const REFS: WeakMap<Model, Map<string, Model>> = new WeakMap();

export default function relatedFor(owner: Model): Map<string, Model> {
let related = REFS.get(owner);

if (!related) {
related = new Map();
REFS.set(owner, related);
}

return related;
}
Loading

0 comments on commit 5aeb903

Please sign in to comment.