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
@@ -3,6 +3,7 @@ require 'jsonapi-resources'
3
3
 
4
4
  ActiveSupport::Inflector.inflections(:en) do |inflect|
5
5
  inflect.uncountable 'preferences'
6
+ inflect.irregular 'numero_telefone', 'numeros_telefone'
6
7
  end
7
8
 
8
9
  ### DATABASE
@@ -156,6 +157,16 @@ ActiveRecord::Schema.define do
156
157
  create_table :hair_cuts, force: true do |t|
157
158
  t.string :style
158
159
  end
160
+
161
+ create_table :numeros_telefone, force: true do |t|
162
+ t.string :numero_telefone
163
+ t.timestamps null: false
164
+ end
165
+
166
+ create_table :categories, force: true do |t|
167
+ t.string :name
168
+ t.string :status, limit: 10
169
+ end
159
170
  end
160
171
 
161
172
  ### MODELS
@@ -211,6 +222,16 @@ class Planet < ActiveRecord::Base
211
222
  belongs_to :planet_type
212
223
 
213
224
  has_and_belongs_to_many :tags, join_table: :planets_tags
225
+
226
+ # Test model callback cancelling save
227
+ before_save :check_not_pluto
228
+
229
+ def check_not_pluto
230
+ # Pluto can't be a planet, so cancel the save
231
+ if name.downcase == 'pluto'
232
+ return false
233
+ end
234
+ end
214
235
  end
215
236
 
216
237
  class PlanetType < ActiveRecord::Base
@@ -242,6 +263,7 @@ class Breed
242
263
  @id = id
243
264
  end
244
265
  @name = name
266
+ @errors = ActiveModel::Errors.new(self)
245
267
  end
246
268
 
247
269
  attr_accessor :id, :name
@@ -250,7 +272,18 @@ class Breed
250
272
  $breed_data.remove(@id)
251
273
  end
252
274
 
253
- def save!
275
+ def valid?
276
+ @errors.clear
277
+ if name.is_a?(String) && name.length > 0
278
+ return true
279
+ else
280
+ @errors.set(:name, ["can't be blank"])
281
+ return false
282
+ end
283
+ end
284
+
285
+ def errors
286
+ @errors
254
287
  end
255
288
  end
256
289
 
@@ -304,6 +337,12 @@ class LineItem < ActiveRecord::Base
304
337
  belongs_to :purchase_order
305
338
  end
306
339
 
340
+ class NumeroTelefone < ActiveRecord::Base
341
+ end
342
+
343
+ class Category < ActiveRecord::Base
344
+ end
345
+
307
346
  ### PORO Data - don't do this in a production app
308
347
  $breed_data = BreedData.new
309
348
  $breed_data.add(Breed.new(0, 'persian'))
@@ -311,6 +350,19 @@ $breed_data.add(Breed.new(1, 'siamese'))
311
350
  $breed_data.add(Breed.new(2, 'sphinx'))
312
351
  $breed_data.add(Breed.new(3, 'to_delete'))
313
352
 
353
+ ### OperationsProcessor
354
+ class CountingActiveRecordOperationsProcessor < ActiveRecordOperationsProcessor
355
+ after_find_operation do
356
+
357
+ count = @operation.resource_klass.find_count(@operation.resource_klass.verify_filters(@operation.filters, @context),
358
+ context: @context,
359
+ include_directives: @operation.include_directives,
360
+ sort_criteria: @operation.sort_criteria)
361
+
362
+ @operation_meta[:total_records] = count
363
+ end
364
+ end
365
+
314
366
  ### CONTROLLERS
315
367
  class AuthorsController < JSONAPI::ResourceController
316
368
  end
@@ -318,7 +370,8 @@ end
318
370
  class PeopleController < JSONAPI::ResourceController
319
371
  end
320
372
 
321
- class PostsController < JSONAPI::ResourceController
373
+ class PostsController < ActionController::Base
374
+ include JSONAPI::ActsAsResourceController
322
375
  end
