Skip to content

Commit b7eb821

Browse files
author
Lee Richmond
committed
Flesh out low-level relationship API
1 parent bc2a927 commit b7eb821

File tree

7 files changed

+109
-15
lines changed

7 files changed

+109
-15
lines changed

src/associations.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,26 @@ import Attribute from './attribute';
22
import Model from './model';
33

44
class Base extends Attribute {
5+
klass: typeof Model;
6+
isRelationship = true;
7+
jsonapiType: string;
8+
9+
constructor(...args) {
10+
super();
11+
this.jsonapiType = args[0];
12+
}
13+
514
getter(context: Model) {
615
return context.relationships[this.name];
716
}
817

918
setter(context: Model, val: any) : void {
10-
context.relationships[this.name] = val;
19+
if (!val.hasOwnProperty('isRelationship')) {
20+
if (!(val instanceof Model) && !(Array.isArray(val))) {
21+
val = new this.klass(val);
22+
}
23+
context.relationships[this.name] = val;
24+
}
1125
}
1226
}
1327

@@ -29,16 +43,16 @@ class HasOne extends Base {
2943
class BelongsTo extends Base {
3044
}
3145

32-
const hasMany = function() : HasMany {
33-
return new HasMany();
46+
const hasMany = function(...args) : HasMany {
47+
return new HasMany(...args);
3448
}
3549

36-
const hasOne = function() : HasOne {
37-
return new HasOne();
50+
const hasOne = function(...args) : HasOne {
51+
return new HasOne(...args);
3852
}
3953

40-
const belongsTo = function() : BelongsTo {
41-
return new BelongsTo();
54+
const belongsTo = function(...args) : BelongsTo {
55+
return new BelongsTo(...args);
4256
}
4357

4458
export { hasMany, hasOne, belongsTo };

src/attribute.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// transforms, default values, etc.
33

44
import Model from './model';
5+
import Config from './configuration';
56

67
export default class Attribute {
78
name: string;
@@ -23,6 +24,11 @@ export default class Attribute {
2324

2425
let attrInstance = instance[propName];
2526
attrInstance.name = propName;
27+
28+
if (attrInstance.isRelationship) {
29+
attrInstance.klass = Config.modelForType(attrInstance.jsonapiType || attrInstance.name);
30+
}
31+
2632
callback(attrInstance);
2733
}
2834
}

src/configuration.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export default class Config {
1010
static bootstrap() : void {
1111
for (let model of this.models) {
1212
this.typeMapping[model.jsonapiType] = model;
13+
}
14+
15+
for (let model of this.models) {
1316
Attribute.applyAll(model);
1417
}
1518
}

test/fixtures.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Model, Config, attr, hasMany, belongsTo } from '../src/main';
1+
import { Model, Config, attr, hasMany, belongsTo, hasOne } from '../src/main';
22

33
// typescript class
44
class Person extends Model {
@@ -18,7 +18,8 @@ let Author = Person.extend({
1818
},
1919

2020
books: hasMany(),
21-
genre: belongsTo()
21+
genre: belongsTo('genres'),
22+
bio: hasOne('bios')
2223
});
2324

2425
class Book extends Model {
@@ -30,11 +31,17 @@ class Book extends Model {
3031
class Genre extends Model {
3132
static jsonapiType = 'genres';
3233

33-
authors: any = hasMany()
34+
authors: any = hasMany('authors')
3435

3536
name: string = attr();
3637
}
3738

39+
class Bio extends Model {
40+
static jsonapiType = 'bios';
41+
42+
description: string = attr()
43+
}
44+
3845
Config.bootstrap();
3946

40-
export { Author, Person, Book, Genre };
47+
export { Author, Person, Book, Genre, Bio };

test/integration/finders-test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import '../../test/test-helper';
2-
//import { Model, Config } from '../../src/main';
3-
42
import { Person, Author } from '../fixtures';
53

64
let fetchMock = require('fetch-mock');
@@ -15,7 +13,10 @@ describe('Model finders', function() {
1513
fetchMock.get('http://example.com/api/v1/people/1', {
1614
data: {
1715
id: '1',
18-
type: 'people'
16+
type: 'people',
17+
attributes: {
18+
name: 'John'
19+
}
1920
}
2021
});
2122
});
@@ -26,6 +27,11 @@ describe('Model finders', function() {
2627
.have.property('id', '1');
2728
});
2829

30+
it('assigns attributes correctly', function() {
31+
return expect(Person.find(1)).to.eventually
32+
.have.property('name', 'John')
33+
});
34+
2935
describe('when API response returns a different type than the caller', function() {
3036
before(function() {
3137
fetchMock.restore();

test/unit/model-test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { sinon } from '../../test/test-helper';
44
import { Model } from '../../src/main';
5-
import { Person, Author, Book, Genre } from '../fixtures';
5+
import { Person, Author, Book, Genre, Bio } from '../fixtures';
66

77
let instance;
88

@@ -27,6 +27,12 @@ describe('Model', function() {
2727
id: '1',
2828
type: 'books'
2929
}]
30+
},
31+
bio: {
32+
data: {
33+
id: '1',
34+
type: 'bios'
35+
}
3036
}
3137
},
3238
meta: {
@@ -62,6 +68,13 @@ describe('Model', function() {
6268
attributes: {
6369
name: 'Maurice Sendak'
6470
}
71+
},
72+
{
73+
type: 'bios',
74+
id: '1',
75+
attributes: {
76+
description: 'Some Dude.'
77+
}
6578
}
6679
]
6780
};
@@ -106,6 +119,13 @@ describe('Model', function() {
106119
expect(genre.name).to.eq("Children's");
107120
});
108121

122+
it('assigns hasOne relationships correctly', function() {
123+
let instance = Model.fromJsonapi(doc.data, doc);
124+
let bio = instance.bio;
125+
expect(bio).to.be.instanceof(Bio);
126+
//expect(bio.description).to.eq("Some Dude.");
127+
});
128+
109129
it('assigns nested relationships correctly', function() {
110130
let instance = Model.fromJsonapi(doc.data, doc);
111131
let authors = instance.genre.authors;

test/unit/relationships-test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/// <reference path="../../index.d.ts" />
2+
3+
import { sinon } from '../../test/test-helper';
4+
import { Author, Genre } from '../fixtures';
5+
6+
describe('Model relationships', function() {
7+
it('supports direct assignment of models', function() {
8+
let author = new Author();
9+
author.genre = new Genre({ name: 'Horror' });
10+
expect(author.genre).to.be.instanceof(Genre);
11+
expect(author.genre.name).to.eq('Horror');
12+
});
13+
14+
it('supports direct assignment of objects', function() {
15+
let author = new Author();
16+
author.genre = { name: 'Horror' };
17+
expect(author.genre).to.be.instanceof(Genre);
18+
expect(author.genre.name).to.eq('Horror');
19+
});
20+
21+
it('supports constructor assignment of models', function() {
22+
let genre = new Genre({ name: 'Horror' });
23+
let author = new Author({ genre: genre });
24+
expect(author.genre).to.be.instanceof(Genre);
25+
expect(author.genre.name).to.eq('Horror');
26+
});
27+
28+
it('supports constructor assignment of objects', function() {
29+
let author = new Author({ genre: { name: 'Horror' }});
30+
expect(author.genre).to.be.instanceof(Genre);
31+
expect(author.genre.name).to.eq('Horror');
32+
});
33+
34+
it('defaults hasMany to empty array', function() {
35+
let genre = new Genre();
36+
expect(genre.authors).to.eql([]);
37+
});
38+
});

0 commit comments

Comments
 (0)