jsonapi-resources 0.4.2 → 0.4.3

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +103 -71
  4. data/Rakefile +2 -2
  5. data/jsonapi-resources.gemspec +2 -2
  6. data/lib/jsonapi-resources.rb +0 -1
  7. data/lib/jsonapi/active_record_operations_processor.rb +10 -2
  8. data/lib/jsonapi/acts_as_resource_controller.rb +26 -24
  9. data/lib/jsonapi/association.rb +50 -15
  10. data/lib/jsonapi/callbacks.rb +1 -2
  11. data/lib/jsonapi/configuration.rb +8 -24
  12. data/lib/jsonapi/error.rb +1 -2
  13. data/lib/jsonapi/error_codes.rb +3 -1
  14. data/lib/jsonapi/exceptions.rb +59 -47
  15. data/lib/jsonapi/include_directives.rb +11 -11
  16. data/lib/jsonapi/mime_types.rb +2 -2
  17. data/lib/jsonapi/operation.rb +28 -11
  18. data/lib/jsonapi/operations_processor.rb +16 -5
  19. data/lib/jsonapi/paginator.rb +19 -19
  20. data/lib/jsonapi/request.rb +175 -196
  21. data/lib/jsonapi/resource.rb +158 -105
  22. data/lib/jsonapi/resource_serializer.rb +37 -26
  23. data/lib/jsonapi/resources/version.rb +2 -2
  24. data/lib/jsonapi/response_document.rb +5 -4
  25. data/lib/jsonapi/routing_ext.rb +24 -19
  26. data/test/controllers/controller_test.rb +261 -31
  27. data/test/fixtures/active_record.rb +206 -8
  28. data/test/fixtures/book_comments.yml +2 -1
  29. data/test/fixtures/books.yml +1 -0
  30. data/test/fixtures/documents.yml +3 -0
  31. data/test/fixtures/people.yml +8 -1
  32. data/test/fixtures/pictures.yml +15 -0
  33. data/test/fixtures/products.yml +3 -0
  34. data/test/fixtures/vehicles.yml +8 -0
  35. data/test/helpers/{hash_helpers.rb → assertions.rb} +6 -1
  36. data/test/integration/requests/request_test.rb +14 -3
  37. data/test/integration/routes/routes_test.rb +47 -0
  38. data/test/test_helper.rb +27 -4
  39. data/test/unit/serializer/include_directives_test.rb +5 -0
  40. data/test/unit/serializer/polymorphic_serializer_test.rb +384 -0
  41. data/test/unit/serializer/serializer_test.rb +19 -1
  42. metadata +14 -4
@@ -52,6 +52,53 @@ class RoutesTest < ActionDispatch::IntegrationTest
52
52
  {controller: 'posts', action: 'update_association', post_id: '1', association: 'tags'})
53
53
  end
54
54
 
55
+ # Polymorphic
56
+ def test_routing_polymorphic_get_related_resource
57
+ assert_routing(
58
+ {
59
+ path: '/pictures/1/imageable',
60
+ method: :get
61
+ },
62
+ {
63
+ association: 'imageable',
64
+ source: 'pictures',
65
+ controller: 'imageables',
66
+ action: 'get_related_resource',
67
+ picture_id: '1'
68
+ }
69
+ )
70
+ end
71
+
72
+ def test_routing_polymorphic_patch_related_resource
73
+ assert_routing(
74
+ {
75
+ path: '/pictures/1/relationships/imageable',
76
+ method: :patch
77
+ },
78
+ {
79
+ association: 'imageable',
80
+ controller: 'pictures',
81
+ action: 'update_association',
82
+ picture_id: '1'
83
+ }
84
+ )
85
+ end
86
+
87
+ def test_routing_polymorphic_delete_related_resource
88
+ assert_routing(
89
+ {
90
+ path: '/pictures/1/relationships/imageable',
91
+ method: :delete
92
+ },
93
+ {
94
+ association: 'imageable',
95
+ controller: 'pictures',
96
+ action: 'destroy_association',
97
+ picture_id: '1'
98
+ }
99
+ )
100
+ end
101
+
55
102
  # V1
56
103
  def test_routing_v1_posts_show
