jsonapi-resources 0.2.0 → 0.3.0.pre1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.travis.yml +5 -2
  4. data/Gemfile +3 -1
  5. data/README.md +52 -13
  6. data/jsonapi-resources.gemspec +1 -1
  7. data/lib/jsonapi-resources.rb +1 -0
  8. data/lib/jsonapi/association.rb +1 -9
  9. data/lib/jsonapi/error_codes.rb +1 -0
  10. data/lib/jsonapi/exceptions.rb +9 -5
  11. data/lib/jsonapi/formatter.rb +9 -18
  12. data/lib/jsonapi/paginator.rb +4 -15
  13. data/lib/jsonapi/request.rb +26 -42
  14. data/lib/jsonapi/resource.rb +35 -45
  15. data/lib/jsonapi/resource_controller.rb +6 -32
  16. data/lib/jsonapi/resource_serializer.rb +62 -33
  17. data/lib/jsonapi/resources/version.rb +1 -1
  18. data/lib/jsonapi/routing_ext.rb +4 -4
  19. data/test/config/database.yml +2 -1
  20. data/test/controllers/controller_test.rb +200 -160
  21. data/test/fixtures/active_record.rb +44 -201
  22. data/test/fixtures/book_comments.yml +11 -0
  23. data/test/fixtures/books.yml +6 -0
  24. data/test/fixtures/comments.yml +17 -0
  25. data/test/fixtures/comments_tags.yml +20 -0
  26. data/test/fixtures/expense_entries.yml +13 -0
  27. data/test/fixtures/facts.yml +11 -0
  28. data/test/fixtures/iso_currencies.yml +17 -0
  29. data/test/fixtures/people.yml +24 -0
  30. data/test/fixtures/posts.yml +96 -0
  31. data/test/fixtures/posts_tags.yml +59 -0
  32. data/test/fixtures/preferences.yml +18 -0
  33. data/test/fixtures/sections.yml +8 -0
  34. data/test/fixtures/tags.yml +39 -0
  35. data/test/helpers/hash_helpers.rb +0 -7
  36. data/test/integration/requests/request_test.rb +86 -28
  37. data/test/integration/routes/routes_test.rb +14 -25
  38. data/test/test_helper.rb +41 -17
  39. data/test/unit/jsonapi_request/jsonapi_request_test.rb +152 -0
  40. data/test/unit/operation/operations_processor_test.rb +13 -2
  41. data/test/unit/resource/resource_test.rb +68 -13
  42. data/test/unit/serializer/serializer_test.rb +328 -220
  43. metadata +33 -6
  44. data/lib/jsonapi/resource_for.rb +0 -29
