Skip to content

Commit 2542a35

Browse files
committed
Merge pull request #87 from digitalsadhu/add/attribute_filter_option
Add attribute whitelist option and update documentation
2 parents f8cb517 + 6d4b547 commit 2542a35

File tree

3 files changed

+222
-51
lines changed

3 files changed

+222
-51
lines changed

README.md

Lines changed: 133 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,12 @@
66
[![Dependency Status](https://david-dm.org/digitalsadhu/loopback-component-jsonapi.svg)](https://david-dm.org/digitalsadhu/loopback-component-jsonapi)
77
[![devDependency Status](https://david-dm.org/digitalsadhu/loopback-component-jsonapi/dev-status.svg)](https://david-dm.org/digitalsadhu/loopback-component-jsonapi#info=devDependencies)
88

9-
JSONAPI support for loopback.
10-
11-
## JSON API Spec
12-
[http://jsonapi.org/](http://jsonapi.org/)
9+
[jsonapi.org](http://jsonapi.org/) support for loopback.
1310

1411
## Status
1512
This project is a work in progress. Consider it beta software. For ember users, the component
1613
should now be basically feature complete. Please test and report any issues.
17-
The functionality that is present is pretty well tested. 130+ integration tests and counting!
14+
The functionality that is present is pretty well tested. 140+ integration tests and counting!
1815

1916
Currently supported:
2017
- Find all records via GET
@@ -44,24 +41,26 @@ Not yet supported:
4441
We have created a sample project using [EmberJS](http://emberjs.com), [Loopback](http://loopback.io) and this compoment. It's called [emberloop](https://github.com/tsteuwer/emberloop).
4542

4643
## Helping out
47-
We are VERY interested in help. See the [issue tracker](https://github.com/digitalsadhu/loopback-component-jsonapi/issues)
44+
We are VERY interested in help. Get in touch via the [issue tracker](https://github.com/digitalsadhu/loopback-component-jsonapi/issues)
4845

4946
## Usage
5047
In your loopback project:
5148

5249
1. `npm install --save loopback-component-jsonapi`
5350
2. Create a `component-config.json` file in your server folder (if you don't already have one)
5451
3. Add the following config to `component-config.json`
52+
5553
```json
5654
{
5755
"loopback-component-jsonapi": {}
5856
}
5957
```
6058

6159
## Advanced usage:
62-
In a fairly limited way, you can configure a how the component behaves.
60+
We are aiming to make the component as configureable as possible. You can configure a how the component behaves with the options shown and listed below. If there is something else you would like to see be configureable, please submit an issue on the repository.
6361

6462
Example:
63+
(all configuration options listed)
6564
```json
6665
{
6766
"loopback-component-jsonapi": {
@@ -74,74 +73,158 @@ Example:
7473
{"model": "post", "methods": "find"},
7574
{"model": "person", "methods": ["find", "create"]}
7675
],
77-
"hideIrrelevantMethods": true
76+
"hideIrrelevantMethods": true,
77+
"attributes": {
78+
"posts": ["title"]
79+
}
7880
}
7981
}
8082
```
8183
### restApiRoot
82-
Url prefix to be used in conjunction with host and resource paths.
83-
eg. http://127.0.0.1:3214/api/people
84-
Default: `/api`
84+
Url prefix to be used in conjunction with host and resource paths. eg. http://127.0.0.1:3214/api/people
85+
86+
#### example
87+
```js
88+
{
89+
...
90+
"restApiRoot": "/api",
91+
...
92+
}
93+
```
94+
95+
- Type: `string`
96+
- Default: `/api`
8597

8698
### enable
87-
Whether the component should be enabled or disabled.
88-
Default: true
99+
Whether the component should be enabled or disabled. Defaults to `false`, flip it to `true` if you need to turn the component off without removing the configuration for some reason.
100+
101+
#### example
102+
```js
103+
{
104+
...
105+
"enable": true,
106+
...
107+
}
108+
```
109+
110+
- Type: `boolean`
111+
- Default: `true`
89112

90113
### handleErrors
91114
When true, the component will unregister all other error handling and
92115
register a custom error handler which always returns errors in jsonapi compliant
93116
format. Validation errors include the correct properties in order to work
94117
out of the box with ember.
95-
Default: true
118+
119+
#### example
120+
```js
121+
{
122+
...
123+
"handleErrors": true,
124+
...
125+
}
126+
```
127+
128+
- Type: `boolean`
129+
- Default: `true`
96130

97131
### exclude
98-
Allows blacklisting of models and methods. (See example above)
132+
Allows blacklisting of models and methods.
99133
Define an array of blacklist objects. Blacklist objects can contain "model" key
100134
"methods" key or both. If just "model" is defined then all methods for the
101-
specified model will not use jsonapi. If just the "methods" key is defined then
102-
all methods specified on all models will be not use jsonapi. If a combination of
135+
specified model will not be serialized of deserialized using jsonapi. If just the "methods" key is defined then
136+
all methods specified on all models will be serialized or deserialized using jsonapi. If a combination of
103137
"model" and "methods" keys are used then the specific combination of model and methods
104-
specified will not use jsonapi.
105-
106-
#### Please note
107-
The default component behavior currently is to only modify the output of the following
108-
methods on all models to be json api compliant:
109-
- find
110-
- create
111-
- updateAttributes
112-
- deleteById
113-
- findById
114-
- __get__.*
115-
- __findRelationships__.*
116-
117-
And the default current behavior for modifying input only applies to the following methods on
118-
all models:
119-
- create
120-
- updateAttributes
121-
122-
Type: array
123-
Default: null
138+
specified will not be serialized or deserialized using jsonapi.
139+
140+
#### example
141+
```js
142+
{
143+
...
144+
"exclude": [
145+
{"model": "comment"},
146+
{"methods": "find"},
147+
{"model": "post", "methods": "find"},
148+
{"model": "person", "methods": ["find", "create"]}
149+
],
150+
...
151+
}
152+
```
153+
154+
- Type: `array`
155+
- Default: `null`
156+
157+
#### Note
158+
The default component behavior is to modify the output of the following CRUD model methods
159+
methods on all models:
160+
- `find`
161+
- `create`
162+
- `updateAttributes`
163+
- `deleteById`
164+
- `findById`
165+
166+
In addition the following wild card method names are matched and the output is modified in order to handle relationships eg. `/api/posts/1/comments`
167+
- `__get__.*`
168+
- `__findRelationships__.*`
169+
170+
The default behavior for modifying input only applies to the following methods on all models:
171+
- `create`
172+
- `updateAttributes`
124173

125174
### hideIrrelevantMethods
126-
By default, loopback-component-jsonapi disables a number of methods from each endpoint
175+
By default, `loopback-component-jsonapi` disables a number of methods from each endpoint
127176
that are not jsonapi relevant. These methods are:
128-
- upsert
129-
- exists
130-
- findOne
131-
- count
132-
- createChangeStream
133-
- updateAll
134-
You can use this option to reenable these methods.
135-
Please note, these methods will not be modified by the component and so their output
177+
- `upsert`
178+
- `exists`
179+
- `findOne`
180+
- `count`
181+
- `createChangeStream`
182+
- `updateAll`
183+
184+
You can use this option to prevent `loopback-component-jsonapi` from doing so. These methods are not modified by the component. Their output
136185
will not be in a jsonapi compliant format.
137-
Type: boolean
138-
Default: true
186+
187+
#### example
188+
```js
189+
{
190+
...
191+
"hideIrrelevantMethods": true,
192+
...
193+
}
194+
```
195+
196+
- Type: `boolean`
197+
- Default: `true`
198+
199+
### attributes
200+
By default, model properties will be converted to attributes in jsonapi terms.
201+
All model properties except the primary key and any foreign keys will be copied into
202+
the attributes object before output. If you wish to limit which properties will
203+
be output as attributes you can specify a whitelist of attributes for each type.
204+
205+
#### example
206+
```js
207+
{
208+
...
209+
"attributes": {
210+
"posts": ["title", "content"],
211+
"comments": ["createdAt", "updatedAt", "comment"]
212+
}
213+
...
214+
}
215+
```
216+
217+
- Type: `object`
218+
- Default: `null`
219+
220+
#### note
221+
The attributes arrays are keyed by type not by model name. Type is the term used by json api to describe the resource type in question and while not required by json api it is usually plural. In `loopback-component-jsonapi` it is whatever the models `plural` is set to in `model.json`. So in our example above we defined: `"posts": ["title", "content"]` as the resource type for the `post` model is `posts`
139222

140223
## Debugging
141224
You can enable debug logging by setting an environment variable:
142-
DEBUG=loopback-component-jsonapi
225+
`DEBUG=loopback-component-jsonapi`
143226

144-
Example:
227+
#### example:
145228
```
146229
DEBUG=loopback-component-jsonapi node .
147230
```

lib/serializer.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module.exports = function serializer (type, data, relations, options) {
66

77
var result = null;
88
var resultData = {};
9+
options.attributes = options.attributes || {};
910

1011
options.isRelationshipRequest = false;
1112

@@ -62,7 +63,9 @@ function parseResource (type, data, relations, options) {
6263
if (property === options.primaryKeyField) {
6364
resource.id = _(value).toString();
6465
} else {
65-
attributes[property] = value;
66+
if (!options.attributes[type] || _.contains(options.attributes[type], property)) {
67+
attributes[property] = value;
68+
}
6669
}
6770
});
6871

test/override-attributes.test.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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('attributes option', 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, content: String, other: String });
16+
app.model(Post);
17+
18+
Comment = ds.createModel('comment', { title: String, comment: String, other: String });
19+
app.model(Comment);
20+
21+
app.use(loopback.rest());
22+
23+
Post.create({title: 'my post', content: 'my post content', other: 'my post other'}, function () {
24+
Comment.create({title: 'my comment title', comment: 'my comment', other: 'comment other'}, done);
25+
});
26+
});
27+
28+
describe('whitelisting model attributes', function () {
29+
beforeEach(function () {
30+
JSONAPIComponent(app, {
31+
attributes: {
32+
posts: ['title']
33+
}
34+
});
35+
});
36+
37+
it('should return only title in attributes for posts', function (done) {
38+
request(app).get('/posts')
39+
.expect(200)
40+
.end(function (err, res) {
41+
expect(err).to.equal(null);
42+
expect(res.body.data[0].attributes).to.deep.equal({ title: 'my post' });
43+
done();
44+
});
45+
});
46+
47+
it('should return all attributes for comments', function (done) {
48+
request(app).get('/comments')
49+
.expect(200)
50+
.end(function (err, res) {
51+
expect(err).to.equal(null);
52+
expect(res.body.data[0].attributes).to.deep.equal({
53+
title: 'my comment title',
54+
comment: 'my comment',
55+
other: 'comment other'
56+
});
57+
done();
58+
});
59+
});
60+
61+
it('should return only title in attributes for a post', function (done) {
62+
request(app).get('/posts/1')
63+
.expect(200)
64+
.end(function (err, res) {
65+
expect(err).to.equal(null);
66+
expect(res.body.data.attributes).to.deep.equal({ title: 'my post' });
67+
done();
68+
});
69+
});
70+
71+
it('should return all attributes for a comment', function (done) {
72+
request(app).get('/comments/1')
73+
.expect(200)
74+
.end(function (err, res) {
75+
expect(err).to.equal(null);
76+
expect(res.body.data.attributes).to.deep.equal({
77+
title: 'my comment title',
78+
comment: 'my comment',
79+
other: 'comment other'
80+
});
81+
done();
82+
});
83+
});
84+
});
85+
});

0 commit comments

Comments
 (0)