jsonapi-resources 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +274 -102
  3. data/jsonapi-resources.gemspec +1 -0
  4. data/lib/jsonapi-resources.rb +15 -0
  5. data/lib/jsonapi/active_record_operations_processor.rb +21 -10
  6. data/lib/jsonapi/acts_as_resource_controller.rb +175 -0
  7. data/lib/jsonapi/configuration.rb +11 -0
  8. data/lib/jsonapi/error_codes.rb +7 -4
  9. data/lib/jsonapi/exceptions.rb +23 -15
  10. data/lib/jsonapi/formatter.rb +5 -5
  11. data/lib/jsonapi/include_directives.rb +67 -0
  12. data/lib/jsonapi/operation.rb +185 -65
  13. data/lib/jsonapi/operation_result.rb +38 -5
  14. data/lib/jsonapi/operation_results.rb +33 -0
  15. data/lib/jsonapi/operations_processor.rb +49 -9
  16. data/lib/jsonapi/paginator.rb +31 -17
  17. data/lib/jsonapi/request.rb +347 -163
  18. data/lib/jsonapi/resource.rb +159 -56
  19. data/lib/jsonapi/resource_controller.rb +1 -234
  20. data/lib/jsonapi/resource_serializer.rb +55 -69
  21. data/lib/jsonapi/resources/version.rb +1 -1
  22. data/lib/jsonapi/response_document.rb +87 -0
  23. data/lib/jsonapi/routing_ext.rb +17 -11
  24. data/test/controllers/controller_test.rb +602 -326
  25. data/test/fixtures/active_record.rb +96 -6
  26. data/test/fixtures/line_items.yml +7 -1
  27. data/test/fixtures/numeros_telefone.yml +3 -0
  28. data/test/fixtures/purchase_orders.yml +6 -0
  29. data/test/integration/requests/request_test.rb +129 -60
  30. data/test/integration/routes/routes_test.rb +17 -17
  31. data/test/test_helper.rb +23 -5
  32. data/test/unit/jsonapi_request/jsonapi_request_test.rb +48 -0
  33. data/test/unit/operation/operations_processor_test.rb +242 -54
  34. data/test/unit/resource/resource_test.rb +108 -2
  35. data/test/unit/serializer/include_directives_test.rb +108 -0
  36. data/test/unit/serializer/response_document_test.rb +61 -0
  37. data/test/unit/serializer/serializer_test.rb +679 -520
  38. metadata +26 -2
@@ -132,7 +132,113 @@ class ResourceTest < ActiveSupport::TestCase
132
132
  end
133
133
  end
134
134
 
135
- def test_updateable_fields_does_not_include_id
136
- assert(!CatResource.updateable_fields.include?(:id))
135
+ def test_updatable_fields_does_not_include_id
136
+ assert(!CatResource.updatable_fields.include?(:id))
137
+ end
138
+
139
+ # TODO: Please remove after `updateable_fields` is removed
140
+ def test_updateable_fields_delegates_to_updatable_fields_with_deprecation
141
+ ActiveSupport::Deprecation.silence do
142
+ assert_empty(CatResource.updateable_fields(nil) - [:mother, :father, :name, :breed])
143
+ end
144
+ end
145
+
146
+ # TODO: Please remove after `createable_fields` is removed
147
+ def test_createable_fields_delegates_to_creatable_fields_with_deprecation
148
+ ActiveSupport::Deprecation.silence do
149
+ assert_empty(CatResource.createable_fields(nil) - [:mother, :father, :name, :breed, :id])
150
+ end
151
+ end
152
+
153
+ def test_has_many_association_filters
154
+ post_resource = PostResource.new(Post.find(1))
155
+ comments = post_resource.comments
156
+ assert_equal(2, comments.size)
157
+
158
+ # define apply_filters method on post resource to not respect filters
159
+ PostResource.instance_eval do
160
+ def apply_filters(records, filters, options)
161
+ # :nocov:
162
+ records
163
+ # :nocov:
164
+ end
165
+ end
166
+
167
+ filtered_comments = post_resource.comments({ filters: { body: 'i liked it' } })
168
+ assert_equal(1, filtered_comments.size)
169
+
170
+ # reset method to original implementation
171
+ PostResource.instance_eval do
172
+ def apply_filters(records, filters, options)
173
+ # :nocov:
174
+ super
175
+ # :nocov:
176
+ end
177
+ end
178
+ end
179
+
180
+ def test_has_many_association_sorts
181
+ post_resource = PostResource.new(Post.find(1))
182
+ comment_ids = post_resource.comments.map{|c| c.model.id }
183
+ assert_equal [1,2], comment_ids
184
+
185
+ # define apply_filters method on post resource to not respect filters
186
+ PostResource.instance_eval do
187
+ def apply_sort(records, criteria)
188
+ # :nocov:
189
+ records
190
+ # :nocov:
191
+ end
192
+ end
193
+
194
+ sorted_comment_ids = post_resource.comments(sort_criteria: [{ field: 'id', direction: 'desc'}]).map{|c| c.model.id }
195
+ assert_equal [2,1], sorted_comment_ids
196
+
197
+ # reset method to original implementation
198
+ PostResource.instance_eval do
199
+ def apply_sort(records, criteria)
200
+ # :nocov:
201
+ super
202
+ # :nocov:
203
+ end
204
+ end
205
+ end
206
+
207
+ def test_has_many_association_pagination
208
+ post_resource = PostResource.new(Post.find(1))
209
+ comments = post_resource.comments
210
+ assert_equal 2, comments.size
211
+
212
+ # define apply_filters method on post resource to not respect filters
213
+ PostResource.instance_eval do
214
+ def apply_pagination(records, criteria, order_options)
215
+ # :nocov:
216
+ records
217
+ # :nocov:
218
+ end
219
+ end
220
+
221
+ paginator_class = Class.new(JSONAPI::Paginator) do
222
+ def initialize(params)
223
+ # param parsing and validation here
224
+ @page = params.to_i
225
+ end
226
+
227
+ def apply(relation, order_options)
228
+ relation.offset(@page).limit(1)
229
+ end
230
+ end
231
+
232
+ paged_comments = post_resource.comments(paginator: paginator_class.new(1))
233
+ assert_equal 1, paged_comments.size
234
+
235
+ # reset method to original implementation
236
+ PostResource.instance_eval do
237
+ def apply_pagination(records, criteria, order_options)
238
+ # :nocov:
239
+ super
240
+ # :nocov:
241
+ end
242
+ end
137
243
  end
138
244
  end