323
376
 
324
377
  class CommentsController < JSONAPI::ResourceController
@@ -342,6 +395,9 @@ end
342
395
  class FactsController < JSONAPI::ResourceController
343
396
  end
344
397
 
398
+ class CategoriesController < JSONAPI::ResourceController
399
+ end
400
+
345
401
  ### CONTROLLERS
346
402
  module Api
347
403
  module V1
@@ -351,7 +407,8 @@ module Api
351
407
  class PeopleController < JSONAPI::ResourceController
352
408
  end
353
409
 
354
- class PostsController < JSONAPI::ResourceController
410
+ class PostsController < ActionController::Base
411
+ include JSONAPI::ActsAsResourceController
355
412
  end
356
413
 
357
414
  class TagsController < JSONAPI::ResourceController
@@ -459,6 +516,11 @@ module Api
459
516
  class OrderFlagsController < JSONAPI::ResourceController
460
517
  end
461
518
  end
519
+
520
+ module V8
521
+ class NumerosTelefoneController < JSONAPI::ResourceController
522
+ end
523
+ end
462
524
  end
463
525
 
464
526
  ### RESOURCES
@@ -492,6 +554,8 @@ class CommentResource < JSONAPI::Resource
492
554
  has_one :post
493
555
  has_one :author, class_name: 'Person'
494
556
  has_many :tags
557
+
558
+ filters :body
495
559
  end
496
560
 
497
561
  class TagResource < JSONAPI::Resource
@@ -555,11 +619,11 @@ class PostResource < JSONAPI::Resource
555
619
  filters :title, :author, :tags, :comments
556
620
  filters :id, :ids
557
621
 
558
- def self.updateable_fields(context)
622
+ def self.updatable_fields(context)
559
623
  super(context) - [:author, :subject]
560
624
  end
561
625
 
562
- def self.createable_fields(context)
626
+ def self.creatable_fields(context)
563
627
  super(context) - [:subject]
564
628
  end
565
629
 
@@ -643,6 +707,11 @@ class BreedResource < JSONAPI::Resource
643
707
  def self.find_by_key(id, options = {})
644
708
  BreedResource.new($breed_data.breeds[id.to_i], options[:context])
645
709
  end
710
+
711
+ def _save
712
+ super
713
+ return :accepted
714
+ end
646
715
  end
647
716
 
648
717
  class PlanetResource < JSONAPI::Resource
@@ -696,6 +765,10 @@ class FactResource < JSONAPI::Resource
696
765
  attribute :cool
697
766
  end
698
767
 
768
+ class CategoryResource < JSONAPI::Resource
769
+ filter :status, default: 'active'
770
+ end
771
+
699
772
  module Api
700
773
  module V1
701
774
  class WriterResource < JSONAPI::Resource
@@ -782,6 +855,10 @@ module Api
782
855
  class BookResource < Api::V2::BookResource
783
856
  paginator :paged
784
857
  end
858
+
859
+ class BookCommentResource < Api::V2::BookCommentResource
860
+ paginator :paged
861
+ end
785
862
  end
786
863
  end
787
864
 
@@ -866,6 +943,12 @@ module Api
866
943
  OrderFlagResource = V6::OrderFlagResource.dup
867
944
  LineItemResource = V6::LineItemResource.dup
868
945
  end
946
+
947
+ module V8
948
+ class NumeroTelefoneResource < JSONAPI::Resource
949
+ attribute :numero_telefone
950
+ end
951
+ end
869
952
  end
870
953
 
871
954
  warn 'start testing Name Collisions'
@@ -896,10 +979,17 @@ saturn = Planet.create(name: 'Satern',
896
979
  description: 'Saturn is the sixth planet from the Sun and the second largest planet in the Solar System, after Jupiter.',
897
980
  planet_type_id: planetoid.id)
898
981
  titan = Moon.create(name:'Titan', description: 'Best known of the Saturn moons.', planet_id: saturn.id)