57
104
  assert_routing({path: '/api/v1/posts/1', method: :get},
data/test/test_helper.rb CHANGED
@@ -15,7 +15,7 @@ require 'rails/test_help'
15
15
  require 'jsonapi-resources'
16
16
 
17
17
  require File.expand_path('../helpers/value_matchers', __FILE__)
18
- require File.expand_path('../helpers/hash_helpers', __FILE__)
18
+ require File.expand_path('../helpers/assertions', __FILE__)
19
19
  require File.expand_path('../helpers/functional_helpers', __FILE__)
20
20
 
21
21
  Rails.env = 'test'
@@ -59,14 +59,30 @@ end
59
59
 
60
60
  def count_queries(&block)
61
61
  @query_count = 0
62
- ActiveSupport::Notifications.subscribe('sql.active_record') do
62
+ @queries = []
63
+ ActiveSupport::Notifications.subscribe('sql.active_record') do |name, started, finished, unique_id, payload|
63
64
  @query_count = @query_count + 1
65
+ @queries.push payload[:sql]
64
66
  end
65
67
  yield block
66
68
  ActiveSupport::Notifications.unsubscribe('sql.active_record')
67
69
  @query_count
68
70
  end
69
71
 
72
+ def assert_query_count(expected, msg = nil)
73
+ msg = message(msg) {
74
+ "Expected #{expected} queries, ran #{@query_count} queries"
75
+ }
76
+ show_queries unless expected == @query_count
77
+ assert expected == @query_count, msg
78
+ end
79
+
80
+ def show_queries
81
+ @queries.each_with_index do |query, index|
82
+ puts "sql[#{index}]: #{query}"
83
+ end
84
+ end
85
+
70
86
  TestApp.initialize!
71
87
 
72
88
  require File.expand_path('../fixtures/active_record', __FILE__)
@@ -89,9 +105,16 @@ TestApp.routes.draw do
89
105
  jsonapi_resources :preferences
90
106
  jsonapi_resources :facts
91
107
  jsonapi_resources :categories
108
+ jsonapi_resources :pictures
109
+ jsonapi_resources :documents
110
+ jsonapi_resources :products
92
111
 
93
112
  namespace :api do
94
113
  namespace :v1 do
114
+ scope ":section_id" do
115
+ jsonapi_resources :people
116
+ end
117
+
95
118
  jsonapi_resources :people
96
119
  jsonapi_resources :comments
97
120
  jsonapi_resources :tags
@@ -110,7 +133,7 @@ TestApp.routes.draw do
110
133
  JSONAPI.configuration.route_format = :underscored_route
111
134
  namespace :v2 do
112
135
  jsonapi_resources :posts do
113
- jsonapi_link :author, except: [:destroy]
136
+ jsonapi_link :author, except: :destroy
114
137
  end
115
138
 
116
139
  jsonapi_resource :preferences, except: [:create, :destroy]
@@ -184,7 +207,7 @@ end
184
207
  Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
185
208
 
186
209
  class Minitest::Test
187
- include Helpers::HashHelpers
210
+ include Helpers::Assertions
188
211
  include Helpers::ValueMatchers
189
212
  include Helpers::FunctionalHelpers
190
213
  end
@@ -105,4 +105,9 @@ class IncludeDirectivesTest < ActiveSupport::TestCase
105
105
  },
106
106
  directives)
107
107
  end
108
+
109
+ def test_three_levels_include_full_model_includes
110
+ directives = JSONAPI::IncludeDirectives.new(['posts.comments.tags'])
111
+ assert_array_equals([{:posts=>[{:comments=>[:tags]}]}], directives.model_includes)
112
+ end
108
113
  end