@@ -0,0 +1,24 @@
1
+ a:
2
+ id: 1
3
+ name: Joe Author
4
+ email: joe@xyz.fake
5
+ date_joined: <%= DateTime.parse('2013-08-07 20:25:00 UTC +00:00') %>
6
+ preferences_id: 1
7
+
8
+ b:
9
+ id: 2
10
+ name: Fred Reader
11
+ email: fred@xyz.fake
12
+ date_joined: <%= DateTime.parse('2013-10-31 20:25:00 UTC +00:00') %>
13
+
14
+ c:
15
+ id: 3
16
+ name: Lazy Author
17
+ email: lazy@xyz.fake
18
+ date_joined: <%= DateTime.parse('2013-10-31 21:25:00 UTC +00:00') %>
19
+
20
+ d:
21
+ id: 4
22
+ name: Tag Crazy Author
23
+ email: taggy@xyz.fake
24
+ date_joined: <%= DateTime.parse('2013-11-30 4:20:00 UTC +00:00') %>
@@ -0,0 +1,96 @@
1
+ post_1:
2
+ id: 1
3
+ title: New post
4
+ body: A body!!!
5
+ author_id: 1
6
+
7
+ post_2:
8
+ id: 2
9
+ title: JR Solves your serialization woes!
10
+ body: Use JR
11
+ author_id: 1
12
+ section_id: 2
13
+
14
+ post_3:
15
+ id: 3
16
+ title: Update This Later
17
+ body: AAAA
18
+ author_id: 3
19
+
20
+ post_4:
21
+ id: 4
22
+ title: Delete This Later - Single
23
+ body: AAAA
24
+ author_id: 3
25
+
26
+ post_5:
27
+ id: 5
28
+ title: Delete This Later - Multiple1
29
+ body: AAAA
30
+ author_id: 3
31
+
32
+ post_6:
33
+ id: 6
34
+ title: Delete This Later - Multiple2
35
+ body: AAAA
36
+ author_id: 3
37
+
38
+ post_7:
39
+ id: 7
40
+ title: Delete This Later - Single2
41
+ body: AAAA
42
+ author_id: 3
43
+
44
+ post_8:
45
+ id: 8
46
+ title: Delete This Later - Multiple2-1
47
+ body: AAAA
48
+ author_id: 3
49
+
50
+ post_9:
51
+ id: 9
52
+ title: Delete This Later - Multiple2-2
53
+ body: AAAA
54
+ author_id: 3
55
+
56
+ post_10:
57
+ id: 10
58
+ title: Update This Later - Multiple
59
+ body: AAAA
60
+ author_id: 3
61
+
62
+ post_11:
63
+ id: 11
64
+ title: JR How To
65
+ body: Use JR to write API apps
66
+ author_id: 1
67
+
68
+ post_12:
69
+ id: 12
70
+ title: Tagged up post 1
71
+ body: AAAA
72
+ author_id: 4
73
+
74
+ post_13:
75
+ id: 13
76
+ title: Tagged up post 2
77
+ body: BBBB
78
+ author_id: 4
79
+
80
+ post_14:
81
+ id: 14
82
+ title: A First Post
83
+ body: A First Post!!!!!!!!!
84
+ author_id: 3
85
+
86
+ post_15:
87
+ id: 15
88
+ title: AAAA First Post
89
+ body: First!!!!!!!!!
90
+ author_id: 3
91
+
92
+ post_16:
93
+ id: 16
94
+ title: SDFGH
95
+ body: Not First!!!!
96
+ author_id: 3
@@ -0,0 +1,59 @@
1
+ post_1_short:
2
+ post_id: 1
3
+ tag_id: 1
4
+
5
+ post_1_whiny:
6
+ post_id: 1
7
+ tag_id: 2
8
+
9
+ post_1_grumpy:
10
+ post_id: 1
11
+ tag_id: 3
12
+
13
+ post_2_jr:
14
+ post_id: 2
15
+ tag_id: 5
16
+
17
+ post_11_jr:
18
+ post_id: 11
19
+ tag_id: 5
20
+
21
+ post_12_silly:
22
+ post_id: 12
23
+ tag_id: 6
24
+
25
+ post_12_sleepy:
26
+ post_id: 12
27
+ tag_id: 7
28
+
29
+ post_12_goofy:
30
+ post_id: 12
31
+ tag_id: 8
32
+
33
+ post_12_wacky:
34
+ post_id: 12
35
+ tag_id: 9
36
+
37
+ post_13_silly:
38
+ post_id: 13
39
+ tag_id: 6
40
+
41
+ post_13_sleepy:
42
+ post_id: 13
43
+ tag_id: 7
44
+
45
+ post_13_goofy:
46
+ post_id: 13
47
+ tag_id: 8
48
+
49
+ post_13_wacky:
50
+ post_id: 13
51
+ tag_id: 9
52
+
53
+ post_14_whiny:
54
+ post_id: 14
55
+ tag_id: 2
56
+
57
+ post_14_grumpy:
58
+ post_id: 14
59
+ tag_id: 3
@@ -0,0 +1,18 @@
1
+ a:
2
+ id: 1
3
+ person_id:
4
+ advanced_mode: false
5
+
6
+ b:
7
+ id: 2
8
+ person_id:
9
+ advanced_mode: false
10
+ c:
11
+ id: 3
12
+ person_id:
13
+ advanced_mode: false
14
+
15
+ d:
16
+ id: 4
17
+ person_id:
18
+ advanced_mode: false
@@ -0,0 +1,8 @@
1
+ javascript:
2
+ id: 1
3
+ name: javascript
4
+
5
+ ruby:
6
+ id: 2
7
+ name: ruby
8
+
@@ -0,0 +1,39 @@
1
+ short_tag:
2
+ id: 1
3
+ name: short
4
+
5
+ whiny_tag:
6
+ id: 2
7
+ name: whiny
8
+
9
+ grumpy_tag:
10
+ id: 3
11
+ name: grumpy
12
+
13
+ happy_tag:
14
+ id: 4
15
+ name: happy
16
+
17
+ jr_tag:
18
+ id: 5
19
+ name: JR
20
+
21
+ silly_tag:
22
+ id: 6
23
+ name: silly
24
+
25
+ sleepy_tag:
26
+ id: 7
27
+ name: sleepy
28
+
29
+ goofy_tag:
30
+ id: 8
31
+ name: goofy
32
+
33
+ wacky_tag:
34
+ id: 9
35
+ name: wacky
36
+
37
+ bad_tag:
38
+ id: 10
39
+ name: bad
@@ -1,15 +1,8 @@
1
1
  module Helpers
2
2
  module HashHelpers
3
- # :nocov:
4
- def assert_hash_contains(exp, act, msg = nil)
5
- msg = message(msg, '') { diff exp, act }
6
- assert(matches_hash?(exp, act), msg)
7
- end
8
-
9
3
  def assert_hash_equals(exp, act, msg = nil)