899
- pluto = Planet.create(name: 'Pluto', description: 'Pluto is the smallest planet.', planet_type_id: planetoid.id)
982
+ makemake = Planet.create(name: 'Makemake', description: 'A small planetoid in the Kuiperbelt.', planet_type_id: planetoid.id)
900
983
  uranus = Planet.create(name: 'Uranus', description: 'Insert adolescent jokes here.', planet_type_id: gas_giant.id)
901
984
  jupiter = Planet.create(name: 'Jupiter', description: 'A gas giant.', planet_type_id: gas_giant.id)
902
985
  betax = Planet.create(name: 'Beta X', description: 'Newly discovered Planet X', planet_type_id: unknown.id)
903
986
  betay = Planet.create(name: 'Beta X', description: 'Newly discovered Planet Y', planet_type_id: unknown.id)
904
987
  betaz = Planet.create(name: 'Beta X', description: 'Newly discovered Planet Z', planet_type_id: unknown.id)
905
988
  betaw = Planet.create(name: 'Beta W', description: 'Newly discovered Planet W')
989
+ Category.create(name: 'Category A', status: 'active')
990
+ Category.create(name: 'Category B', status: 'active')
991
+ Category.create(name: 'Category C', status: 'active')
992
+ Category.create(name: 'Category D', status: 'inactive')
993
+ Category.create(name: 'Category E', status: 'inactive')
994
+ Category.create(name: 'Category F', status: 'inactive')
995
+ Category.create(name: 'Category G', status: 'inactive')
@@ -22,4 +22,10 @@ li_4:
22
22
  id: 4
23
23
  part_number: 5678
24
24
  quantity: 2
25
- item_cost: 199.99
25
+ item_cost: 199.99
26
+
27
+ li_5:
28
+ id: 5
29
+ part_number: 5WERT
30
+ quantity: 1
31
+ item_cost: 299.98
@@ -0,0 +1,3 @@
1
+ info:
2
+ id: 1
3
+ numero_telefone: 1-800-555-1212
@@ -9,3 +9,9 @@ po_2:
9
9
  requested_delivery_date:
10
10
  delivery_date: nil
11
11
  customer_id: 1
12
+
13
+ po_3:
14
+ id: 3
15
+ requested_delivery_date:
16
+ delivery_date: nil
17
+ customer_id: 1
@@ -1,8 +1,6 @@
1
1
  require File.expand_path('../../../test_helper', __FILE__)
2
- require File.expand_path('../../../fixtures/active_record', __FILE__)
3
2
 
4
3
  class RequestTest < ActionDispatch::IntegrationTest
5
-
6
4
  def setup
7
5
  JSONAPI.configuration.json_key_format = :underscored_key
8
6
  JSONAPI.configuration.route_format = :underscored_route
@@ -18,6 +16,11 @@ class RequestTest < ActionDispatch::IntegrationTest
18
16
  assert_equal 200, status
19
17
  end
20
18
 
19
+ def test_get_inflected_resource
20
+ get '/api/v8/numeros_telefone'
21
+ assert_equal 200, status
22
+ end
23
+
21
24
  def test_get_nested_has_one
22
25
  get '/posts/1/author'
23
26
  assert_equal 200, status
@@ -45,7 +48,7 @@ class RequestTest < ActionDispatch::IntegrationTest
45
48
  get '/iso_currencies?filter[country_name]=Canada'
46
49
  assert_equal 200, status
47
50
  assert_equal 1, json_response['data'].size
48
- assert_equal 'Canada', json_response['data'][0]['country_name']
51
+ assert_equal 'Canada', json_response['data'][0]['attributes']['country_name']
49
52
  end
50
53
 
51
54
  def test_get_camelized_key_filtered
@@ -53,7 +56,7 @@ class RequestTest < ActionDispatch::IntegrationTest
53
56
  get '/iso_currencies?filter[countryName]=Canada'
54
57
  assert_equal 200, status
55
58
  assert_equal 1, json_response['data'].size