@@ -0,0 +1,384 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+ require 'jsonapi-resources'
3
+ require 'json'
4
+
5
+ class PolymorphismTest < ActionDispatch::IntegrationTest
6
+ def setup
7
+ @pictures = Picture.all
8
+ @person = Person.find(1)
9
+
10
+ JSONAPI.configuration.json_key_format = :camelized_key
11
+ JSONAPI.configuration.route_format = :camelized_route
12
+ end
13
+
14
+ def after_teardown
15
+ JSONAPI.configuration.json_key_format = :underscored_key
16
+ end
17
+
18
+ def test_polymorphic_association
19
+ associations = PictureResource._associations
20
+ imageable = associations[:imageable]
21
+
22
+ assert_equal associations.size, 1
23
+ assert imageable.polymorphic?
24
+ end
25
+
26
+ def test_polymorphic_has_many_serialization
27
+ serialized_data = JSONAPI::ResourceSerializer.new(
28
+ PersonResource,
29
+ include: %w(vehicles)
30
+ ).serialize_to_hash(PersonResource.new(@person))
31
+
32
+ assert_hash_equals(
33
+ {
34
+ :data => {
35
+ "id" => "1",
36
+ "type" => "people",
37
+ "links" => {
38
+ :self => "/people/1"
39
+ },
40
+ "attributes" => {
41
+ "name" => "Joe Author",
42
+ "email" => "joe@xyz.fake",
43
+ "dateJoined" => "2013-08-07 16:25:00 -0400"
44
+ },
45
+ "relationships" => {
46
+ "comments" => {
47
+ :links => {
48
+ :self => "/people/1/relationships/comments",
49
+ :related => "/people/1/comments"
50
+ }
51
+ },
52
+ "posts" => {
53
+ :links => {
54
+ :self => "/people/1/relationships/posts",
55
+ :related => "/people/1/posts"
56
+ }
57
+ },
58
+ "vehicles" => {
59
+ :links => {
60
+ :self => "/people/1/relationships/vehicles",
61
+ :related => "/people/1/vehicles"
62
+ },
63
+ :data => [
64
+ { :type => "cars", :id=> "1" },
65
+ { :type => "boats", :id=>"2" }
66
+ ]
67
+ },
68
+ "preferences" => {
69
+ :links => {
70
+ :self => "/people/1/relationships/preferences",
71
+ :related => "/people/1/preferences"
72
+ },
73
+ :data => {
74
+ :type => "preferences",
75
+ :id=>"1"
76
+ }
77
+ },
78
+ "hairCut" => {
79
+ :links => {
80
+ :self => "/people/1/relationships/hairCut",
81
+ :related => "/people/1/hairCut"
82
+ },
83
+ :data => nil
84
+ }
85
+ }
86
+ },
87
+ :included => [
88
+ {
89
+ "id" => "1",
90
+ "type" => "cars",
91
+ "links" => {
92
+ :self => "/cars/1"
93
+ },
94
+ "relationships" => {
95
+ "person" => {
96
+ :links => {
97
+ :self => "/cars/1/relationships/person",
98
+ :related => "/cars/1/person"
99
+ },
100
+ :data => {
101
+ :type => "people",
102
+ :id => "1"
103
+ }
104
+ }
105
+ }
106
+ },
107
+ {
108
+ "id" => "2",
109
+ "type" => "boats",
110
+ "links" => {
111
+ :self => "/boats/2"
112
+ },
113
+ "relationships" => {
114
+ "person" => {
115
+ :links => {
116
+ :self => "/boats/2/relationships/person",
117
+ :related => "/boats/2/person"
118
+ },
119
+ :data => {
120
+ :type => "people",
121
+ :id => "1"
122
+ }
123
+ }
124
+ }
125
+ }
126
+ ]
127
+ },
128
+ serialized_data
129
+ )
130
+ end
131
+
132
+ def test_polymorphic_has_one_serialization
133
+ serialized_data = JSONAPI::ResourceSerializer.new(
134
+ PictureResource,
135
+ include: %w(imageable)
136
+ ).serialize_to_hash(@pictures.map { |p| PictureResource.new p })
137
+
138
+ assert_hash_equals(
139
+ {
140
+ data: [
141
+ {
142
+ id: '1',
143
+ type: 'pictures',
144
+ links: {
145
+ self: '/pictures/1'
146
+ },
147
+ attributes: {
148
+ name: 'enterprise_gizmo.jpg'
149
+ },
150
+ relationships: {
151
+ imageable: {
152
+ links: {
153
+ self: '/pictures/1/relationships/imageable',
154
+ related: '/pictures/1/imageable'
155
+ },
156
+ data: {
157
+ type: 'products',
158
+ id: '1'
159
+ }
160
+ }
161
+ }
162
+ },
163
+ {
164
+ id: '2',
165
+ type: 'pictures',
166
+ links: {
167
+ self: '/pictures/2'
168
+ },
169
+ attributes: {
170
+ name: 'company_brochure.jpg'
171
+ },
172
+ relationships: {
173
+ imageable: {
174
+ links: {
175
+ self: '/pictures/2/relationships/imageable',
176
+ related: '/pictures/2/imageable'
177
+ },
178
+ data: {
179
+ type: 'documents',
180
+ id: '1'
181
+ }
182
+ }
183
+ }
184
+ },
185
+ {
186
+ id: '3',
187
+ type: 'pictures',
188
+ links: {
189
+ self: '/pictures/3'
190
+ },
191
+ attributes: {
192
+ name: 'group_photo.jpg'
193
+ },
194
+ relationships: {
195
+ imageable: {
196
+ links: {
197
+ self: '/pictures/3/relationships/imageable',
198
+ related: '/pictures/3/imageable'
199
+ },
200
+ data: nil
201
+ }
202
+ }
203
+ }
204
+
205
+ ],
206
+ :included => [
207
+ {
208
+ id: '1',
209
+ type: 'products',
210
+ links: {
211
+ self: '/products/1'
212
+ },
213
+ attributes: {
214
+ name: 'Enterprise Gizmo'
215
+ },
216
+ relationships: {
217
+ picture: {
218
+ links: {
219
+ self: '/products/1/relationships/picture',
220
+ related: '/products/1/picture',
221
+ },
222
+ data: {
223
+ type: 'pictures',
224
+ id: '1'
225
+ }
226
+ }
227
+ }
228
+ },
229
+ {
230
+ id: '1',
231
+ type: 'documents',
232
+ links: {
233
+ self: '/documents/1'
234
+ },
235
+ attributes: {
236
+ name: 'Company Brochure'
237
+ },
238
+ relationships: {
239
+ pictures: {
240
+ links: {
241
+ self: '/documents/1/relationships/pictures',
242
+ related: '/documents/1/pictures'
243
+ }
244
+ }
245
+ }
246
+ }
247
+ ]
248
+ },
249
+ serialized_data
250
+ )
251
+ end
252
+
253
+ def test_polymorphic_get_related_resource
254
+ get '/pictures/1/imageable'
255
+ serialized_data = JSON.parse(response.body)
256
+ assert_hash_equals(
257
+ {
258
+ data: {
259
+ id: '1',
260
+ type: 'products',
261
+ links: {
262
+ self: 'http://www.example.com/products/1'
263
+ },
264
+ attributes: {
265
+ name: 'Enterprise Gizmo'
266
+ },
267
+ relationships: {
268
+ picture: {
269
+ links: {
270
+ self: 'http://www.example.com/products/1/relationships/picture',
271
+ related: 'http://www.example.com/products/1/picture'
272
+ },
273
+ data: {
274
+ type: 'pictures',
275
+ id: '1'
276
+ }
277
+ }
278
+ }
279
+ }
280
+ },
281
+ serialized_data
282
+ )
283
+ end
284
+
285
+ def test_create_resource_with_polymorphic_relationship
286
+ document = Document.find(1)
287
+ post "/pictures/",
288
+ {
289
+ data: {
290
+ type: "pictures",
291
+ attributes: {
292
+ name: "hello.jpg"
293
+ },
294
+ relationships: {
295
+ imageable: {
296
+ data: {
297
+ type: "documents",
298
+ id: document.id.to_s
299
+ }
300
+ }
301
+ }
302
+ }
303
+ }.to_json,
304
+ {
305
+ 'Content-Type' => JSONAPI::MEDIA_TYPE
306
+ }
307
+ assert_equal response.status, 201
308
+ picture = Picture.find(json_response["data"]["id"])
309
+ assert_not_nil picture.imageable, "imageable should be present"
310
+ ensure
311
+ picture.destroy
312
+ end
313
+
314
+ def test_polymorphic_create_relationship
315
+ picture = Picture.find(3)
316
+ original_imageable = picture.imageable
317
+ assert_nil original_imageable
318
+
319
+ patch "/pictures/#{picture.id}/relationships/imageable",
320
+ {
321
+ association: 'imageable',
322
+ data: {
323
+ type: 'documents',
324
+ id: '1'
325
+ }
326
+ }.to_json,
327
+ {
328
+ 'Content-Type' => JSONAPI::MEDIA_TYPE
329
+ }
330
+ assert_response :no_content
331
+ picture = Picture.find(3)
332
+ assert_equal 'Document', picture.imageable.class.to_s
333
+
334
+ # restore data
335
+ picture.imageable = original_imageable
336
+ picture.save
337
+ end
338
+
339
+ def test_polymorphic_update_relationship
340
+ picture = Picture.find(1)
341
+ original_imageable = picture.imageable
342
+ assert_not_equal 'Document', picture.imageable.class.to_s
343
+
344
+ patch "/pictures/#{picture.id}/relationships/imageable",
345
+ {
346
+ association: 'imageable',
347
+ data: {
348
+ type: 'documents',
349
+ id: '1'
350
+ }
351
+ }.to_json,
352
+ {
353
+ 'Content-Type' => JSONAPI::MEDIA_TYPE
354
+ }
355
+ assert_response :no_content
356
+ picture = Picture.find(1)
357
+ assert_equal 'Document', picture.imageable.class.to_s
358
+
359
+ # restore data
360
+ picture.imageable = original_imageable
361
+ picture.save
362
+ end
363
+
364
+ def test_polymorphic_delete_relationship
365
+ picture = Picture.find(1)
366
+ original_imageable = picture.imageable
367
+ assert original_imageable
368
+
369
+ delete "/pictures/#{picture.id}/relationships/imageable",
370
+ {
371
+ association: 'imageable'
372
+ }.to_json,
373
+ {
374
+ 'Content-Type' => JSONAPI::MEDIA_TYPE
375
+ }
376
+ assert_response :no_content
377
+ picture = Picture.find(1)
378
+ assert_nil picture.imageable
379
+
380
+ # restore data
381
+ picture.imageable = original_imageable
382
+ picture.save
383
+ end
384
+ end