10
4
  msg = message(msg, '') { diff exp, act }
11
5
  assert(matches_hash?(exp, act, {exact: true}), msg)
12
6
  end
13
- # :nocov:
14
7
  end
15
8
  end
@@ -68,22 +68,31 @@ class RequestTest < ActionDispatch::IntegrationTest
68
68
  JSONAPI.configuration.route_format = :camelized_route
69
69
  get '/api/v4/expenseEntries/1/links/isoCurrency'
70
70
  assert_equal 200, status
71
- assert_hash_equals({'data' => {
71
+ assert_hash_equals({'links' => {
72
+ 'self' => 'http://www.example.com/api/v4/expenseEntries/1/links/isoCurrency',
73
+ 'related' => 'http://www.example.com/api/v4/expenseEntries/1/isoCurrency'
74
+ },
75
+ 'data' => {
72
76
  'type' => 'isoCurrencies',
73
- 'id' => 'USD',
74
- 'self' => 'http://www.example.com/api/v4/expenseEntries/1/links/isoCurrency',
75
- 'resource' => 'http://www.example.com/api/v4/expenseEntries/1/isoCurrency'}}, json_response)
77
+ 'id' => 'USD'
78
+ }
79
+ }, json_response)
76
80
  end
77
81
 
78
82
  def test_put_single_without_content_type
79
83
  put '/posts/3',
80
84
  {
81
85
  'data' => {
82
- 'type' => 'posts',
83
- 'id' => '3',
86
+ 'linkage' => {
87
+ 'type' => 'posts',
88
+ 'id' => '3',
89
+ },
84
90
  'title' => 'A great new Post',
85
91
  'links' => {
86
- 'tags' => {type: 'tags', ids: [3, 4]}
92
+ 'tags' => [
93
+ {type: 'tags', id: 3},
94
+ {type: 'tags', id: 4}
95
+ ]
87
96
  }
88
97
  }
89
98
  }.to_json, "CONTENT_TYPE" => "application/json"
@@ -99,7 +108,10 @@ class RequestTest < ActionDispatch::IntegrationTest
99
108
  'id' => '3',
100
109
  'title' => 'A great new Post',
101
110
  'links' => {
102
- 'tags' => {type: 'tags', ids: [3, 4]}
111
+ 'tags' => [
112
+ {type: 'tags', id: 3},
113
+ {type: 'tags', id: 4}
114
+ ]
103
115
  }
104
116
  }
105
117
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
@@ -113,7 +125,10 @@ class RequestTest < ActionDispatch::IntegrationTest
113
125
  'posts' => {
114
126
  'title' => 'A great new Post',
115
127
  'links' => {
116
- 'tags' => [3, 4]
128
+ 'tags' => [
129
+ {type: 'tags', id: 3},
130
+ {type: 'tags', id: 4}
131
+ ]
117
132
  }
118
133
  }
119
134
  }.to_json, "CONTENT_TYPE" => "application/json"
@@ -139,12 +154,19 @@ class RequestTest < ActionDispatch::IntegrationTest
139
154
 
140
155
  def test_update_association_without_content_type
141
156
  ruby = Section.find_by(name: 'ruby')
142
- put '/posts/3/links/section', { 'sections' => {type: 'sections', id: ruby.id.to_s }}.to_json
157
+ patch '/posts/3/links/section', { 'sections' => {type: 'sections', id: ruby.id.to_s }}.to_json
143
158
 
144
159
  assert_equal 415, status
145
160
  end
146
161
 
147
- def test_update_association_has_one
162
+ def test_patch_update_association_has_one
163
+ ruby = Section.find_by(name: 'ruby')
164
+ patch '/posts/3/links/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
165
+
166
+ assert_equal 204, status
167
+ end
168
+
169
+ def test_put_update_association_has_one
148
170
  ruby = Section.find_by(name: 'ruby')
149
171
  put '/posts/3/links/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
150
172
 
@@ -169,7 +191,29 @@ class RequestTest < ActionDispatch::IntegrationTest
169
191
  'id' => '3',
170
192
  'title' => 'A great new Post',
