jsonapi-resources 0.2.0 → 0.3.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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