56
- assert_equal 'Canada', json_response['data'][0]['countryName']
59
+ assert_equal 'Canada', json_response['data'][0]['attributes']['countryName']
57
60
  end
58
61
 
59
62
  def test_get_camelized_route_and_key_filtered
@@ -61,16 +64,16 @@ class RequestTest < ActionDispatch::IntegrationTest
61
64
  get '/api/v4/isoCurrencies?filter[countryName]=Canada'
62
65
  assert_equal 200, status
63
66
  assert_equal 1, json_response['data'].size
64
- assert_equal 'Canada', json_response['data'][0]['countryName']
67
+ assert_equal 'Canada', json_response['data'][0]['attributes']['countryName']
65
68
  end
66
69
 
67
70
  def test_get_camelized_route_and_links
68
71
  JSONAPI.configuration.json_key_format = :camelized_key
69
72
  JSONAPI.configuration.route_format = :camelized_route
70
- get '/api/v4/expenseEntries/1/links/isoCurrency'
73
+ get '/api/v4/expenseEntries/1/relationships/isoCurrency'
71
74
  assert_equal 200, status
72
75
  assert_hash_equals({'links' => {
73
- 'self' => 'http://www.example.com/api/v4/expenseEntries/1/links/isoCurrency',
76
+ 'self' => 'http://www.example.com/api/v4/expenseEntries/1/relationships/isoCurrency',
74
77
  'related' => 'http://www.example.com/api/v4/expenseEntries/1/isoCurrency'
75
78
  },
76
79
  'data' => {
@@ -88,7 +91,9 @@ class RequestTest < ActionDispatch::IntegrationTest
88
91
  'type' => 'posts',
89
92
  'id' => '3',
90
93
  },
91
- 'title' => 'A great new Post',
94
+ 'attributes' => {
95
+ 'title' => 'A great new Post'
96
+ },
92
97
  'links' => {
93
98
  'tags' => [
94
99
  {type: 'tags', id: 3},
@@ -107,10 +112,12 @@ class RequestTest < ActionDispatch::IntegrationTest
107
112
  'data' => {
108
113
  'type' => 'posts',
109
114
  'id' => '3',
110
- 'title' => 'A great new Post',
111
- 'links' => {
115
+ 'attributes' => {
116
+ 'title' => 'A great new Post'
117
+ },
118
+ 'relationships' => {
112
119
  'tags' => {
113
- 'linkage' => [
120
+ 'data' => [
114
121
  {type: 'tags', id: 3},
115
122
  {type: 'tags', id: 4}
116
123
  ]
@@ -126,10 +133,12 @@ class RequestTest < ActionDispatch::IntegrationTest
126
133
  post '/posts',
127
134
  {
128
135
  'posts' => {
129
- 'title' => 'A great new Post',
130
- 'links' => {
136
+ 'attributes' => {
137
+ 'title' => 'A great new Post'
138
+ },
139
+ 'relationships' => {
131
140
  'tags' => {
132
- 'linkage' => [
141
+ 'data' => [
133
142
  {type: 'tags', id: 3},
134
143
  {type: 'tags', id: 4}
135
144
  ]
@@ -146,10 +155,12 @@ class RequestTest < ActionDispatch::IntegrationTest
146
155
  {
147
156
  'data' => {
148
157
  'type' => 'posts',
149
- 'title' => 'A great new Post',
150
- 'body' => 'JSONAPIResources is the greatest thing since unsliced bread.',
151
- 'links' => {
152
- 'author' => {'linkage' => {type: 'people', id: '3'}}
158
+ 'attributes' => {
159
+ 'title' => 'A great new Post',
160
+ 'body' => 'JSONAPIResources is the greatest thing since unsliced bread.'
161
+ },
162
+ 'relationships' => {
163
+ 'author' => {'data' => {type: 'people', id: '3'}}
153
164
  }
154
165
  }
155
166
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
@@ -176,9 +187,9 @@ class RequestTest < ActionDispatch::IntegrationTest
176
187
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
177
188
 
178
189
  assert_equal 201, status
179
- assert_nil json_response['data']['body']
180
- assert_nil json_response['data']['links']['post']['linkage']
181
- assert_nil json_response['data']['links']['author']['linkage']
190
+ assert_nil json_response['data']['attributes']['body']
191
+ assert_nil json_response['data']['relationships']['post']['data']
192
+ assert_nil json_response['data']['relationships']['author']['data']
182
193
  end
183
194
 
184
195
  def test_post_single_minimal_invalid
@@ -194,21 +205,21 @@ class RequestTest < ActionDispatch::IntegrationTest
194
205
 
195
206
  def test_update_association_without_content_type
196
207
  ruby = Section.find_by(name: 'ruby')
197
- patch '/posts/3/links/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json
208
+ patch '/posts/3/relationships/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json
198
209
 
199
210
  assert_equal 415, status
200
211
  end
201
212
 
202
213
  def test_patch_update_association_has_one
203
214
  ruby = Section.find_by(name: 'ruby')
204
- patch '/posts/3/links/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
215
+ patch '/posts/3/relationships/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
205
216
 
206
217
  assert_equal 204, status
207
218
  end
208
219
 
209
220
  def test_put_update_association_has_one
210
221
  ruby = Section.find_by(name: 'ruby')
211
- put '/posts/3/links/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
222
+ put '/posts/3/relationships/section', { 'data' => {type: 'sections', id: ruby.id.to_s }}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
212
223
 
213
224
  assert_equal 204, status
214
225
  end
@@ -217,14 +228,14 @@ class RequestTest < ActionDispatch::IntegrationTest
217
228
  # Comments are acts_as_set=false so PUT/PATCH should respond with 403
218
229
 
219
230
  rogue = Comment.find_by(body: 'Rogue Comment Here')
220
- patch '/posts/5/links/comments', { 'data' => [{type: 'comments', id: rogue.id.to_s }]}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
231
+ patch '/posts/5/relationships/comments', { 'data' => [{type: 'comments', id: rogue.id.to_s }]}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
221
232
 
222
233
  assert_equal 403, status
223
234
  end
224
235
 
225
236
  def test_post_update_association_has_many
226
237
  rogue = Comment.find_by(body: 'Rogue Comment Here')
227
- post '/posts/5/links/comments', { 'data' => [{type: 'comments', id: rogue.id.to_s }]}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
238
+ post '/posts/5/relationships/comments', { 'data' => [{type: 'comments', id: rogue.id.to_s }]}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
228
239
 
229
240
  assert_equal 204, status
230
241
  end
@@ -233,7 +244,7 @@ class RequestTest < ActionDispatch::IntegrationTest
233
244
  # Comments are acts_as_set=false so PUT/PATCH should respond with 403. Note: JR currently treats PUT and PATCH as equivalent
234
245
 
235
246
  rogue = Comment.find_by(body: 'Rogue Comment Here')
236
- put '/posts/5/links/comments', { 'data' => [{type: 'comments', id: rogue.id.to_s }]}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
247
+ put '/posts/5/relationships/comments', { 'data' => [{type: 'comments', id: rogue.id.to_s }]}.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
237
248
 
238
249
  assert_equal 403, status
239
250
  end
@@ -254,10 +265,12 @@ class RequestTest < ActionDispatch::IntegrationTest
254
265
  'data' => {
255
266
  'type' => 'posts',
256
267
  'id' => '3',
257
- 'title' => 'A great new Post',
258
- 'links' => {
268
+ 'attributes' => {
269
+ 'title' => 'A great new Post'
270
+ },
271
+ 'relationships' => {
259
272
  'tags' => {
260
- 'linkage' => [
273
+ 'data' => [
261
274
  {type: 'tags', id: 3},
262
275
  {type: 'tags', id: 4}
263
276
  ]
@@ -275,10 +288,12 @@ class RequestTest < ActionDispatch::IntegrationTest
275
288
  'data' => {
276
289
  'type' => 'posts',
277
290
  'id' => '3',
278
- 'title' => 'A great new Post',
279
- 'links' => {
291
+ 'attributes' => {
292
+ 'title' => 'A great new Post'
293
+ },
294
+ 'relationships' => {
280
295
  'tags' => {
281
- 'linkage' => [
296
+ 'data' => [
282
297
  {type: 'tags', id: 3},
283
298
  {type: 'tags', id: 4}
284
299
  ]
@@ -295,9 +310,11 @@ class RequestTest < ActionDispatch::IntegrationTest
295
310
  {
296
311
  'data' => {
297
312
  'type' => 'posts',
298
- 'title' => 'A great new Post',
299
- 'links' => {
300
- 'author' => {'linkage' => {type: 'people', id: '3'}}
313
+ 'attributes' => {
314
+ 'title' => 'A great new Post'
315
+ },
316
+ 'relationships' => {
317
+ 'author' => {'data' => {type: 'people', id: '3'}}
301
318
  }
302
319
  }
303
320
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
@@ -328,7 +345,7 @@ class RequestTest < ActionDispatch::IntegrationTest
328
345
  get '/api/v2/books'
329
346
  assert_equal 200, status
330
347
  assert_equal JSONAPI.configuration.default_page_size, json_response['data'].size
331
- assert_equal 'Book 0', json_response['data'][0]['title']
348
+ assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
332
349
  end
333
350
 
334
351
  def test_pagination_offset_style_offset
@@ -336,7 +353,7 @@ class RequestTest < ActionDispatch::IntegrationTest
336
353
  get '/api/v2/books?page[offset]=50'
337
354
  assert_equal 200, status
338
355
  assert_equal JSONAPI.configuration.default_page_size, json_response['data'].size
339
- assert_equal 'Book 50', json_response['data'][0]['title']
356
+ assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
340
357
  end
341
358
 
342
359
  def test_pagination_offset_style_offset_limit
@@ -344,7 +361,7 @@ class RequestTest < ActionDispatch::IntegrationTest
344
361
  get '/api/v2/books?page[offset]=50&page[limit]=20'
345
362
  assert_equal 200, status
346
363
  assert_equal 20, json_response['data'].size
347
- assert_equal 'Book 50', json_response['data'][0]['title']
364
+ assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
348
365
  end
349
366
 
350
367
  def test_pagination_offset_bad_param
@@ -359,7 +376,7 @@ class RequestTest < ActionDispatch::IntegrationTest
359
376
  assert_equal 200, status
360
377
  assert_equal 2, json_response['data'].size
361
378
  assert_equal 'http://www.example.com/api/v2/books/1/book_comments',
362
- json_response['data'][1]['links']['book_comments']['related']
379
+ json_response['data'][1]['relationships']['book_comments']['links']['related']
363
380
  end
364
381
 
365
382
  def test_pagination_related_resources_data
@@ -368,7 +385,7 @@ class RequestTest < ActionDispatch::IntegrationTest
368
385
  get '/api/v2/books/1/book_comments?page[limit]=10'
369
386
  assert_equal 200, status
370
387
  assert_equal 10, json_response['data'].size
371
- assert_equal 'This is comment 9 on book 1.', json_response['data'][9]['body']
388
+ assert_equal 'This is comment 9 on book 1.', json_response['data'][9]['attributes']['body']
372
389
  end
373
390
 
374
391
  def test_pagination_related_resources_data_includes
@@ -377,7 +394,7 @@ class RequestTest < ActionDispatch::IntegrationTest
377
394
  get '/api/v2/books/1/book_comments?page[limit]=10&include=author,book'
378
395
  assert_equal 200, status
379
396
  assert_equal 10, json_response['data'].size
380
- assert_equal 'This is comment 9 on book 1.', json_response['data'][9]['body']
397
+ assert_equal 'This is comment 9 on book 1.', json_response['data'][9]['attributes']['body']
381
398
  end
382
399
 
383
400
  def test_flow_self
@@ -395,11 +412,11 @@ class RequestTest < ActionDispatch::IntegrationTest
395
412
  assert_equal 200, status
396
413
  post_1 = json_response['data'][0]
397
414
 
398
- get post_1['links']['author']['self']
415
+ get post_1['relationships']['author']['links']['self']
399
416
  assert_equal 200, status
400
417
  assert_hash_equals(json_response, {
401
418
  'links' => {
402
- 'self' => 'http://www.example.com/posts/1/links/author',
419
+ 'self' => 'http://www.example.com/posts/1/relationships/author',
403
420
  'related' => 'http://www.example.com/posts/1/author'
404
421
  },
405
422
  'data' => {type: 'people', id: '1'}
@@ -411,12 +428,12 @@ class RequestTest < ActionDispatch::IntegrationTest
411
428
  assert_equal 200, status
412
429
  post_1 = json_response['data'][0]
413
430
 
414
- get post_1['links']['tags']['self']
431
+ get post_1['relationships']['tags']['links']['self']
415
432
  assert_equal 200, status
416
433
  assert_hash_equals(json_response,
417
434
  {
418
435
  'links' => {
419
- 'self' => 'http://www.example.com/posts/1/links/tags',
436
+ 'self' => 'http://www.example.com/posts/1/relationships/tags',
420
437
  'related' => 'http://www.example.com/posts/1/tags'
421
438
  },
422
439
  'data' => [
@@ -432,18 +449,18 @@ class RequestTest < ActionDispatch::IntegrationTest
432
449
  assert_equal 200, status
433
450
  post_1 = json_response['data'][4]
434
451
 
435
- post post_1['links']['tags']['self'],
452
+ post post_1['relationships']['tags']['links']['self'],
436
453
  {'data' => [{'type' => 'tags', 'id' => '10'}]}.to_json,
437
454
  "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
438
455
 
439
456
  assert_equal 204, status
440
457
 
441
- get post_1['links']['tags']['self']
458
+ get post_1['relationships']['tags']['links']['self']
442
459
  assert_equal 200, status
443
460
  assert_hash_equals(json_response,
444
461
  {
445
462
  'links' => {
446
- 'self' => 'http://www.example.com/posts/5/links/tags',
463
+ 'self' => 'http://www.example.com/posts/5/relationships/tags',
447
464
  'related' => 'http://www.example.com/posts/5/tags'
448
465
  },
449
466
  'data' => [
@@ -499,7 +516,9 @@ class RequestTest < ActionDispatch::IntegrationTest
499
516
  post '/api/v6/purchase-orders',
500
517
  {
501
518
  'data' => {
502
- 'delivery-name' => 'ASDFG Corp',
519
+ 'attributes' => {
520
+ 'delivery-name' => 'ASDFG Corp'
521
+ },
503
522
  'type' => 'purchase-orders'
504
523
  }
505
524
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
@@ -513,7 +532,9 @@ class RequestTest < ActionDispatch::IntegrationTest
513
532
  post '/api/v6/purchase-orders',
514
533
  {
515
534
  'data' => {
516
- 'delivery_name' => 'ASDFG Corp',
535
+ 'attributes' => {
536
+ 'delivery_name' => 'ASDFG Corp'
537
+ },
517
538
  'type' => 'purchase_orders'
518
539
  }
519
540
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
@@ -527,7 +548,9 @@ class RequestTest < ActionDispatch::IntegrationTest
527
548
  post '/api/v7/purchase_orders',
528
549
  {
529
550
  'data' => {
530
- 'delivery-name' => 'ASDFG Corp',
551
+ 'attributes' => {
552
+ 'delivery-name' => 'ASDFG Corp'
553
+ },
531
554
  'type' => 'purchase-orders'
532
555
  }
533
556
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
@@ -541,7 +564,9 @@ class RequestTest < ActionDispatch::IntegrationTest
541
564
  post '/api/v6/purchase-orders',
542
565
  {
543
566
  'data' => {
544
- 'delivery_name' => 'ASDFG Corp',
567
+ 'attributes' => {
568
+ 'delivery_name' => 'ASDFG Corp'
569
+ },
545
570
  'type' => 'purchase-orders'
546
571
  }
547
572
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
@@ -556,7 +581,9 @@ class RequestTest < ActionDispatch::IntegrationTest
556
581
  {
557
582
  'data' => {
558
583
  'id' => '1',
559
- 'delivery-name' => 'ASDFG Corp',
584
+ 'attributes' => {
585
+ 'delivery-name' => 'ASDFG Corp'
586
+ },
560
587
  'type' => 'purchase-orders'
561
588
  }
562
589
  }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
@@ -572,10 +599,12 @@ class RequestTest < ActionDispatch::IntegrationTest
572
599
  'data' => {
573
600
  'id' => '1',
574
601
  'type' => 'line-items',
575
- 'item-cost' => '23.57',
576
- 'links' => {
602
+ 'attributes' => {
603
+ 'item-cost' => '23.57'
604
+ },
605
+ 'relationships' => {
577
606
  'purchase-order' => {
578
- 'linkage' => {'type' => 'purchase-orders', 'id' => '2'}
607
+ 'data' => {'type' => 'purchase-orders', 'id' => '2'}
579
608
  }
580
609
  }
581
610
  }
@@ -592,15 +621,15 @@ class RequestTest < ActionDispatch::IntegrationTest
592
621
  'data' => {
593
622
  'id' => '2',
594
623
  'type' => 'purchase-orders',
595
- 'links' => {
624
+ 'relationships' => {
596
625
  'line-items' => {
597
- 'linkage' => [
626
+ 'data' => [
598
627
  {'type' => 'line-items', 'id' => '3'},
599
628
  {'type' => 'line-items', 'id' => '4'}
600
629
  ]
601
630
  },
602
631
  'order-flags' => {
603
- 'linkage' => [
632
+ 'data' => [
604
633
  {'type' => 'order-flags', 'id' => '1'},
605
634
  {'type' => 'order-flags', 'id' => '2'}
606
635
  ]
@@ -611,4 +640,44 @@ class RequestTest < ActionDispatch::IntegrationTest
611
640
 
612
641
  assert_equal 200, status
613
642
  end
643
+
644
+ def test_post_has_many_link
645
+ JSONAPI.configuration.route_format = :dasherized_route
646
+ JSONAPI.configuration.json_key_format = :dasherized_key
647
+ post '/api/v6/purchase-orders/3/relationships/line-items',
648
+ {
649
+ 'data' => [
650
+ {'type' => 'line-items', 'id' => '3'},
651
+ {'type' => 'line-items', 'id' => '4'}
652
+ ]
653
+ }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
654
+
655
+ assert_equal 204, status
656
+ end
657
+
658
+ def test_patch_has_many_link
659
+ JSONAPI.configuration.route_format = :dasherized_route
660
+ JSONAPI.configuration.json_key_format = :dasherized_key
661
+ patch '/api/v6/purchase-orders/3/relationships/order-flags',
662
+ {
663
+ 'data' => [
664
+ {'type' => 'order-flags', 'id' => '1'},
665
+ {'type' => 'order-flags', 'id' => '2'}
666
+ ]
667
+ }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
668
+
669
+ assert_equal 204, status
670
+ end
671
+
672
+ def test_patch_has_one
673
+ JSONAPI.configuration.route_format = :dasherized_route
674
+ JSONAPI.configuration.json_key_format = :dasherized_key
675
+ patch '/api/v6/line-items/5/relationships/purchase-order',
676
+ {
677
+ 'data' => {'type' => 'purchase-orders', 'id' => '3'}
678
+ }.to_json, "CONTENT_TYPE" => JSONAPI::MEDIA_TYPE
679
+
680
+ assert_equal 204, status
681
+ end
682
+
614
683
  end