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.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/README.md +103 -71
- data/Rakefile +2 -2
- data/jsonapi-resources.gemspec +2 -2
- data/lib/jsonapi-resources.rb +0 -1
- data/lib/jsonapi/active_record_operations_processor.rb +10 -2
- data/lib/jsonapi/acts_as_resource_controller.rb +26 -24
- data/lib/jsonapi/association.rb +50 -15
- data/lib/jsonapi/callbacks.rb +1 -2
- data/lib/jsonapi/configuration.rb +8 -24
- data/lib/jsonapi/error.rb +1 -2
- data/lib/jsonapi/error_codes.rb +3 -1
- data/lib/jsonapi/exceptions.rb +59 -47
- data/lib/jsonapi/include_directives.rb +11 -11
- data/lib/jsonapi/mime_types.rb +2 -2
- data/lib/jsonapi/operation.rb +28 -11
- data/lib/jsonapi/operations_processor.rb +16 -5
- data/lib/jsonapi/paginator.rb +19 -19
- data/lib/jsonapi/request.rb +175 -196
- data/lib/jsonapi/resource.rb +158 -105
- data/lib/jsonapi/resource_serializer.rb +37 -26
- data/lib/jsonapi/resources/version.rb +2 -2
- data/lib/jsonapi/response_document.rb +5 -4
- data/lib/jsonapi/routing_ext.rb +24 -19
- data/test/controllers/controller_test.rb +261 -31
- data/test/fixtures/active_record.rb +206 -8
- data/test/fixtures/book_comments.yml +2 -1
- data/test/fixtures/books.yml +1 -0
- data/test/fixtures/documents.yml +3 -0
- data/test/fixtures/people.yml +8 -1
- data/test/fixtures/pictures.yml +15 -0
- data/test/fixtures/products.yml +3 -0
- data/test/fixtures/vehicles.yml +8 -0
- data/test/helpers/{hash_helpers.rb → assertions.rb} +6 -1
- data/test/integration/requests/request_test.rb +14 -3
- data/test/integration/routes/routes_test.rb +47 -0
- data/test/test_helper.rb +27 -4
- data/test/unit/serializer/include_directives_test.rb +5 -0
- data/test/unit/serializer/polymorphic_serializer_test.rb +384 -0
- data/test/unit/serializer/serializer_test.rb +19 -1
- 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/
|
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
|
-
|
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:
|
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::
|
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
|