171
193
  'links' => {
172
- 'tags' => {type: 'tags', ids: [3, 4]}
194
+ 'tags' => [
195
+ {type: 'tags', id: 3},
196
+ {type: 'tags', id: 4}
197
+ ]
198
+ }
199
+ }
200
+ }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
201
+
202
+ assert_match JSONAPI::MEDIA_TYPE, headers['Content-Type']
203
+ end
204
+
205
+ def test_patch_content_type
206
+ patch '/posts/3',
207
+ {
208
+ 'data' => {
209
+ 'type' => 'posts',
210
+ 'id' => '3',
211
+ 'title' => 'A great new Post',
212
+ 'links' => {
213
+ 'tags' => [
214
+ {type: 'tags', id: 3},
215
+ {type: 'tags', id: 4}
216
+ ]
173
217
  }
174
218
  }
175
219
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
@@ -246,7 +290,7 @@ class RequestTest < ActionDispatch::IntegrationTest
246
290
  assert_equal 200, status
247
291
  assert_equal 2, json_response['data'].size
248
292
  assert_equal 'http://www.example.com/api/v2/books/1/book_comments',
249
- json_response['data'][0]['links']['book_comments']['resource']
293
+ json_response['data'][1]['links']['book_comments']['related']
250
294
  end
251
295
 
252
296
  def test_pagination_related_resources_data
@@ -255,7 +299,7 @@ class RequestTest < ActionDispatch::IntegrationTest
255
299
  get '/api/v2/books/1/book_comments?page[limit]=10'
256
300
  assert_equal 200, status
257
301
  assert_equal 10, json_response['data'].size
258
- assert_equal 'This is comment 9 on book 0.', json_response['data'][9]['body']
302
+ assert_equal 'This is comment 9 on book 1.', json_response['data'][9]['body']
259
303
  end
260
304
 
261
305
  def test_pagination_related_resources_data_includes
@@ -264,7 +308,7 @@ class RequestTest < ActionDispatch::IntegrationTest
264
308
  get '/api/v2/books/1/book_comments?page[limit]=10&include=author,book'
265
309
  assert_equal 200, status
266
310
  assert_equal 10, json_response['data'].size
267
- assert_equal 'This is comment 9 on book 0.', json_response['data'][9]['body']
311
+ assert_equal 'This is comment 9 on book 1.', json_response['data'][9]['body']
268
312
  end
269
313
 
270
314
  def test_flow_self
@@ -284,7 +328,13 @@ class RequestTest < ActionDispatch::IntegrationTest
284
328
 
285
329
  get post_1['links']['author']['self']
286
330
  assert_equal 200, status
287
- assert_hash_equals(json_response, {'data' => post_1['links']['author']})
331
+ assert_hash_equals(json_response, {
332
+ 'links' => {
333
+ 'self' => 'http://www.example.com/posts/1/links/author',
334
+ 'related' => 'http://www.example.com/posts/1/author'
335
+ },
336
+ 'data' => {type: 'people', id: '1'}
337
+ })
288
338
  end
289
339
 
290
340
  def test_flow_link_has_many_self_link
@@ -295,21 +345,26 @@ class RequestTest < ActionDispatch::IntegrationTest
295
345
  get post_1['links']['tags']['self']
296
346
  assert_equal 200, status
297
347
  assert_hash_equals(json_response,
298
- {'data' => {
299
- 'self' => 'http://www.example.com/posts/1/links/tags',
300
- 'resource' => 'http://www.example.com/posts/1/tags',
301
- 'type' => 'tags', 'ids'=>['1', '2', '3']
302
- }
348
+ {
349
+ 'links' => {
350
+ 'self' => 'http://www.example.com/posts/1/links/tags',
351
+ 'related' => 'http://www.example.com/posts/1/tags'
352
+ },
353
+ 'data' => [
354
+ {type: 'tags', id: '1'},
355
+ {type: 'tags', id: '2'},
356
+ {type: 'tags', id: '3'}
357
+ ]
303
358
  })
304
359
  end
305
360
 
306
361
  def test_flow_link_has_many_self_link_put
307
362
  get '/posts'
308
363
  assert_equal 200, status
309
- post_1 = json_response['data'][0]
364
+ post_1 = json_response['data'][4]
310
365
 
311
366
  post post_1['links']['tags']['self'],
312
- {'data' => {'type' => 'tags', 'ids' => ['5']}}.to_json,
367
+ {'data' => [{'type' => 'tags', 'id' => '10'}]}.to_json,
313
368
  "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
314
369
 
315
370
  assert_equal 204, status
@@ -317,11 +372,14 @@ class RequestTest < ActionDispatch::IntegrationTest
317
372
  get post_1['links']['tags']['self']
318
373
  assert_equal 200, status
319
374
  assert_hash_equals(json_response,
320
- {'data' => {
321
- 'self' => 'http://www.example.com/posts/1/links/tags',
322
- 'resource' => 'http://www.example.com/posts/1/tags',
323
- 'type' => 'tags', 'ids'=>['1', '2', '3', '5']
324
- }
375
+ {
376
+ 'links' => {
377
+ 'self' => 'http://www.example.com/posts/5/links/tags',
378
+ 'related' => 'http://www.example.com/posts/5/tags'
379
+ },
380
+ 'data' => [
381
+ {type: 'tags', id: '10'}
382
+ ]
325
383
  })
326
384
  end
327
385