Skip to content

Commit 1a9d47d

Browse files
committed
feat(configuration): Implement fk removal config
Add support for configuring whether the component should remove foreign keys from models or not. Fix #124
1 parent db746e3 commit 1a9d47d

File tree

5 files changed

+153
-16
lines changed

5 files changed

+153
-16
lines changed

lib/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ var debug = require('debug')('loopback-component-jsonapi')
1616
module.exports = function (app, options) {
1717
var defaultOptions = {
1818
restApiRoot: '/api',
19-
enable: true
19+
enable: true,
20+
foreignKeys: false
2021
}
2122
options = options || {}
2223
options = _.defaults(options, defaultOptions)

lib/serializer.js

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,34 @@ function parseResource (type, data, relations, options) {
103103
resource.relationships = relationships
104104
}
105105

106-
// Remove any foreign keys from this resource
107-
options.app.models().forEach(function (model) {
108-
_.each(model.relations, function (relation) {
109-
var fkModel = relation.modelTo
110-
var fkName = relation.keyTo
111-
if (utils.relationFkOnModelFrom(relation)) {
112-
fkModel = relation.modelFrom
113-
fkName = relation.keyFrom
114-
}
115-
if (fkModel === options.model && fkName !== options.primaryKeyField) {
116-
delete data[fkName]
117-
}
106+
if (options.foreignKeys !== true) {
107+
// Remove any foreign keys from this resource
108+
options.app.models().forEach(function (model) {
109+
_.each(model.relations, function (relation) {
110+
var fkModel = relation.modelTo
111+
var fkName = relation.keyTo
112+
if (utils.relationFkOnModelFrom(relation)) {
113+
fkModel = relation.modelFrom
114+
fkName = relation.keyFrom
115+
}
116+
if (fkModel === options.model && fkName !== options.primaryKeyField) {
117+
// check options and decide whether to remove foreign keys.
118+
if (options.foreignKeys !== false && Array.isArray(options.foreignKeys)) {
119+
for (var i = 0; i < options.foreignKeys.length; i++) {
120+
// if match on model
121+
if (options.foreignKeys[i].model === fkModel.sharedClass.name) {
122+
// if no method specified
123+
if (!options.foreignKeys[i].method) return
124+
// if method match
125+
if (options.foreignKeys[i].method === options.method) return
126+
}
127+
}
128+
}
129+
delete data[fkName]
130+
}
131+
})
118132
})
119-
})
133+
}
120134

121135
_.each(data, function (value, property) {
122136
if (property === options.primaryKeyField) {

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"type-is": "^1.6.9"
4040
},
4141
"devDependencies": {
42-
"babel-eslint": "^6.1.0",
4342
"chai": "^3.3.0",
4443
"coveralls": "^2.11.9",
4544
"istanbul": "^0.4.2",

test/create.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('loopback json api component create method', function () {
6161
// waiting on a fix
6262
// see https://github.com/visionmedia/superagent/issues/753
6363
query(app).post('/posts', data, options, function (err, res) {
64-
if (err) console.log(err)
64+
expect(err).to.equal(null)
6565
expect(res.headers['content-type']).to.match(/application\/vnd\.api\+json/)
6666
expect(res.statusCode).to.equal(201)
6767
expect(res.body).to.have.all.keys('data')

test/foreign-keys.test.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
var request = require('supertest')
2+
var loopback = require('loopback')
3+
var expect = require('chai').expect
4+
var JSONAPIComponent = require('../')
5+
var app
6+
var Post
7+
var Comment
8+
9+
describe('foreign key configuration', function () {
10+
beforeEach(function (done) {
11+
app = loopback()
12+
app.set('legacyExplorer', false)
13+
var ds = loopback.createDataSource('memory')
14+
15+
Post = ds.createModel('post', { title: String })
16+
app.model(Post)
17+
18+
Comment = ds.createModel('comment', { comment: String })
19+
app.model(Comment)
20+
21+
Comment.belongsTo(Post)
22+
Post.hasMany(Comment)
23+
24+
app.use(loopback.rest())
25+
26+
Post.create({title: 'my post'}, function (err, post) {
27+
if (err) throw err
28+
post.comments.create({comment: 'my comment'}, done)
29+
})
30+
})
31+
32+
describe('by default, foreign keys are not exposed through the api', function () {
33+
beforeEach(function () {
34+
JSONAPIComponent(app)
35+
})
36+
it('should remove foreign keys from model before output', function (done) {
37+
request(app).get('/comments/1')
38+
.end(function (err, res) {
39+
expect(err).to.equal(null)
40+
expect(res.body.data.attributes).to.not.include.key('postId')
41+
done()
42+
})
43+
})
44+
})
45+
46+
describe('configuring component to always expose foreign keys through the api', function () {
47+
beforeEach(function () {
48+
JSONAPIComponent(app, {
49+
foreignKeys: true
50+
})
51+
})
52+
it('should not remove foreign keys from models before output', function (done) {
53+
request(app).get('/comments/1')
54+
.end(function (err, res) {
55+
expect(err).to.equal(null)
56+
expect(res.body.data.attributes).to.include.key('postId')
57+
done()
58+
})
59+
})
60+
})
61+
62+
describe('configuring component to expose foreign keys for post model through the api', function () {
63+
beforeEach(function () {
64+
JSONAPIComponent(app, {
65+
foreignKeys: [
66+
{model: 'post'}
67+
]
68+
})
69+
})
70+
it('should not expose postId on comment model', function (done) {
71+
request(app).get('/comments/1')
72+
.end(function (err, res) {
73+
expect(err).to.equal(null)
74+
expect(res.body.data.attributes).to.not.include.key('postId')
75+
done()
76+
})
77+
})
78+
})
79+
80+
describe('configuring component to expose foreign keys for comment model through the api', function () {
81+
beforeEach(function () {
82+
JSONAPIComponent(app, {
83+
foreignKeys: [
84+
{model: 'comment'}
85+
]
86+
})
87+
})
88+
it('should expose postId on comment model', function (done) {
89+
request(app).get('/comments/1')
90+
.end(function (err, res) {
91+
expect(err).to.equal(null)
92+
expect(res.body.data.attributes).to.include.key('postId')
93+
done()
94+
})
95+
})
96+
})
97+
98+
describe('configuring component to expose foreign keys for comment model method findById through the api', function () {
99+
beforeEach(function () {
100+
JSONAPIComponent(app, {
101+
foreignKeys: [
102+
{model: 'comment', method: 'findById'}
103+
]
104+
})
105+
})
106+
it('should not expose foreign keys in find all', function (done) {
107+
request(app).get('/comments')
108+
.end(function (err, res) {
109+
expect(err).to.equal(null)
110+
expect(res.body.data[0].attributes).to.not.include.key('postId')
111+
done()
112+
})
113+
})
114+
it('should expose foreign keys in find', function (done) {
115+
request(app).get('/comments/1')
116+
.end(function (err, res) {
117+
expect(err).to.equal(null)
118+
expect(res.body.data.attributes).to.include.key('postId')
119+
done()
120+
})
121+
})
122+
})
123+
})

0 commit comments

Comments
 (0)