Skip to content

Commit a3ddda1

Browse files
author
Lee Richmond
committed
Camelize attributes
1 parent b7eb821 commit a3ddda1

File tree

9 files changed

+113
-34
lines changed

9 files changed

+113
-34
lines changed

gulpfile.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ gulp.task('test', ['clean:test'], () =>
1818
.pipe(mocha())
1919
);
2020

21+
gulp.task('test-browser', function () {
22+
gulp
23+
.src(['./index.d.ts.', './src/main.ts', './src/**/*.ts', './test/fixtures.ts', './test/test-helper.ts', './test/**/*-test.ts'], { base: '.' })
24+
.pipe(tsProject())
25+
.pipe(webpack(require('./webpack.config.js') ))
26+
.pipe(gulp.dest('tmp/browser'))
27+
});
28+
2129
gulp.task('build', function () {
2230
gulp
2331
.src(['./index.d.ts', './src/main.ts'])

index.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<html>
2+
<head>
3+
<meta charset="utf-8">
4+
<title>Test</title>
5+
</head>
6+
<body>
7+
<script src="dist/jsorm.js"></script>
8+
<script>
9+
var Model = jsorm.Model;
10+
var attr = jsorm.attr;
11+
var Config = jsorm.Config;
12+
13+
const Staffer = Model.extend({
14+
static: {
15+
jsonapiType: 'staffers',
16+
apiNamespace: '/v1/',
17+
baseUrl: 'http://localhost:3000/api'
18+
},
19+
20+
first_name: attr()
21+
});
22+
23+
Staffer.find(1297466).then((s) => {
24+
console.log(s);
25+
console.log(s.marital_status);
26+
});
27+
28+
Config.bootstrap();
29+
</script>
30+
</body>
31+
</html>

src/model.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ import Config from './configuration';
55
import Attribute from './attribute';
66
import deserialize from './util/deserialize';
77
import _extend from './util/extend';
8+
import { camelize } from './util/string';
89

910
export default class Model {
1011
static baseUrl = process.env.BROWSER? '': 'http://localhost:9999'
11-
static endpoint = 'define-in-subclass';
1212
static apiNamespace = '/';
1313
static jsonapiType = 'define-in-subclass';
14+
static endpoint: string;
1415

1516
id: string;
16-
attributes: Object = {};
17+
_attributes: Object = {};
1718
relationships: Object = {};
1819
__meta__: Object | void = null;
1920
parentClass: typeof Model;
@@ -37,7 +38,7 @@ export default class Model {
3738
}
3839

3940
constructor(attributes?: Object) {
40-
this._assignAttributes(attributes);
41+
this.attributes = attributes;
4142
}
4243

4344
static all() : Promise<Array<Model>> {
@@ -77,7 +78,8 @@ export default class Model {
7778
}
7879

7980
static url(id?: string | number) : string {
80-
let base = `${this.baseUrl}${this.apiNamespace}${this.endpoint}`;
81+
let endpoint = this.endpoint || this.jsonapiType;
82+
let base = `${this.baseUrl}${this.apiNamespace}${endpoint}`;
8183

8284
if (id) {
8385
base = `${base}/${id}`;
@@ -90,10 +92,15 @@ export default class Model {
9092
return deserialize(resource, payload);
9193
}
9294

93-
private _assignAttributes(attrs: Object) : void {
95+
get attributes() : Object {
96+
return this._attributes;
97+
}
98+
99+
set attributes(attrs : Object) {
94100
for(var key in attrs) {
95-
if (key == 'id' || this.klass.attributeList.indexOf(key) >= 0) {
96-
this[key] = attrs[key];
101+
let attributeName = camelize(key);
102+
if (key == 'id' || this.klass.attributeList.indexOf(attributeName) >= 0) {
103+
this[attributeName] = attrs[key];
97104
}
98105
}
99106
}

src/util/deserialize.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ class Deserializer {
4646
let instance = new klass({ id: resource.id });
4747
this._models.push(instance);
4848

49-
for (let key in resource.attributes) {
50-
instance[key] = resource.attributes[key];
51-
}
49+
instance.attributes = resource.attributes;
5250
this._processRelationships(instance, resource.relationships);
5351
instance.__meta__ = resource.meta;
5452
return instance;
@@ -57,6 +55,7 @@ class Deserializer {
5755
_processRelationships(instance, relationships) {
5856
for (let key in relationships) {
5957
let relationData = relationships[key].data;
58+
if(!relationData) continue; // only links, empty, etc
6059

6160
if (Array.isArray(relationData)) {
6261
for (let datum of relationData) {

src/util/extend.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/// <reference path="../../index.d.ts" />
22

33
export default function(superclass, classObj) {
4-
global.__extends(Subclass, superclass);
5-
function Subclass() {
4+
global.__extends(Model, superclass);
5+
function Model() {
66
var _this = superclass.apply(this, arguments) || this;
77

88
for (let prop in classObj) {
@@ -15,9 +15,9 @@ export default function(superclass, classObj) {
1515
}
1616

1717
for (let classProp in classObj.static) {
18-
Subclass[classProp] = classObj.static[classProp];
18+
Model[classProp] = classObj.static[classProp];
1919
}
2020

21-
superclass.inherited(Subclass);
22-
return Subclass;
21+
superclass.inherited(Model);
22+
return Model;
2323
}

src/util/string.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const underscore = function(str) {
2+
return str.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
3+
}
4+
5+
const camelize = function(str) {
6+
return str.replace(/(\_[a-z])/g, function($1){return $1.toUpperCase().replace('_','');});
7+
}
8+
9+
export { underscore, camelize };

test/fixtures.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class Person extends Model {
88

99
static jsonapiType = 'people';
1010

11-
name: string = attr();
11+
firstName: string = attr();
12+
lastName: string = attr();
1213
}
1314

1415
// plain js class
@@ -18,6 +19,7 @@ let Author = Person.extend({
1819
},
1920

2021
books: hasMany(),
22+
tags: hasMany(),
2123
genre: belongsTo('genres'),
2224
bio: hasOne('bios')
2325
});
@@ -42,6 +44,12 @@ class Bio extends Model {
4244
description: string = attr()
4345
}
4446

47+
class Tag extends Model {
48+
static jsonapiType = 'tags';
49+
50+
name: string = attr()
51+
}
52+
4553
Config.bootstrap();
4654

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

test/unit/attributes-test.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,29 @@ import { Person } from '../fixtures';
66
describe('Model attributes', function() {
77
it('supports direct assignment', function() {
88
let person = new Person();
9-
expect(person.name).to.eq(undefined);
10-
person.name = 'John';
11-
expect(person.name).to.eq('John');
9+
expect(person.firstName).to.eq(undefined);
10+
person.firstName = 'John';
11+
expect(person.firstName).to.eq('John');
1212
});
1313

1414
it('supports constructor assignment', function() {
15-
let person = new Person({ name: 'Joe' });
16-
expect(person.name).to.eq('Joe');
17-
expect(person.attributes['name']).to.eq('Joe');
15+
let person = new Person({ firstName: 'Joe' });
16+
expect(person.firstName).to.eq('Joe');
17+
expect(person.attributes['firstName']).to.eq('Joe');
18+
});
19+
20+
it('camelizes underscored strings', function() {
21+
let person = new Person({ first_name: 'Joe' });
22+
expect(person.firstName).to.eq('Joe');
1823
});
1924

2025
it('syncs with #attributes', function() {
2126
let person = new Person();
2227
expect(person.attributes).to.eql({});
23-
person.name = 'John';
24-
expect(person.attributes).to.eql({ name: 'John' });
25-
person.attributes['name'] = 'Jane';
26-
expect(person.name).to.eq('Jane');
28+
person.firstName = 'John';
29+
expect(person.attributes).to.eql({ firstName: 'John' });
30+
person.attributes['firstName'] = 'Jane';
31+
expect(person.firstName).to.eq('Jane');
2732
});
2833

2934
// Without this behavior, the API could add a backwards-compatible field,

test/unit/model-test.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ describe('Model', function() {
1313
id: '1',
1414
type: 'authors',
1515
attributes: {
16-
name: 'Donald Budge'
16+
firstName: 'Donald Budge',
17+
unknown: 'adsf'
1718
},
1819
relationships: {
20+
tags: {},
1921
genre: {
2022
data: {
2123
id: '1',
@@ -66,7 +68,7 @@ describe('Model', function() {
6668
type: 'authors',
6769
id: '2',
6870
attributes: {
69-
name: 'Maurice Sendak'
71+
firstName: 'Maurice Sendak'
7072
}
7173
},
7274
{
@@ -91,12 +93,17 @@ describe('Model', function() {
9193

9294
it('assigns attributes correctly', function() {
9395
let instance = Model.fromJsonapi(doc.data, doc);
94-
expect(instance.name).to.eq('Donald Budge');
96+
expect(instance.firstName).to.eq('Donald Budge');
9597
expect(instance.attributes).to.eql({
96-
name: 'Donald Budge'
98+
firstName: 'Donald Budge'
9799
})
98100
});
99101

102+
it('does not assign unknown attributes', function() {
103+
let instance = Model.fromJsonapi(doc.data, doc);
104+
expect(instance).to.not.have.property('unknown');
105+
});
106+
100107
it('assigns metadata correctly', function() {
101108
let instance = Model.fromJsonapi(doc.data, doc);
102109
expect(instance.__meta__).to.eql({
@@ -123,7 +130,7 @@ describe('Model', function() {
123130
let instance = Model.fromJsonapi(doc.data, doc);
124131
let bio = instance.bio;
125132
expect(bio).to.be.instanceof(Bio);
126-
//expect(bio.description).to.eq("Some Dude.");
133+
expect(bio.description).to.eq("Some Dude.");
127134
});
128135

129136
it('assigns nested relationships correctly', function() {
@@ -132,8 +139,13 @@ describe('Model', function() {
132139
expect(authors.length).to.eq(2);
133140
expect(authors[0]).to.be.instanceof(Author);
134141
expect(authors[1]).to.be.instanceof(Author);
135-
expect(authors[0].name).to.eq('Donald Budge');
136-
expect(authors[1].name).to.eq('Maurice Sendak');
142+
expect(authors[0].firstName).to.eq('Donald Budge');
143+
expect(authors[1].firstName).to.eq('Maurice Sendak');
144+
});
145+
146+
it('skips relationships without data', function() {
147+
let instance = Model.fromJsonapi(doc.data, doc);
148+
expect(instance.tags).to.eql([]);
137149
});
138150
});
139151
});

0 commit comments

Comments
 (0)