@@ -0,0 +1,108 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+ require 'jsonapi-resources'
3
+
4
+ class IncludeDirectivesTest < ActiveSupport::TestCase
5
+
6
+ def test_one_level_one_include
7
+ directives = JSONAPI::IncludeDirectives.new(['posts']).include_directives
8
+
9
+ assert_hash_equals(
10
+ {
11
+ include_related: {
12
+ posts: {
13
+ include: true,
14
+ include_related:{}
15
+ }
16
+ }
17
+ },
18
+ directives)
19
+ end
20
+
21
+ def test_one_level_multiple_includes
22
+ directives = JSONAPI::IncludeDirectives.new(['posts', 'comments', 'tags']).include_directives
23
+
24
+ assert_hash_equals(
25
+ {
26
+ include_related: {
27
+ posts: {
28
+ include: true,
29
+ include_related:{}
30
+ },
31
+ comments: {
32
+ include: true,
33
+ include_related:{}
34
+ },
35
+ tags: {
36
+ include: true,
37
+ include_related:{}
38
+ }
39
+ }
40
+ },
41
+ directives)
42
+ end
43
+
44
+ def test_two_levels_include_full_path
45
+ directives = JSONAPI::IncludeDirectives.new(['posts.comments']).include_directives
46
+
47
+ assert_hash_equals(
48
+ {
49
+ include_related: {
50
+ posts: {
51
+ include: true,
52
+ include_related:{
53
+ comments: {
54
+ include: true,
55
+ include_related:{}
56
+ }
57
+ }
58
+ }
59
+ }
60
+ },
61
+ directives)
62
+ end
63
+
64
+ def test_two_levels_include_full_path_redundant
65
+ directives = JSONAPI::IncludeDirectives.new(['posts','posts.comments']).include_directives
66
+
67
+ assert_hash_equals(
68
+ {
69
+ include_related: {
70
+ posts: {
71
+ include: true,
72
+ include_related:{
73
+ comments: {
74
+ include: true,
75
+ include_related:{}
76
+ }
77
+ }
78
+ }
79
+ }
80
+ },
81
+ directives)
82
+ end
83
+
84
+ def test_three_levels_include_full
85
+ directives = JSONAPI::IncludeDirectives.new(['posts.comments.tags']).include_directives
86
+
87
+ assert_hash_equals(
88
+ {
89
+ include_related: {
90
+ posts: {
91
+ include: true,
92
+ include_related:{
93
+ comments: {
94
+ include: true,
95
+ include_related:{
96
+ tags: {
97
+ include: true,
98
+ include_related:{}
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ },
106
+ directives)
107
+ end
108
+ end
@@ -0,0 +1,61 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+ require 'jsonapi-resources'
3
+ require 'json'
4
+
5
+ class ResponseDocumentTest < ActionDispatch::IntegrationTest
6
+ def setup
7
+ JSONAPI.configuration.json_key_format = :dasherized_key
8
+ JSONAPI.configuration.route_format = :dasherized_route
9
+ end
10
+
11
+ def create_response_document(operation_results, resource_klass)
12
+ JSONAPI::ResponseDocument.new(
13
+ operation_results,
14
+ {
15
+ primary_resource_klass: resource_klass
16
+ }
17
+ )
18
+ end
19
+
20
+ def test_response_document
21
+ operations = [
22
+ JSONAPI::CreateResourceOperation.new(PlanetResource, data: {attributes: {'name' => 'Earth 2.0'}}),
23
+ JSONAPI::CreateResourceOperation.new(PlanetResource, data: {attributes: {'name' => 'Vulcan'}})
24
+ ]
25
+
26
+ request = JSONAPI::Request.new
27
+ request.operations = operations
28
+
29
+ op = BasicOperationsProcessor.new()
30
+ operation_results = op.process(request)
31
+
32
+ response_doc = create_response_document(operation_results, PlanetResource)
33
+
34
+ assert_equal :created, response_doc.status
35
+ contents = response_doc.contents
36
+ assert contents.is_a?(Hash)
37
+ assert contents[:data].is_a?(Array)
38
+ assert_equal 2, contents[:data].size
39
+ end
40
+
41
+ def test_response_document_multiple_find
42
+ operations = [
43
+ JSONAPI::FindOperation.new(PostResource, filters: {id: '1'}),
44
+ JSONAPI::FindOperation.new(PostResource, filters: {id: '2'})
45
+ ]
46
+
47
+ request = JSONAPI::Request.new
48
+ request.operations = operations
49
+
50
+ op = ActiveRecordOperationsProcessor.new()
51
+ operation_results = op.process(request)
52
+
53
+ response_doc = create_response_document(operation_results, PostResource)
54
+
55
+ assert_equal :ok, response_doc.status
56
+ contents = response_doc.contents
57
+ assert contents.is_a?(Hash)
58
+ assert contents[:data].is_a?(Array)
59
+ assert_equal 2, contents[:data].size
60
+ end
61
+ end
@@ -19,42 +19,58 @@ class SerializerTest < ActionDispatch::IntegrationTest
19
19
 
20
20
  def test_serializer
21
21
 
22
+ serialized = JSONAPI::ResourceSerializer.new(
23
+ PostResource,
24
+ base_url: 'http://example.com').serialize_to_hash(PostResource.new(@post)
25
+ )
26
+
22
27
  assert_hash_equals(
23
28
  {
24
29
  data: {
25
30
  type: 'posts',
26
31
  id: '1',
27
- title: 'New post',
28
- body: 'A body!!!',
29
- subject: 'New post',
30
32
  links: {
31
33
  self: 'http://example.com/posts/1',
34
+ },
35
+ attributes: {
36
+ title: 'New post',
37
+ body: 'A body!!!',
38
+ subject: 'New post'
39
+ },
40
+ relationships: {
32
41
  section: {
33
- self: 'http://example.com/posts/1/links/section',
34
- related: 'http://example.com/posts/1/section',
35
- linkage: nil
42
+ links: {
43
+ self: 'http://example.com/posts/1/relationships/section',
44
+ related: 'http://example.com/posts/1/section'
45
+ },
46
+ data: nil
36
47
  },
37
48
  author: {
38
- self: 'http://example.com/posts/1/links/author',
39
- related: 'http://example.com/posts/1/author',
40
- linkage: {
49
+ links: {
50
+ self: 'http://example.com/posts/1/relationships/author',
51
+ related: 'http://example.com/posts/1/author'
52
+ },
53
+ data: {
41
54
  type: 'people',
42
55
  id: '1'
43
56
  }
44
57
  },
45
58
  tags: {
46
- self: 'http://example.com/posts/1/links/tags',
47
- related: 'http://example.com/posts/1/tags'
59
+ links: {
60
+ self: 'http://example.com/posts/1/relationships/tags',
61
+ related: 'http://example.com/posts/1/tags'
62
+ }
48
63
  },
49
64
  comments: {
50
- self: 'http://example.com/posts/1/links/comments',
51
- related: 'http://example.com/posts/1/comments'
65
+ links: {
66
+ self: 'http://example.com/posts/1/relationships/comments',
67
+ related: 'http://example.com/posts/1/comments'
68
+ }
52
69
  }
53
70
  }
54
71
  }
55
72
  },
56
- JSONAPI::ResourceSerializer.new(PostResource,
57
- base_url: 'http://example.com').serialize_to_hash(PostResource.new(@post))
73
+ serialized
58
74
  )
59
75
  end
60
76
 
@@ -73,27 +89,37 @@ class SerializerTest < ActionDispatch::IntegrationTest
73
89
  data: {
74
90
  type: 'posts',
75
91
  id: '1',
76
- title: 'New post',
77
- body: 'A body!!!',
78
- subject: 'New post',
79
92
  links: {
80
- self: 'http://example.com/api/v1/posts/1',
93
+ self: 'http://example.com/api/v1/posts/1'
94
+ },
95
+ attributes: {
96
+ title: 'New post',
97
+ body: 'A body!!!',
98
+ subject: 'New post'
99
+ },
100
+ relationships: {
81
101
  section: {
82
- self: 'http://example.com/api/v1/posts/1/links/section',
83
- related: 'http://example.com/api/v1/posts/1/section',
84
- linkage: nil
102
+ links:{
103
+ self: 'http://example.com/api/v1/posts/1/relationships/section',
104
+ related: 'http://example.com/api/v1/posts/1/section'
105
+ },
106
+ data: nil
85
107
  },
86
108
  writer: {
87
- self: 'http://example.com/api/v1/posts/1/links/writer',
88
- related: 'http://example.com/api/v1/posts/1/writer',
89
- linkage: {
109
+ links:{
110
+ self: 'http://example.com/api/v1/posts/1/relationships/writer',
111
+ related: 'http://example.com/api/v1/posts/1/writer'
112
+ },
113
+ data: {
90
114
  type: 'writers',
91
115
  id: '1'
92
116
  }
93
117
  },
94
118
  comments: {
95
- self: 'http://example.com/api/v1/posts/1/links/comments',
96
- related: 'http://example.com/api/v1/posts/1/comments'
119
+ links:{
120
+ self: 'http://example.com/api/v1/posts/1/relationships/comments',
121
+ related: 'http://example.com/api/v1/posts/1/comments'
122
+ }
97
123
  }
98
124
  }
99
125
  }
@@ -111,13 +137,19 @@ class SerializerTest < ActionDispatch::IntegrationTest
111
137
  data: {
112
138
  type: 'posts',
113
139
  id: '1',
114
- title: 'New post',
115
140
  links: {
116
- self: '/posts/1',
141
+ self: '/posts/1'
142
+ },
143
+ attributes: {
144
+ title: 'New post'
145
+ },
146
+ relationships: {
117
147
  author: {
118
- self: '/posts/1/links/author',
119
- related: '/posts/1/author',
120
- linkage: {
148
+ links: {
149
+ self: '/posts/1/relationships/author',
150
+ related: '/posts/1/author'
151
+ },
152
+ data: {
121
153
  type: 'people',
122
154
  id: '1'
123
155
  }
@@ -133,7 +165,7 @@ class SerializerTest < ActionDispatch::IntegrationTest
133
165
  def test_serializer_include
134
166
  serialized = JSONAPI::ResourceSerializer.new(
135
167
  PostResource,
136
- include: [:author]
168
+ include: ['author']
137
169
  ).serialize_to_hash(PostResource.new(@post))
138
170
 
139
171
  assert_hash_equals(
@@ -141,31 +173,43 @@ class SerializerTest < ActionDispatch::IntegrationTest
141
173
  data: {
142
174
  type: 'posts',
143
175
  id: '1',
144
- title: 'New post',
145
- body: 'A body!!!',
146
- subject: 'New post',
147
176
  links: {
148
- self: '/posts/1',
177
+ self: '/posts/1'
178
+ },
179
+ attributes: {
180
+ title: 'New post',
181
+ body: 'A body!!!',
182
+ subject: 'New post'
183
+ },
184
+ relationships: {
149
185
  section: {
150
- self: '/posts/1/links/section',
151
- related: '/posts/1/section',
152
- linkage: nil
186
+ links: {
187
+ self: '/posts/1/relationships/section',
188
+ related: '/posts/1/section'
189
+ },
190
+ data: nil
153
191
  },
154
192
  author: {
155
- self: '/posts/1/links/author',
156
- related: '/posts/1/author',
157
- linkage: {
193
+ links: {
194
+ self: '/posts/1/relationships/author',
195
+ related: '/posts/1/author'
196
+ },
197
+ data: {
158
198
  type: 'people',
159
199
  id: '1'
160
200
  }
161
201
  },
162
202
  tags: {
163
- self: '/posts/1/links/tags',
164
- related: '/posts/1/tags'
203
+ links: {
204
+ self: '/posts/1/relationships/tags',
205
+ related: '/posts/1/tags'
206
+ }
165
207
  },
166
208
  comments: {
167
- self: '/posts/1/links/comments',
168
- related: '/posts/1/comments'
209
+ links: {
210
+ self: '/posts/1/relationships/comments',
211
+ related: '/posts/1/comments'
212
+ }
169
213
  }
170
214
  }
171
215
  },
@@ -173,31 +217,43 @@ class SerializerTest < ActionDispatch::IntegrationTest
173
217
  {
174
218
  type: 'people',
175
219
  id: '1',
176
- name: 'Joe Author',
177
- email: 'joe@xyz.fake',
178
- dateJoined: '2013-08-07 16:25:00 -0400',
220
+ attributes: {
221
+ name: 'Joe Author',
222
+ email: 'joe@xyz.fake',
223
+ dateJoined: '2013-08-07 16:25:00 -0400'
224
+ },
179
225
  links: {
180
- self: '/people/1',
226
+ self: '/people/1'
227
+ },
228
+ relationships: {
181
229
  comments: {
182
- self: '/people/1/links/comments',
183
- related: '/people/1/comments'
230
+ links: {
231
+ self: '/people/1/relationships/comments',
232
+ related: '/people/1/comments'
233
+ }
184
234
  },
185
235
  posts: {
186
- self: '/people/1/links/posts',
187
- related: '/people/1/posts'
236
+ links: {
237
+ self: '/people/1/relationships/posts',
238
+ related: '/people/1/posts'
239
+ }
188
240
  },
189
241
  preferences: {
190
- self: '/people/1/links/preferences',
191
- related: '/people/1/preferences',
192
- linkage: {
242
+ links: {
243
+ self: '/people/1/relationships/preferences',
244
+ related: '/people/1/preferences'
245
+ },
246
+ data: {
193
247
  type: 'preferences',
194
248
  id: '1'
195
249
  }
196
250
  },
197
251
  hairCut: {
198
- self: "/people/1/links/hairCut",
199
- related: "/people/1/hairCut",
200
- linkage: nil
252
+ links: {
253
+ self: "/people/1/relationships/hairCut",
254
+ related: "/people/1/hairCut"
255
+ },
256
+ data: nil
201
257
  }
202
258
  }
203
259
  }
@@ -210,7 +266,7 @@ class SerializerTest < ActionDispatch::IntegrationTest
210
266
  def test_serializer_key_format
211
267
  serialized = JSONAPI::ResourceSerializer.new(
212
268
  PostResource,
213
- include: [:author],
269
+ include: ['author'],
214
270
  key_formatter: UnderscoredKeyFormatter
215
271
  ).serialize_to_hash(PostResource.new(@post))
216
272
 
@@ -219,31 +275,43 @@ class SerializerTest < ActionDispatch::IntegrationTest
219
275
  data: {
220
276
  type: 'posts',
221
277
  id: '1',
222
- title: 'New post',
223
- body: 'A body!!!',
224
- subject: 'New post',
278
+ attributes: {
279
+ title: 'New post',
280
+ body: 'A body!!!',
281
+ subject: 'New post'
282
+ },
225
283
  links: {
226
- self: '/posts/1',
284
+ self: '/posts/1'
285
+ },
286
+ relationships: {
227
287
  section: {
228
- self: '/posts/1/links/section',
229
- related: '/posts/1/section',
230
- linkage: nil
288
+ links: {
289
+ self: '/posts/1/relationships/section',
290
+ related: '/posts/1/section'
291
+ },
292
+ data: nil
231
293
  },
232
294
  author: {
233
- self: '/posts/1/links/author',
234
- related: '/posts/1/author',
235
- linkage: {
295
+ links: {
296
+ self: '/posts/1/relationships/author',
297
+ related: '/posts/1/author'
298
+ },
299
+ data: {
236
300
  type: 'people',
237
301
  id: '1'
238
302
  }
239
303
  },
240
304
  tags: {
241
- self: '/posts/1/links/tags',
242
- related: '/posts/1/tags'
305
+ links: {
306
+ self: '/posts/1/relationships/tags',
307
+ related: '/posts/1/tags'
308
+ }
243
309
  },
244
310
  comments: {
245
- self: '/posts/1/links/comments',
246
- related: '/posts/1/comments'
311
+ links: {
312
+ self: '/posts/1/relationships/comments',
313
+ related: '/posts/1/comments'
314
+ }
247
315
  }
248
316
  }
249
317
  },
@@ -251,31 +319,43 @@ class SerializerTest < ActionDispatch::IntegrationTest
251
319
  {
252
320
  type: 'people',
253
321
  id: '1',
254
- name: 'Joe Author',
255
- email: 'joe@xyz.fake',
256
- date_joined: '2013-08-07 16:25:00 -0400',
322
+ attributes: {
323
+ name: 'Joe Author',
324
+ email: 'joe@xyz.fake',
325
+ date_joined: '2013-08-07 16:25:00 -0400'
326
+ },
257
327
  links: {
258
- self: '/people/1',
328
+ self: '/people/1'
329
+ },
330
+ relationships: {
259
331
  comments: {
260
- self: '/people/1/links/comments',
261
- related: '/people/1/comments'
332
+ links: {
333
+ self: '/people/1/relationships/comments',
334
+ related: '/people/1/comments'
335
+ }
262
336
  },
263
337
  posts: {
264
- self: '/people/1/links/posts',
265
- related: '/people/1/posts'
338
+ links: {
339
+ self: '/people/1/relationships/posts',
340
+ related: '/people/1/posts'
341
+ }
266
342
  },
267
343
  preferences: {
268
- self: '/people/1/links/preferences',
269
- related: '/people/1/preferences',
270
- linkage: {
344
+ links: {
345
+ self: '/people/1/relationships/preferences',
346
+ related: '/people/1/preferences'
347
+ },
348
+ data: {
271
349
  type: 'preferences',
272
350
  id: '1'
273
351
  }
274
352
  },
275
353
  hair_cut: {
276
- self: '/people/1/links/hairCut',
277
- related: '/people/1/hairCut',
278
- linkage: nil
354
+ links: {
355
+ self: '/people/1/relationships/hairCut',
356
+ related: '/people/1/hairCut'
357
+ },
358
+ data: nil
279
359
  }
280
360
  }
281
361
  }
@@ -292,32 +372,44 @@ class SerializerTest < ActionDispatch::IntegrationTest
292
372
  data: {
293
373
  type: 'posts',
294
374
  id: '1',
295
- title: 'New post',
296
- body: 'A body!!!',
297
- subject: 'New post',
375
+ attributes: {
376
+ title: 'New post',
377
+ body: 'A body!!!',
378
+ subject: 'New post'
379
+ },
298
380
  links: {
299
- self: '/posts/1',
381
+ self: '/posts/1'
382
+ },
383
+ relationships: {
300
384
  section: {
301
- self: '/posts/1/links/section',
302
- related: '/posts/1/section',
303
- linkage: nil
385
+ links: {
386
+ self: '/posts/1/relationships/section',
387
+ related: '/posts/1/section'
388
+ },
389
+ data: nil
304
390
  },
305
391
  author: {
306
- self: '/posts/1/links/author',
307
- related: '/posts/1/author',
308
- linkage: {
392
+ links: {
393
+ self: '/posts/1/relationships/author',
394
+ related: '/posts/1/author'
395
+ },
396
+ data: {
309
397
  type: 'people',
310
398
  id: '1'
311
399
  }
312
400
  },
313
401
  tags: {
314
- self: '/posts/1/links/tags',
315
- related: '/posts/1/tags'
402
+ links: {
403
+ self: '/posts/1/relationships/tags',
404
+ related: '/posts/1/tags'
405
+ }
316
406
  },
317
407
  comments: {
318
- self: '/posts/1/links/comments',
319
- related: '/posts/1/comments',
320
- linkage: [
408
+ links: {
409
+ self: '/posts/1/relationships/comments',
410
+ related: '/posts/1/comments'
411
+ },
412
+ data: [
321
413
  {type: 'comments', id: '1'},
322
414
  {type: 'comments', id: '2'}
323
415
  ]
@@ -328,65 +420,93 @@ class SerializerTest < ActionDispatch::IntegrationTest
328
420
  {
329
421
  type: 'tags',
330
422
  id: '1',
331
- name: 'short',
423
+ attributes: {
424
+ name: 'short'
425
+ },
332
426
  links: {
333
- self: '/tags/1',
427
+ self: '/tags/1'
428
+ },
429
+ relationships: {
334
430
  posts: {
335
- self: '/tags/1/links/posts',
336
- related: '/tags/1/posts'
431
+ links: {
432
+ self: '/tags/1/relationships/posts',
433
+ related: '/tags/1/posts'
434
+ }
337
435
  }
338
436
  }
339
437
  },
340
438
  {
341
439
  type: 'tags',
342
440
  id: '2',
343
- name: 'whiny',
441
+ attributes: {
442
+ name: 'whiny'
443
+ },
344
444
  links: {
345
- self: '/tags/2',
445
+ self: '/tags/2'
446
+ },
447
+ relationships: {
346
448
  posts: {
347
- self: '/tags/2/links/posts',
348
- related: '/tags/2/posts'
449
+ links: {
450
+ self: '/tags/2/relationships/posts',
451
+ related: '/tags/2/posts'
452
+ }
349
453
  }
350
454
  }
351
455
  },
352
456
  {
353
457
  type: 'tags',
354
458
  id: '4',
355
- name: 'happy',
459
+ attributes: {
460
+ name: 'happy'
461
+ },
356
462
  links: {
357
- self: '/tags/4',
463
+ self: '/tags/4'
464
+ },
465
+ relationships: {
358
466
  posts: {
359
- self: '/tags/4/links/posts',
360
- related: '/tags/4/posts',
467
+ links: {
468
+ self: '/tags/4/relationships/posts',
469
+ related: '/tags/4/posts'
470
+ },
361
471
  }
362
472
  }
363
473
  },
364
474
  {
365
475
  type: 'comments',
366
476
  id: '1',
367
- body: 'what a dumb post',
477
+ attributes: {
478
+ body: 'what a dumb post'
479
+ },
368
480
  links: {
369
- self: '/comments/1',
481
+ self: '/comments/1'
482
+ },
483
+ relationships: {
370
484
  author: {
371
- self: '/comments/1/links/author',
372
- related: '/comments/1/author',
373
- linkage: {
485
+ links: {
486
+ self: '/comments/1/relationships/author',
487
+ related: '/comments/1/author'
488
+ },
489
+ data: {
374
490
  type: 'people',
375
491
  id: '1'
376
492
  }
377
493
  },
378
494
  post: {
379
- self: '/comments/1/links/post',
380
- related: '/comments/1/post',
381
- linkage: {
495
+ links: {
496
+ self: '/comments/1/relationships/post',
497
+ related: '/comments/1/post'
498
+ },
499
+ data: {
382
500
  type: 'posts',
383
501
  id: '1'
384
502
  }
385
503
  },
386
504
  tags: {
387
- self: '/comments/1/links/tags',
388
- related: '/comments/1/tags',
389
- linkage: [
505
+ links: {
506
+ self: '/comments/1/relationships/tags',
507
+ related: '/comments/1/tags'
508
+ },
509
+ data: [
390
510
  {type: 'tags', id: '1'},
391
511
  {type: 'tags', id: '2'}
392
512
  ]
@@ -396,29 +516,39 @@ class SerializerTest < ActionDispatch::IntegrationTest
396
516
  {
397
517
  type: 'comments',
398
518
  id: '2',
399
- body: 'i liked it',
519
+ attributes: {
520
+ body: 'i liked it'
521
+ },
400
522
  links: {
401
- self: '/comments/2',
523
+ self: '/comments/2'
524
+ },
525
+ relationships: {
402
526
  author: {
403
- self: '/comments/2/links/author',
404
- related: '/comments/2/author',
405
- linkage: {
527
+ links: {
528
+ self: '/comments/2/relationships/author',
529
+ related: '/comments/2/author'
530
+ },
531
+ data: {
406
532
  type: 'people',
407
533
  id: '2'
408
534
  }
409
535
  },
410
536
  post: {
411
- self: '/comments/2/links/post',
412
- related: '/comments/2/post',
413
- linkage: {
537
+ links: {
538
+ self: '/comments/2/relationships/post',
539
+ related: '/comments/2/post'
540
+ },
541
+ data: {
414
542
  type: 'posts',
415
543
  id: '1'
416
544
  }
417
545
  },
418
546
  tags: {
419
- self: '/comments/2/links/tags',
420
- related: '/comments/2/tags',
421
- linkage: [
547
+ links: {
548
+ self: '/comments/2/relationships/tags',
549
+ related: '/comments/2/tags'
550
+ },
551
+ data: [
422
552
  {type: 'tags', id: '1'},
423
553
  {type: 'tags', id: '4'}
424
554
  ]
@@ -428,156 +558,7 @@ class SerializerTest < ActionDispatch::IntegrationTest
428
558
  ]
429
559
  },
430
560
  JSONAPI::ResourceSerializer.new(PostResource,
431
- include: [:comments, 'comments.tags']).serialize_to_hash(PostResource.new(@post))
432
- )
433
- end
434
-
435
- def test_serializer_include_has_many_sub_objects_only
436
-
437
- assert_hash_equals(
438
- {
439
- data: {
440
- type: 'posts',
441
- id: '1',
442
- title: 'New post',
443
- body: 'A body!!!',
444
- subject: 'New post',
445
- links: {
446
- self: '/posts/1',
447
- section: {
448
- self: '/posts/1/links/section',
449
- related: '/posts/1/section',
450
- linkage: nil
451
- },
452
- author: {
453
- self: '/posts/1/links/author',
454
- related: '/posts/1/author',
455
- linkage: {
456
- type: 'people',
457
- id: '1'
458
- }
459
- },
460
- tags: {
461
- self: '/posts/1/links/tags',
462
- related: '/posts/1/tags'
463
- },
464
- comments: {
465
- self: '/posts/1/links/comments',
466
- related: '/posts/1/comments'
467
- }
468
- }
469
- },
470
- included: [
471
- {
472
- type: 'tags',
473
- id: '1',
474
- name: 'short',
475
- links: {
476
- self: '/tags/1',
477
- posts: {
478
- self: '/tags/1/links/posts',
479
- related: '/tags/1/posts'
480
- }
481
- }
482
- },
483
- {
484
- type: 'tags',
485
- id: '2',
486
- name: 'whiny',
487
- links: {
488
- self: '/tags/2',
489
- posts: {
490
- self: '/tags/2/links/posts',
491
- related: '/tags/2/posts'
492
- }
493
- }
494
- },
495
- {
496
- type: 'tags',
497
- id: '4',
498
- name: 'happy',
499
- links: {
500
- self: '/tags/4',
501
- posts: {
502
- self: '/tags/4/links/posts',
503
- related: '/tags/4/posts',
504
- }
505
- }
506
- }
507
- ]
508
- },
509
- JSONAPI::ResourceSerializer.new(PostResource, include: ['comments.tags']).serialize_to_hash(PostResource.new(@post))
510
- )
511
- end
512
-
513
- def test_serializer_include_has_one_sub_objects_only
514
-
515
- assert_hash_equals(
516
- {
517
- data: {
518
- type: 'posts',
519
- id: '1',
520
- title: 'New post',
521
- body: 'A body!!!',
522
- subject: 'New post',
523
- links: {
524
- self: '/posts/1',
525
- section: {
526
- self: '/posts/1/links/section',
527
- related: '/posts/1/section',
528
- linkage: nil
529
- },
530
- author: {
531
- self: '/posts/1/links/author',
532
- related: '/posts/1/author',
533
- linkage: {
534
- type: 'people',
535
- id: '1'
536
- }
537
- },
538
- tags: {
539
- self: '/posts/1/links/tags',
540
- related: '/posts/1/tags'
541
- },
542
- comments: {
543
- self: '/posts/1/links/comments',
544
- related: '/posts/1/comments'
545
- }
546
- }
547
- },
548
- included: [
549
- {
550
- type: 'comments',
551
- id: '1',
552
- body: 'what a dumb post',
553
- links: {
554
- self: '/comments/1',
555
- author: {
556
- self: '/comments/1/links/author',
557
- related: '/comments/1/author',
558
- linkage: {
559
- type: 'people',
560
- id: '1'
561
- }
562
- },
563
- post: {
564
- self: '/comments/1/links/post',
565
- related: '/comments/1/post',
566
- linkage: {
567
- type: 'posts',
568
- id: '1'
569
- }
570
- },
571
- tags: {
572
- self: '/comments/1/links/tags',
573
- related: '/comments/1/tags'
574
- }
575
- }
576
- }
577
- ]
578
- },
579
- JSONAPI::ResourceSerializer.new(PostResource,
580
- include: ['author.comments']).serialize_to_hash(PostResource.new(@post))
561
+ include: ['comments', 'comments.tags']).serialize_to_hash(PostResource.new(@post))
581
562
  )
582
563
  end
583
564
 
@@ -592,32 +573,44 @@ class SerializerTest < ActionDispatch::IntegrationTest
592
573
  data: {
593
574
  type: 'people',
594
575
  id: '2',
595
- name: 'Fred Reader',
596
- email: 'fred@xyz.fake',
597
- dateJoined: '2013-10-31 16:25:00 -0400',
576
+ attributes: {
577
+ name: 'Fred Reader',
578
+ email: 'fred@xyz.fake',
579
+ dateJoined: '2013-10-31 16:25:00 -0400'
580
+ },
598
581
  links: {
599
- self: '/people/2',
582
+ self: '/people/2'
583
+ },
584
+ relationships: {
600
585
  posts: {
601
- self: '/people/2/links/posts',
602
- related: '/people/2/posts'
586
+ links: {
587
+ self: '/people/2/relationships/posts',
588
+ related: '/people/2/posts'
589
+ }
603
590
  },
604
591
  comments: {
605
- self: '/people/2/links/comments',
606
- related: '/people/2/comments',
607
- linkage: [
592
+ links: {
593
+ self: '/people/2/relationships/comments',
594
+ related: '/people/2/comments'
595
+ },
596
+ data: [
608
597
  {type: 'comments', id: '2'},
609
598
  {type: 'comments', id: '3'}
610
599
  ]
611
600
  },
612
601
  preferences: {
613
- self: "/people/2/links/preferences",
614
- related: "/people/2/preferences",
615
- linkage: nil
602
+ links: {
603
+ self: "/people/2/relationships/preferences",
604
+ related: "/people/2/preferences"
605
+ },
606
+ data: nil
616
607
  },
617
608
  hairCut: {
618
- self: "/people/2/links/hairCut",
619
- related: "/people/2/hairCut",
620
- linkage: nil
609
+ links: {
610
+ self: "/people/2/relationships/hairCut",
611
+ related: "/people/2/hairCut"
612
+ },
613
+ data: nil
621
614
  }
622
615
  }
623
616
  },
@@ -625,56 +618,76 @@ class SerializerTest < ActionDispatch::IntegrationTest
625
618
  {
626
619
  type: 'comments',
627
620
  id: '2',
628
- body: 'i liked it',
621
+ attributes: {
622
+ body: 'i liked it'
623
+ },
629
624
  links: {
630
- self: '/comments/2',
625
+ self: '/comments/2'
626
+ },
627
+ relationships: {
631
628
  author: {
632
- self: '/comments/2/links/author',
633
- related: '/comments/2/author',
634
- linkage: {
629
+ links: {
630
+ self: '/comments/2/relationships/author',
631
+ related: '/comments/2/author'
632
+ },
633
+ data: {
635
634
  type: 'people',
636
635
  id: '2'
637
636
  }
638
637
  },
639
638
  post: {
640
- self: '/comments/2/links/post',
641
- related: '/comments/2/post',
642
- linkage: {
639
+ links: {
640
+ self: '/comments/2/relationships/post',
641
+ related: '/comments/2/post'
642
+ },
643
+ data: {
643
644
  type: 'posts',
644
645
  id: '1'
645
646
  }
646
647
  },
647
648
  tags: {
648
- self: '/comments/2/links/tags',
649
- related: '/comments/2/tags'
649
+ links: {
650
+ self: '/comments/2/relationships/tags',
651
+ related: '/comments/2/tags'
652
+ }
650
653
  }
651
654
  }
652
655
  },
653
656
  {
654
657
  type: 'comments',
655
658
  id: '3',
656
- body: 'Thanks man. Great post. But what is JR?',
659
+ attributes: {
660
+ body: 'Thanks man. Great post. But what is JR?'
661
+ },
657
662
  links: {
658
- self: '/comments/3',
663
+ self: '/comments/3'
664
+ },
665
+ relationships: {
659
666
  author: {
660
- self: '/comments/3/links/author',
661
- related: '/comments/3/author',
662
- linkage: {
667
+ links: {
668
+ self: '/comments/3/relationships/author',
669
+ related: '/comments/3/author'
670
+ },
671
+ data: {
663
672
  type: 'people',
664
673
  id: '2'
665
674
  }
666
675
  },
667
676
  post: {
668
- self: '/comments/3/links/post',
669
- related: '/comments/3/post',
670
- linkage: {
677
+ links: {
678
+ self: '/comments/3/relationships/post',
679
+ related: '/comments/3/post'
680
+ },
681
+ data: {
671
682
  type: 'posts',
672
683
  id: '2'
673
684
  }
674
685
  },
675
686
  tags: {
676
- self: '/comments/3/links/tags',
677
- related: '/comments/3/tags'
687
+ links: {
688
+ self: '/comments/3/relationships/tags',
689
+ related: '/comments/3/tags'
690
+ }
678
691
  }
679
692
  }
680
693
  }
@@ -697,32 +710,44 @@ class SerializerTest < ActionDispatch::IntegrationTest
697
710
  {
698
711
  type: 'posts',
699
712
  id: '1',
700
- title: 'New post',
701
- body: 'A body!!!',
702
- subject: 'New post',
713
+ attributes: {
714
+ title: 'New post',
715
+ body: 'A body!!!',
716
+ subject: 'New post'
717
+ },
703
718
  links: {
704
- self: '/posts/1',
719
+ self: '/posts/1'
720
+ },
721
+ relationships: {
705
722
  section: {
706
- self: '/posts/1/links/section',
707
- related: '/posts/1/section',
708
- linkage: nil
723
+ links: {
724
+ self: '/posts/1/relationships/section',
725
+ related: '/posts/1/section'
726
+ },
727
+ data: nil
709
728
  },
710
729
  author: {
711
- self: '/posts/1/links/author',
712
- related: '/posts/1/author',
713
- linkage: {
730
+ links: {
731
+ self: '/posts/1/relationships/author',
732
+ related: '/posts/1/author'
733
+ },
734
+ data: {
714
735
  type: 'people',
715
736
  id: '1'
716
737
  }
717
738
  },
718
739
  tags: {
719
- self: '/posts/1/links/tags',
720
- related: '/posts/1/tags'
740
+ links: {
741
+ self: '/posts/1/relationships/tags',
742
+ related: '/posts/1/tags'
743
+ }
721
744
  },
722
745
  comments: {
723
- self: '/posts/1/links/comments',
724
- related: '/posts/1/comments',
725
- linkage: [
746
+ links: {
747
+ self: '/posts/1/relationships/comments',
748
+ related: '/posts/1/comments'
749
+ },
750
+ data: [
726
751
  {type: 'comments', id: '1'},
727
752
  {type: 'comments', id: '2'}
728
753
  ]
@@ -732,35 +757,47 @@ class SerializerTest < ActionDispatch::IntegrationTest
732
757
  {
733
758
  type: 'posts',
734
759
  id: '2',
735
- title: 'JR Solves your serialization woes!',
736
- body: 'Use JR',
737
- subject: 'JR Solves your serialization woes!',
760
+ attributes: {
761
+ title: 'JR Solves your serialization woes!',
762
+ body: 'Use JR',
763
+ subject: 'JR Solves your serialization woes!'
764
+ },
738
765
  links: {
739
- self: '/posts/2',
766
+ self: '/posts/2'
767
+ },
768
+ relationships: {
740
769
  section: {
741
- self: '/posts/2/links/section',
742
- related: '/posts/2/section',
743
- linkage: {
770
+ links: {
771
+ self: '/posts/2/relationships/section',
772
+ related: '/posts/2/section'
773
+ },
774
+ data: {
744
775
  type: 'sections',
745
776
  id: '2'
746
777
  }
747
778
  },
748
779
  author: {
749
- self: '/posts/2/links/author',
750
- related: '/posts/2/author',
751
- linkage: {
780
+ links: {
781
+ self: '/posts/2/relationships/author',
782
+ related: '/posts/2/author'
783
+ },
784
+ data: {
752
785
  type: 'people',
753
786
  id: '1'
754
787
  }
755
788
  },
756
789
  tags: {
757
- self: '/posts/2/links/tags',
758
- related: '/posts/2/tags'
790
+ links: {
791
+ self: '/posts/2/relationships/tags',
792
+ related: '/posts/2/tags'
793
+ }
759
794
  },
760
795
  comments: {
761
- self: '/posts/2/links/comments',
762
- related: '/posts/2/comments',
763
- linkage: [
796
+ links: {
797
+ self: '/posts/2/relationships/comments',
798
+ related: '/posts/2/comments'
799
+ },
800
+ data: [
764
801
  {type: 'comments', id: '3'}
765
802
  ]
766
803
  }
@@ -771,77 +808,111 @@ class SerializerTest < ActionDispatch::IntegrationTest
771
808
  {
772
809
  type: 'tags',
773
810
  id: '1',
774
- name: 'short',
811
+ attributes: {
812
+ name: 'short'
813
+ },
775
814
  links: {
776
- self: '/tags/1',
815
+ self: '/tags/1'
816
+ },
817
+ relationships: {
777
818
  posts: {
778
- self: '/tags/1/links/posts',
779
- related: '/tags/1/posts'
819
+ links: {
820
+ self: '/tags/1/relationships/posts',
821
+ related: '/tags/1/posts'
822
+ }
780
823
  }
781
824
  }
782
825
  },
783
826
  {
784
827
  type: 'tags',
785
828
  id: '2',
786
- name: 'whiny',
829
+ attributes: {
830
+ name: 'whiny'
831
+ },
787
832
  links: {
788
- self: '/tags/2',
833
+ self: '/tags/2'
834
+ },
835
+ relationships: {
789
836
  posts: {
790
- self: '/tags/2/links/posts',
791
- related: '/tags/2/posts'
837
+ links: {
838
+ self: '/tags/2/relationships/posts',
839
+ related: '/tags/2/posts'
840
+ }
792
841
  }
793
842
  }
794
843
  },
795
844
  {
796
845
  type: 'tags',
797
846
  id: '4',
798
- name: 'happy',
847
+ attributes: {
848
+ name: 'happy'
849
+ },
799
850
  links: {
800
- self: '/tags/4',
851
+ self: '/tags/4'
852
+ },
853
+ relationships: {
801
854
  posts: {
802
- self: '/tags/4/links/posts',
803
- related: '/tags/4/posts',
855
+ links: {
856
+ self: '/tags/4/relationships/posts',
857
+ related: '/tags/4/posts'
858
+ }
804
859
  }
805
860
  }
806
861
  },
807
862
  {
808
863
  type: 'tags',
809
864
  id: '5',
810
- name: 'JR',
865
+ attributes: {
866
+ name: 'JR'
867
+ },
811
868
  links: {
812
- self: '/tags/5',
869
+ self: '/tags/5'
870
+ },
871
+ relationships: {
813
872
  posts: {
814
- self: '/tags/5/links/posts',
815
- related: '/tags/5/posts',
873
+ links: {
874
+ self: '/tags/5/relationships/posts',
875
+ related: '/tags/5/posts'
876
+ }
816
877
  }
817
878
  }
818
879
  },
819
880
  {
820
881
  type: 'comments',
821
882
  id: '1',
822
- body: 'what a dumb post',
883
+ attributes: {
884
+ body: 'what a dumb post'
885
+ },
823
886
  links: {
824
- self: '/comments/1',
887
+ self: '/comments/1'
888
+ },
889
+ relationships: {
825
890
  author: {
826
- self: '/comments/1/links/author',
827
- related: '/comments/1/author',
828
- linkage: {
891
+ links: {
892
+ self: '/comments/1/relationships/author',
893
+ related: '/comments/1/author'
894
+ },
895
+ data: {
829
896
  type: 'people',
830
897
  id: '1'
831
898
  }
832
899
  },
833
900
  post: {
834
- self: '/comments/1/links/post',
835
- related: '/comments/1/post',
836
- linkage: {
901
+ links: {
902
+ self: '/comments/1/relationships/post',
903
+ related: '/comments/1/post'
904
+ },
905
+ data: {
837
906
  type: 'posts',
838
907
  id: '1'
839
908
  }
840
909
  },
841
910
  tags: {
842
- self: '/comments/1/links/tags',
843
- related: '/comments/1/tags',
844
- linkage: [
911
+ links: {
912
+ self: '/comments/1/relationships/tags',
913
+ related: '/comments/1/tags'
914
+ },
915
+ data: [
845
916
  {type: 'tags', id: '1'},
846
917
  {type: 'tags', id: '2'}
847
918
  ]
@@ -851,29 +922,39 @@ class SerializerTest < ActionDispatch::IntegrationTest
851
922
  {
852
923
  type: 'comments',
853
924
  id: '2',
854
- body: 'i liked it',
925
+ attributes: {
926
+ body: 'i liked it'
927
+ },
855
928
  links: {
856
- self: '/comments/2',
929
+ self: '/comments/2'
930
+ },
931
+ relationships: {
857
932
  author: {
858
- self: '/comments/2/links/author',
859
- related: '/comments/2/author',
860
- linkage: {
933
+ links: {
934
+ self: '/comments/2/relationships/author',
935
+ related: '/comments/2/author'
936
+ },
937
+ data: {
861
938
  type: 'people',
862
939
  id: '2'
863
940
  }
864
941
  },
865
942
  post: {
866
- self: '/comments/2/links/post',
867
- related: '/comments/2/post',
868
- linkage: {
943
+ links: {
944
+ self: '/comments/2/relationships/post',
945
+ related: '/comments/2/post'
946
+ },
947
+ data: {
869
948
  type: 'posts',
870
949
  id: '1'
871
950
  }
872
951
  },
873
952
  tags: {
874
- self: '/comments/2/links/tags',
875
- related: '/comments/2/tags',
876
- linkage: [
953
+ links: {
954
+ self: '/comments/2/relationships/tags',
955
+ related: '/comments/2/tags'
956
+ },
957
+ data: [
877
958
  {type: 'tags', id: '4'},
878
959
  {type: 'tags', id: '1'}
879
960
  ]
@@ -883,29 +964,39 @@ class SerializerTest < ActionDispatch::IntegrationTest
883
964
  {
884
965
  type: 'comments',
885
966
  id: '3',
886
- body: 'Thanks man. Great post. But what is JR?',
967
+ attributes: {
968
+ body: 'Thanks man. Great post. But what is JR?'
969
+ },
887
970
  links: {
888
- self: '/comments/3',
971
+ self: '/comments/3'
972
+ },
973
+ relationships: {
889
974
  author: {
890
- self: '/comments/3/links/author',
891
- related: '/comments/3/author',
892
- linkage: {
975
+ links: {
976
+ self: '/comments/3/relationships/author',
977
+ related: '/comments/3/author'
978
+ },
979
+ data: {
893
980
  type: 'people',
894
981
  id: '2'
895
982
  }
896
983
  },
897
984
  post: {
898
- self: '/comments/3/links/post',
899
- related: '/comments/3/post',
900
- linkage: {
985
+ links: {
986
+ self: '/comments/3/relationships/post',
987
+ related: '/comments/3/post'
988
+ },
989
+ data: {
901
990
  type: 'posts',
902
991
  id: '2'
903
992
  }
904
993
  },
905
994
  tags: {
906
- self: '/comments/3/links/tags',
907
- related: '/comments/3/tags',
908
- linkage: [
995
+ links: {
996
+ self: '/comments/3/relationships/tags',
997
+ related: '/comments/3/tags'
998
+ },
999
+ data: [
909
1000
  {type: 'tags', id: '5'}
910
1001
  ]
911
1002
  }
@@ -931,33 +1022,21 @@ class SerializerTest < ActionDispatch::IntegrationTest
931
1022
  {
932
1023
  type: 'posts',
933
1024
  id: '1',
934
- title: 'New post',
1025
+ attributes: {
1026
+ title: 'New post'
1027
+ },
935
1028
  links: {
936
- self: '/posts/1',
937
- author: {
938
- self: '/posts/1/links/author',
939
- related: '/posts/1/author',
940
- linkage: {
941
- type: 'people',
942
- id: '1'
943
- }
944
- }
1029
+ self: '/posts/1'
945
1030
  }
946
1031
  },
947
1032
  {
948
1033
  type: 'posts',
949
1034
  id: '2',
950
- title: 'JR Solves your serialization woes!',
1035
+ attributes: {
1036
+ title: 'JR Solves your serialization woes!'
1037
+ },
951
1038
  links: {
952
- self: '/posts/2',
953
- author: {
954
- self: '/posts/2/links/author',
955
- related: '/posts/2/author',
956
- linkage: {
957
- type: 'people',
958
- id: '1'
959
- }
960
- }
1039
+ self: '/posts/2'
961
1040
  }
962
1041
  }
963
1042
  ],
@@ -965,35 +1044,37 @@ class SerializerTest < ActionDispatch::IntegrationTest
965
1044
  {
966
1045
  type: 'posts',
967
1046
  id: '11',
968
- title: 'JR How To',
1047
+ attributes: {
1048
+ title: 'JR How To'
1049
+ },
969
1050
  links: {
970
- self: '/posts/11',
971
- author: {
972
- self: '/posts/11/links/author',
973
- related: '/posts/11/author',
974
- linkage: {
975
- type: 'people',
976
- id: '1'
977
- }
978
- }
1051
+ self: '/posts/11'
979
1052
  }
980
1053
  },
981
1054
  {
982
1055
  type: 'people',
983
1056
  id: '1',
984
- email: 'joe@xyz.fake',
1057
+ attributes: {
1058
+ email: 'joe@xyz.fake'
1059
+ },
985
1060
  links: {
986
- self: '/people/1',
1061
+ self: '/people/1'
1062
+ },
1063
+ relationships: {
987
1064
  comments: {
988
- self: '/people/1/links/comments',
989
- related: '/people/1/comments'
1065
+ links: {
1066
+ self: '/people/1/relationships/comments',
1067
+ related: '/people/1/comments'
1068
+ }
990
1069
  }
991
1070
  }
992
1071
  },
993
1072
  {
994
1073
  id: '1',
995
1074
  type: 'tags',
996
- name: 'short',
1075
+ attributes: {
1076
+ name: 'short'
1077
+ },
997
1078
  links: {
998
1079
  self: '/tags/1'
999
1080
  }
@@ -1001,7 +1082,9 @@ class SerializerTest < ActionDispatch::IntegrationTest
1001
1082
  {
1002
1083
  id: '2',
1003
1084
  type: 'tags',
1004
- name: 'whiny',
1085
+ attributes: {
1086
+ name: 'whiny'
1087
+ },
1005
1088
  links: {
1006
1089
  self: '/tags/2'
1007
1090
  }
@@ -1009,7 +1092,9 @@ class SerializerTest < ActionDispatch::IntegrationTest
1009
1092
  {
1010
1093
  id: '4',
1011
1094
  type: 'tags',
1012
- name: 'happy',
1095
+ attributes: {
1096
+ name: 'happy'
1097
+ },
1013
1098
  links: {
1014
1099
  self: '/tags/4'
1015
1100
  }
@@ -1017,7 +1102,9 @@ class SerializerTest < ActionDispatch::IntegrationTest
1017
1102
  {
1018
1103
  id: '5',
1019
1104
  type: 'tags',
1020
- name: 'JR',
1105
+ attributes: {
1106
+ name: 'JR'
1107
+ },
1021
1108
  links: {
1022
1109
  self: '/tags/5'
1023
1110
  }
@@ -1025,13 +1112,19 @@ class SerializerTest < ActionDispatch::IntegrationTest
1025
1112
  {
1026
1113
  type: 'comments',
1027
1114
  id: '1',
1028
- body: 'what a dumb post',
1115
+ attributes: {
1116
+ body: 'what a dumb post'
1117
+ },
1029
1118
  links: {
1030
- self: '/comments/1',
1119
+ self: '/comments/1'
1120
+ },
1121
+ relationships: {
1031
1122
  post: {
1032
- self: '/comments/1/links/post',
1033
- related: '/comments/1/post',
1034
- linkage: {
1123
+ links: {
1124
+ self: '/comments/1/relationships/post',
1125
+ related: '/comments/1/post'
1126
+ },
1127
+ data: {
1035
1128
  type: 'posts',
1036
1129
  id: '1'
1037
1130
  }
@@ -1041,13 +1134,19 @@ class SerializerTest < ActionDispatch::IntegrationTest
1041
1134
  {
1042
1135
  type: 'comments',
1043
1136
  id: '2',
1044
- body: 'i liked it',
1137
+ attributes: {
1138
+ body: 'i liked it'
1139
+ },
1045
1140
  links: {
1046
- self: '/comments/2',
1141
+ self: '/comments/2'
1142
+ },
1143
+ relationships: {
1047
1144
  post: {
1048
- self: '/comments/2/links/post',
1049
- related: '/comments/2/post',
1050
- linkage: {
1145
+ links: {
1146
+ self: '/comments/2/relationships/post',
1147
+ related: '/comments/2/post'
1148
+ },
1149
+ data: {
1051
1150
  type: 'posts',
1052
1151
  id: '1'
1053
1152
  }
@@ -1057,13 +1156,19 @@ class SerializerTest < ActionDispatch::IntegrationTest
1057
1156
  {
1058
1157
  type: 'comments',
1059
1158
  id: '3',
1060
- body: 'Thanks man. Great post. But what is JR?',
1159
+ attributes: {
1160
+ body: 'Thanks man. Great post. But what is JR?'
1161
+ },
1061
1162
  links: {
1062
- self: '/comments/3',
1163
+ self: '/comments/3'
1164
+ },
1165
+ relationships: {
1063
1166
  post: {
1064
- self: '/comments/3/links/post',
1065
- related: '/comments/3/post',
1066
- linkage: {
1167
+ links: {
1168
+ self: '/comments/3/relationships/post',
1169
+ related: '/comments/3/post'
1170
+ },
1171
+ data: {
1067
1172
  type: 'posts',
1068
1173
  id: '2'
1069
1174
  }
@@ -1076,7 +1181,7 @@ class SerializerTest < ActionDispatch::IntegrationTest
1076
1181
  include: ['comments', 'author', 'comments.tags', 'author.posts'],
1077
1182
  fields: {
1078
1183
  people: [:id, :email, :comments],
1079
- posts: [:id, :title, :author],
1184
+ posts: [:id, :title],
1080
1185
  tags: [:name],
1081
1186
  comments: [:id, :body, :post]
1082
1187
  }).serialize_to_hash(posts)
@@ -1092,22 +1197,30 @@ class SerializerTest < ActionDispatch::IntegrationTest
1092
1197
  data: {
1093
1198
  type: 'expenseEntries',
1094
1199
  id: '1',
1095
- transactionDate: '04/15/2014',
1096
- cost: 12.05,
1200
+ attributes: {
1201
+ transactionDate: '04/15/2014',
1202
+ cost: 12.05
1203
+ },
1097
1204
  links: {
1098
- self: '/expenseEntries/1',
1205
+ self: '/expenseEntries/1'
1206
+ },
1207
+ relationships: {
1099
1208
  isoCurrency: {
1100
- self: '/expenseEntries/1/links/isoCurrency',
1101
- related: '/expenseEntries/1/isoCurrency',
1102
- linkage: {
1209
+ links: {
1210
+ self: '/expenseEntries/1/relationships/isoCurrency',
1211
+ related: '/expenseEntries/1/isoCurrency'
1212
+ },
1213
+ data: {
1103
1214
  type: 'isoCurrencies',
1104
1215
  id: 'USD'
1105
1216
  }
1106
1217
  },
1107
1218
  employee: {
1108
- self: '/expenseEntries/1/links/employee',
1109
- related: '/expenseEntries/1/employee',
1110
- linkage: {
1219
+ links: {
1220
+ self: '/expenseEntries/1/relationships/employee',
1221
+ related: '/expenseEntries/1/employee'
1222
+ },
1223
+ data: {
1111
1224
  type: 'people',
1112
1225
  id: '3'
1113
1226
  }
@@ -1118,9 +1231,11 @@ class SerializerTest < ActionDispatch::IntegrationTest
1118
1231
  {
1119
1232
  type: 'isoCurrencies',
1120
1233
  id: 'USD',
1121
- countryName: 'United States',
1122
- name: 'United States Dollar',
1123
- minorUnit: 'cent',
1234
+ attributes: {
1235
+ countryName: 'United States',
1236
+ name: 'United States Dollar',
1237
+ minorUnit: 'cent'
1238
+ },
1124
1239
  links: {
1125
1240
  self: '/isoCurrencies/USD'
1126
1241
  }
@@ -1128,9 +1243,11 @@ class SerializerTest < ActionDispatch::IntegrationTest
1128
1243
  {
1129
1244
  type: 'people',
1130
1245
  id: '3',
1131
- email: 'lazy@xyz.fake',
1132
- name: 'Lazy Author',
1133
- dateJoined: '2013-10-31 17:25:00 -0400',
1246
+ attributes: {
1247
+ email: 'lazy@xyz.fake',
1248
+ name: 'Lazy Author',
1249
+ dateJoined: '2013-10-31 17:25:00 -0400'
1250
+ },
1134
1251
  links: {
1135
1252
  self: '/people/3',
1136
1253
  }
@@ -1138,7 +1255,7 @@ class SerializerTest < ActionDispatch::IntegrationTest
1138
1255
  ]
1139
1256
  },
1140
1257
  JSONAPI::ResourceSerializer.new(ExpenseEntryResource,
1141
- include: ['isoCurrency', 'employee'],
1258
+ include: ['iso_currency', 'employee'],
1142
1259
  fields: {people: [:id, :name, :email, :date_joined]}).serialize_to_hash(
1143
1260
  ExpenseEntryResource.new(@expense_entry))
1144
1261
  )
@@ -1153,22 +1270,32 @@ class SerializerTest < ActionDispatch::IntegrationTest
1153
1270
  data: {
1154
1271
  type: 'planets',
1155
1272
  id: '8',
1156
- name: 'Beta W',
1157
- description: 'Newly discovered Planet W',
1273
+ attributes: {
1274
+ name: 'Beta W',
1275
+ description: 'Newly discovered Planet W'
1276
+ },
1158
1277
  links: {
1159
- self: '/planets/8',
1278
+ self: '/planets/8'
1279
+ },
1280
+ relationships: {
1160
1281
  planetType: {
1161
- self: '/planets/8/links/planetType',
1162
- related: '/planets/8/planetType',
1163
- linkage: nil
1282
+ links: {
1283
+ self: '/planets/8/relationships/planetType',
1284
+ related: '/planets/8/planetType'
1285
+ },
1286
+ data: nil
1164
1287
  },
1165
1288
  tags: {
1166
- self: '/planets/8/links/tags',
1167
- related: '/planets/8/tags'
1289
+ links: {
1290
+ self: '/planets/8/relationships/tags',
1291
+ related: '/planets/8/tags'
1292
+ }
1168
1293
  },
1169
1294
  moons: {
1170
- self: '/planets/8/links/moons',
1171
- related: '/planets/8/moons'
1295
+ links: {
1296
+ self: '/planets/8/relationships/moons',
1297
+ related: '/planets/8/moons'
1298
+ }
1172
1299
  }
1173
1300
  }
1174
1301
  }
@@ -1190,47 +1317,67 @@ class SerializerTest < ActionDispatch::IntegrationTest
1190
1317
  data: [{
1191
1318
  type: 'planets',
1192
1319
  id: '7',
1193
- name: 'Beta X',
1194
- description: 'Newly discovered Planet Z',
1320
+ attributes: {
1321
+ name: 'Beta X',
1322
+ description: 'Newly discovered Planet Z'
1323
+ },
1195
1324
  links: {
1196
- self: '/planets/7',
1325
+ self: '/planets/7'
1326
+ },
1327
+ relationships: {
1197
1328
  planetType: {
1198
- self: '/planets/7/links/planetType',
1199
- related: '/planets/7/planetType',
1200
- linkage: {
1329
+ links: {
1330
+ self: '/planets/7/relationships/planetType',
1331
+ related: '/planets/7/planetType'
1332
+ },
1333
+ data: {
1201
1334
  type: 'planetTypes',
1202
1335
  id: '5'
1203
1336
  }
1204
1337
  },
1205
1338
  tags: {
1206
- self: '/planets/7/links/tags',
1207
- related: '/planets/7/tags'
1339
+ links: {
1340
+ self: '/planets/7/relationships/tags',
1341
+ related: '/planets/7/tags'
1342
+ }
1208
1343
  },
1209
1344
  moons: {
1210
- self: '/planets/7/links/moons',
1211
- related: '/planets/7/moons'
1345
+ links: {
1346
+ self: '/planets/7/relationships/moons',
1347
+ related: '/planets/7/moons'
1348
+ }
1212
1349
  }
1213
1350
  }
1214
1351
  },
1215
1352
  {
1216
1353
  type: 'planets',
1217
1354
  id: '8',
1218
- name: 'Beta W',
1219
- description: 'Newly discovered Planet W',
1355
+ attributes: {
1356
+ name: 'Beta W',
1357
+ description: 'Newly discovered Planet W'
1358
+ },
1220
1359
  links: {
1221
- self: '/planets/8',
1360
+ self: '/planets/8'
1361
+ },
1362
+ relationships: {
1222
1363
  planetType: {
1223
- self: '/planets/8/links/planetType',
1224
- related: '/planets/8/planetType',
1225
- linkage: nil
1364
+ links: {
1365
+ self: '/planets/8/relationships/planetType',
1366
+ related: '/planets/8/planetType'
1367
+ },
1368
+ data: nil
1226
1369
  },
1227
1370
  tags: {
1228
- self: '/planets/8/links/tags',
1229
- related: '/planets/8/tags'
1371
+ links: {
1372
+ self: '/planets/8/relationships/tags',
1373
+ related: '/planets/8/tags'
1374
+ }
1230
1375
  },
1231
1376
  moons: {
1232
- self: '/planets/8/links/moons',
1233
- related: '/planets/8/moons'
1377
+ links: {
1378
+ self: '/planets/8/relationships/moons',
1379
+ related: '/planets/8/moons'
1380
+ }
1234
1381
  }
1235
1382
  }
1236
1383
  }
@@ -1239,7 +1386,9 @@ class SerializerTest < ActionDispatch::IntegrationTest
1239
1386
  {
1240
1387
  type: 'planetTypes',
1241
1388
  id: '5',
1242
- name: 'unknown',
1389
+ attributes: {
1390
+ name: 'unknown'
1391
+ },
1243
1392
  links: {
1244
1393
  self: '/planetTypes/5'
1245
1394
  }
@@ -1258,17 +1407,25 @@ class SerializerTest < ActionDispatch::IntegrationTest
1258
1407
  data: {
1259
1408
  type: 'preferences',
1260
1409
  id: '1',
1261
- advanced_mode: false,
1410
+ attributes: {
1411
+ advanced_mode: false
1412
+ },
1262
1413
  links: {
1263
- self: '/preferences/1',
1414
+ self: '/preferences/1'
1415
+ },
1416
+ relationships: {
1264
1417
  author: {
1265
- self: '/preferences/1/links/author',
1266
- related: '/preferences/1/author',
1267
- linkage: nil
1418
+ links: {
1419
+ self: '/preferences/1/relationships/author',
1420
+ related: '/preferences/1/author'
1421
+ },
1422
+ data: nil
1268
1423
  },
1269
1424
  friends: {
1270
- self: '/preferences/1/links/friends',
1271
- related: '/preferences/1/friends'
1425
+ links: {
1426
+ self: '/preferences/1/relationships/friends',
1427
+ related: '/preferences/1/friends'
1428
+ }
1272
1429
  }
1273
1430
  }
1274
1431
  }
@@ -1287,15 +1444,17 @@ class SerializerTest < ActionDispatch::IntegrationTest
1287
1444
  data: {
1288
1445
  type: 'facts',
1289
1446
  id: '1',
1290
- spouse_name: 'Jane Author',
1291
- bio: 'First man to run across Antartica.',
1292
- quality_rating: 23.89/45.6,
1293
- salary: BigDecimal('47000.56', 30),
1294
- date_time_joined: DateTime.parse('2013-08-07 20:25:00 UTC +00:00'),
1295
- birthday: Date.parse('1965-06-30'),
1296
- bedtime: Time.parse('2000-01-01 20:00:00 UTC +00:00'), #DB seems to set the date to 2001-01-01 for time types
1297
- photo: "abc",
1298
- cool: false,
1447
+ attributes: {
1448
+ spouse_name: 'Jane Author',
1449
+ bio: 'First man to run across Antartica.',
1450
+ quality_rating: 23.89/45.6,
1451
+ salary: BigDecimal('47000.56', 30),
1452
+ date_time_joined: DateTime.parse('2013-08-07 20:25:00 UTC +00:00'),
1453
+ birthday: Date.parse('1965-06-30'),
1454
+ bedtime: Time.parse('2000-01-01 20:00:00 UTC +00:00'), #DB seems to set the date to 2001-01-01 for time types
1455
+ photo: "abc",
1456
+ cool: false
1457
+ },
1299
1458
  links: {
1300
1459
  self: '/facts/1'
1301
1460
  }