jsonapi-serializers 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/README.md +39 -10
- data/lib/jsonapi-serializers/serializer.rb +25 -4
- data/lib/jsonapi-serializers/version.rb +1 -1
- data/spec/serializer_spec.rb +112 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a63fa6a767f6236335d147e099ca81b13e24d72
|
4
|
+
data.tar.gz: e8fe7e954d1b5a14d2903ff0d72923eb24d0abb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 64417e501b68bb8e686eb362e8ea53c65afff161d5c9f0f881e08d15436c8f4b27fd06491ee5d479d74c99ce7ed5c78e72e001a82a18a019d56cbd7b99ef28e9
|
7
|
+
data.tar.gz: da8f7e8f9c78c18f5d6f6a2ae7b1399116a7876f40c0c71b4d59d36615be5ba88e7b5c978b3f536dbd61e071ea6fd103940044a3b3a3d89d08a66649f113ddf1
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -18,12 +18,13 @@ This library is up-to-date with the finalized v1 JSON API spec.
|
|
18
18
|
* [Custom attributes](#custom-attributes)
|
19
19
|
* [More customizations](#more-customizations)
|
20
20
|
* [Base URL](#base-url)
|
21
|
-
* [Root jsonapi object](#root-jsonapi-object)
|
22
21
|
* [Root metadata](#root-metadata)
|
23
22
|
* [Root links](#root-links)
|
24
23
|
* [Root errors](#root-errors)
|
24
|
+
* [Root jsonapi object](#root-jsonapi-object)
|
25
25
|
* [Explicit serializer discovery](#explicit-serializer-discovery)
|
26
26
|
* [Namespace serializers](#namespace-serializers)
|
27
|
+
* [Sparse fieldsets](#sparse-fieldsets)
|
27
28
|
* [Relationships](#relationships)
|
28
29
|
* [Compound documents and includes](#compound-documents-and-includes)
|
29
30
|
* [Relationship path handling](#relationship-path-handling)
|
@@ -318,14 +319,6 @@ JSONAPI::Serializer.serialize(post, base_url: 'http://example.com')
|
|
318
319
|
|
319
320
|
Note: if you override `self_link` in your serializer and leave out `base_url`, it will not be included.
|
320
321
|
|
321
|
-
### Root 'jsonapi' object
|
322
|
-
|
323
|
-
You can pass a `jsonapi` argument to specify a [top-level "jsonapi" key](http://jsonapi.org/format/#document-jsonapi-object) containing the version of JSON:API in use:
|
324
|
-
|
325
|
-
```ruby
|
326
|
-
JSONAPI::Serializer.serialize(post, jsonapi: {version: '1.0'})
|
327
|
-
```
|
328
|
-
|
329
322
|
### Root metadata
|
330
323
|
|
331
324
|
You can pass a `meta` argument to specify top-level metadata:
|
@@ -372,6 +365,14 @@ class Api::V1::ReposController < Api::V1::BaseController
|
|
372
365
|
end
|
373
366
|
```
|
374
367
|
|
368
|
+
### Root 'jsonapi' object
|
369
|
+
|
370
|
+
You can pass a `jsonapi` argument to specify a [top-level "jsonapi" key](http://jsonapi.org/format/#document-jsonapi-object) containing the version of JSON:API in use:
|
371
|
+
|
372
|
+
```ruby
|
373
|
+
JSONAPI::Serializer.serialize(post, jsonapi: {version: '1.0'})
|
374
|
+
```
|
375
|
+
|
375
376
|
### Explicit serializer discovery
|
376
377
|
|
377
378
|
By default, jsonapi-serializers assumes that the serializer class for `Namespace::User` is `Namespace::UserSerializer`. You can override this behavior on a per-object basis by implementing the `jsonapi_serializer_class_name` method.
|
@@ -416,6 +417,35 @@ JSONAPI::Serializer.serialize(post, namespace: Api::V2)
|
|
416
417
|
|
417
418
|
This option overrides the `jsonapi_serializer_class_name` method.
|
418
419
|
|
420
|
+
### Sparse fieldsets
|
421
|
+
|
422
|
+
The JSON:API spec allows to return only [specific fields](http://jsonapi.org/format/#fetching-sparse-fieldsets) from attributes and relationships.
|
423
|
+
|
424
|
+
For example, if you wanted to return only the `title` field and `author` relationship link for `posts`:
|
425
|
+
|
426
|
+
```ruby
|
427
|
+
fields =
|
428
|
+
JSONAPI::Serializer.serialize(post, fields: {posts: [:title]})
|
429
|
+
```
|
430
|
+
|
431
|
+
Sparse fieldsets also affect relationship links. In this case, only the `author` relationship link would be included:
|
432
|
+
|
433
|
+
``` ruby
|
434
|
+
JSONAPI::Serializer.serialize(post, fields: {posts: [:title, :author]})
|
435
|
+
```
|
436
|
+
|
437
|
+
Sparse fieldsets operate on a per-type basis, so they affect all resources in the response including in compound documents. For example, this will affect both the `posts` type in the primary data and the `users` type in the compound data:
|
438
|
+
|
439
|
+
``` ruby
|
440
|
+
JSONAPI::Serializer.serialize(
|
441
|
+
post,
|
442
|
+
fields: {posts: ['title', 'author'], users: ['name']},
|
443
|
+
include: 'author',
|
444
|
+
)
|
445
|
+
```
|
446
|
+
|
447
|
+
Sparse fieldsets support comma-separated strings (`fields: {posts: 'title,author'}`, arrays of strings (`fields: {posts: ['title', 'author']}`), single symbols (`fields: {posts: :title}`), and arrays of symbols (`fields: {posts: [:title, :author]}`).
|
448
|
+
|
419
449
|
## Relationships
|
420
450
|
|
421
451
|
You can easily specify relationships with the `has_one` and `has_many` directives.
|
@@ -783,7 +813,6 @@ See [Releases](https://github.com/fotinakis/jsonapi-serializers/releases).
|
|
783
813
|
|
784
814
|
## Unfinished business
|
785
815
|
|
786
|
-
* Support for the `fields` spec is planned, would love a PR contribution for this.
|
787
816
|
* Support for pagination/sorting is unlikely to be supported because it would likely involve coupling to ActiveRecord, but please open an issue if you have ideas of how to support this generically.
|
788
817
|
|
789
818
|
## Contributing
|
@@ -33,6 +33,7 @@ module JSONAPI
|
|
33
33
|
@base_url = options[:base_url]
|
34
34
|
|
35
35
|
# Internal serializer options, not exposed through attr_accessor. No touchie.
|
36
|
+
@_fields = options[:fields] || {}
|
36
37
|
@_include_linkages = options[:include_linkages] || []
|
37
38
|
end
|
38
39
|
|
@@ -165,7 +166,7 @@ module JSONAPI
|
|
165
166
|
return {} if self.class.attributes_map.nil?
|
166
167
|
attributes = {}
|
167
168
|
self.class.attributes_map.each do |attribute_name, attr_data|
|
168
|
-
next if !should_include_attr?(
|
169
|
+
next if !should_include_attr?(attribute_name, attr_data)
|
169
170
|
value = evaluate_attr_or_block(attribute_name, attr_data[:attr_or_block])
|
170
171
|
attributes[format_name(attribute_name)] = value
|
171
172
|
end
|
@@ -176,7 +177,7 @@ module JSONAPI
|
|
176
177
|
return {} if self.class.to_one_associations.nil?
|
177
178
|
data = {}
|
178
179
|
self.class.to_one_associations.each do |attribute_name, attr_data|
|
179
|
-
next if !should_include_attr?(
|
180
|
+
next if !should_include_attr?(attribute_name, attr_data)
|
180
181
|
data[attribute_name] = attr_data
|
181
182
|
end
|
182
183
|
data
|
@@ -190,7 +191,7 @@ module JSONAPI
|
|
190
191
|
return {} if self.class.to_many_associations.nil?
|
191
192
|
data = {}
|
192
193
|
self.class.to_many_associations.each do |attribute_name, attr_data|
|
193
|
-
next if !should_include_attr?(
|
194
|
+
next if !should_include_attr?(attribute_name, attr_data)
|
194
195
|
data[attribute_name] = attr_data
|
195
196
|
end
|
196
197
|
data
|
@@ -200,11 +201,14 @@ module JSONAPI
|
|
200
201
|
evaluate_attr_or_block(attribute_name, attr_data[:attr_or_block])
|
201
202
|
end
|
202
203
|
|
203
|
-
def should_include_attr?(
|
204
|
+
def should_include_attr?(attribute_name, attr_data)
|
204
205
|
# Allow "if: :show_title?" and "unless: :hide_title?" attribute options.
|
206
|
+
if_method_name = attr_data[:options][:if]
|
207
|
+
unless_method_name = attr_data[:options][:unless]
|
205
208
|
show_attr = true
|
206
209
|
show_attr &&= send(if_method_name) if if_method_name
|
207
210
|
show_attr &&= !send(unless_method_name) if unless_method_name
|
211
|
+
show_attr &&= @_fields[type.to_s].include?(attribute_name) if @_fields[type.to_s]
|
208
212
|
show_attr
|
209
213
|
end
|
210
214
|
protected :should_include_attr?
|
@@ -252,6 +256,7 @@ module JSONAPI
|
|
252
256
|
options[:jsonapi] = options.delete('jsonapi') || options[:jsonapi]
|
253
257
|
options[:meta] = options.delete('meta') || options[:meta]
|
254
258
|
options[:links] = options.delete('links') || options[:links]
|
259
|
+
options[:fields] = options.delete('fields') || options[:fields] || {}
|
255
260
|
|
256
261
|
# Deprecated: use serialize_errors method instead
|
257
262
|
options[:errors] = options.delete('errors') || options[:errors]
|
@@ -260,12 +265,27 @@ module JSONAPI
|
|
260
265
|
includes = options[:include]
|
261
266
|
includes = (includes.is_a?(String) ? includes.split(',') : includes).uniq if includes
|
262
267
|
|
268
|
+
# Transforms input so that the comma-separated fields are separate symbols in array
|
269
|
+
# and keys are stringified
|
270
|
+
# Example:
|
271
|
+
# {posts: 'title,author,long_comments'} => {'posts' => [:title, :author, :long_comments]}
|
272
|
+
# {posts: ['title', 'author', 'long_comments'} => {'posts' => [:title, :author, :long_comments]}
|
273
|
+
#
|
274
|
+
fields = {}
|
275
|
+
# Normalize fields to accept a comma-separated string or an array of strings.
|
276
|
+
options[:fields].map do |type, whitelisted_fields|
|
277
|
+
whitelisted_fields = [whitelisted_fields] if whitelisted_fields.is_a?(Symbol)
|
278
|
+
whitelisted_fields = whitelisted_fields.split(',') if whitelisted_fields.is_a?(String)
|
279
|
+
fields[type.to_s] = whitelisted_fields.map(&:to_sym)
|
280
|
+
end
|
281
|
+
|
263
282
|
# An internal-only structure that is passed through serializers as they are created.
|
264
283
|
passthrough_options = {
|
265
284
|
context: options[:context],
|
266
285
|
serializer: options[:serializer],
|
267
286
|
namespace: options[:namespace],
|
268
287
|
include: includes,
|
288
|
+
fields: fields,
|
269
289
|
base_url: options[:base_url]
|
270
290
|
}
|
271
291
|
|
@@ -328,6 +348,7 @@ module JSONAPI
|
|
328
348
|
included_passthrough_options = {}
|
329
349
|
included_passthrough_options[:base_url] = passthrough_options[:base_url]
|
330
350
|
included_passthrough_options[:context] = passthrough_options[:context]
|
351
|
+
included_passthrough_options[:fields] = passthrough_options[:fields]
|
331
352
|
included_passthrough_options[:serializer] = find_serializer_class(data[:object], options)
|
332
353
|
included_passthrough_options[:namespace] = passthrough_options[:namespace]
|
333
354
|
included_passthrough_options[:include_linkages] = data[:include_linkages]
|
data/spec/serializer_spec.rb
CHANGED
@@ -441,12 +441,12 @@ describe JSONAPI::Serializer do
|
|
441
441
|
it 'can include a top level errors node' do
|
442
442
|
errors = [
|
443
443
|
{
|
444
|
-
'source' => {
|
444
|
+
'source' => {'pointer' => '/data/attributes/first-name'},
|
445
445
|
'title' => 'Invalid Attribute',
|
446
446
|
'detail' => 'First name must contain at least three characters.'
|
447
447
|
},
|
448
448
|
{
|
449
|
-
'source' => {
|
449
|
+
'source' => {'pointer' => '/data/attributes/first-name'},
|
450
450
|
'title' => 'Invalid Attribute',
|
451
451
|
'detail' => 'First name must contain an emoji.'
|
452
452
|
}
|
@@ -477,15 +477,15 @@ describe JSONAPI::Serializer do
|
|
477
477
|
user = DummyUser.new
|
478
478
|
jsonapi_errors = [
|
479
479
|
{
|
480
|
-
'source' => {
|
480
|
+
'source' => {'pointer' => '/data/attributes/email'},
|
481
481
|
'detail' => 'Email is invalid'
|
482
482
|
},
|
483
483
|
{
|
484
|
-
'source' => {
|
484
|
+
'source' => {'pointer' => '/data/attributes/email'},
|
485
485
|
'detail' => "Email can't be blank"
|
486
486
|
},
|
487
487
|
{
|
488
|
-
'source' => {
|
488
|
+
'source' => {'pointer' => '/data/attributes/first-name'},
|
489
489
|
'detail' => "First name can't be blank"
|
490
490
|
}
|
491
491
|
]
|
@@ -854,6 +854,113 @@ describe JSONAPI::Serializer do
|
|
854
854
|
})
|
855
855
|
end
|
856
856
|
end
|
857
|
+
|
858
|
+
context 'sparse fieldsets' do
|
859
|
+
it 'allows to limit fields(attributes) for serialized resource' do
|
860
|
+
first_user = create(:user)
|
861
|
+
second_user = create(:user)
|
862
|
+
first_comment = create(:long_comment, user: first_user)
|
863
|
+
second_comment = create(:long_comment, user: second_user)
|
864
|
+
long_comments = [first_comment, second_comment]
|
865
|
+
post = create(:post, :with_author, long_comments: long_comments)
|
866
|
+
|
867
|
+
serialized_data = JSONAPI::Serializer.serialize(post, fields: {posts: :title})
|
868
|
+
expect(serialized_data).to eq ({
|
869
|
+
'data' => {
|
870
|
+
'type' => 'posts',
|
871
|
+
'id' => post.id.to_s,
|
872
|
+
'attributes' => {
|
873
|
+
'title' => post.title,
|
874
|
+
},
|
875
|
+
'links' => {
|
876
|
+
'self' => '/posts/1'
|
877
|
+
}
|
878
|
+
}
|
879
|
+
})
|
880
|
+
end
|
881
|
+
it 'allows to limit fields(relationships) for serialized resource' do
|
882
|
+
first_user = create(:user)
|
883
|
+
second_user = create(:user)
|
884
|
+
first_comment = create(:long_comment, user: first_user)
|
885
|
+
second_comment = create(:long_comment, user: second_user)
|
886
|
+
long_comments = [first_comment, second_comment]
|
887
|
+
post = create(:post, :with_author, long_comments: long_comments)
|
888
|
+
|
889
|
+
fields = {posts: 'title,author,long_comments'}
|
890
|
+
serialized_data = JSONAPI::Serializer.serialize(post, fields: fields)
|
891
|
+
expect(serialized_data['data']['relationships']).to eq ({
|
892
|
+
'author' => {
|
893
|
+
'links' => {
|
894
|
+
'self' => '/posts/1/relationships/author',
|
895
|
+
'related' => '/posts/1/author'
|
896
|
+
}
|
897
|
+
},
|
898
|
+
'long-comments' => {
|
899
|
+
'links' => {
|
900
|
+
'self' => '/posts/1/relationships/long-comments',
|
901
|
+
'related' => '/posts/1/long-comments'
|
902
|
+
}
|
903
|
+
}
|
904
|
+
})
|
905
|
+
end
|
906
|
+
it "allows also to pass specific fields as array instead of comma-separates values" do
|
907
|
+
first_user = create(:user)
|
908
|
+
second_user = create(:user)
|
909
|
+
first_comment = create(:long_comment, user: first_user)
|
910
|
+
second_comment = create(:long_comment, user: second_user)
|
911
|
+
long_comments = [first_comment, second_comment]
|
912
|
+
post = create(:post, :with_author, long_comments: long_comments)
|
913
|
+
|
914
|
+
serialized_data = JSONAPI::Serializer.serialize(post, fields: {posts: ['title', 'author']})
|
915
|
+
expect(serialized_data['data']['attributes']).to eq ({
|
916
|
+
'title' => post.title
|
917
|
+
})
|
918
|
+
expect(serialized_data['data']['relationships']).to eq ({
|
919
|
+
'author' => {
|
920
|
+
'links' => {
|
921
|
+
'self' => '/posts/1/relationships/author',
|
922
|
+
'related' => '/posts/1/author'
|
923
|
+
}
|
924
|
+
}
|
925
|
+
})
|
926
|
+
end
|
927
|
+
it 'allows to limit fields(attributes and relationships) for included resources' do
|
928
|
+
first_user = create(:user)
|
929
|
+
second_user = create(:user)
|
930
|
+
first_comment = create(:long_comment, user: first_user)
|
931
|
+
second_comment = create(:long_comment, user: second_user)
|
932
|
+
long_comments = [first_comment, second_comment]
|
933
|
+
post = create(:post, :with_author, long_comments: long_comments)
|
934
|
+
|
935
|
+
expected_primary_data = serialize_primary(post, {
|
936
|
+
serializer: MyApp::PostSerializer,
|
937
|
+
include_linkages: ['author'],
|
938
|
+
fields: {'posts' => [:title, :author] }
|
939
|
+
})
|
940
|
+
|
941
|
+
fields = {posts: 'title,author', users: ''}
|
942
|
+
serialized_data = JSONAPI::Serializer.serialize(post, fields: fields, include: 'author')
|
943
|
+
expect(serialized_data).to eq ({
|
944
|
+
'data' => expected_primary_data,
|
945
|
+
'included' => [
|
946
|
+
serialize_primary(
|
947
|
+
post.author,
|
948
|
+
serializer: MyAppOtherNamespace::UserSerializer,
|
949
|
+
fields: {'users' => []},
|
950
|
+
)
|
951
|
+
]
|
952
|
+
})
|
953
|
+
|
954
|
+
fields = {posts: 'title,author'}
|
955
|
+
serialized_data = JSONAPI::Serializer.serialize(post, fields: fields, include: 'author')
|
956
|
+
expect(serialized_data).to eq ({
|
957
|
+
'data' => expected_primary_data,
|
958
|
+
'included' => [
|
959
|
+
serialize_primary(post.author, serializer: MyAppOtherNamespace::UserSerializer)
|
960
|
+
]
|
961
|
+
})
|
962
|
+
end
|
963
|
+
end
|
857
964
|
end
|
858
965
|
|
859
966
|
describe 'serialize (class method)' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-serializers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Fotinakis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|