jsonapi-resources 0.4.2 → 0.4.3

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