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
@@ -18,6 +18,7 @@ module JSONAPI
18
18
 
19
19
  @fields = options.fetch(:fields, {})
20
20
  @include = options.fetch(:include, [])
21
+ @include_directives = options[:include_directives]
21
22
  @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
22
23
  @route_formatter = options.fetch(:route_formatter, JSONAPI.configuration.route_formatter)
23
24
  @base_url = options.fetch(:base_url, '')
@@ -28,10 +29,9 @@ module JSONAPI
28
29
  is_resource_collection = source.respond_to?(:to_ary)
29
30
 
30
31
  @included_objects = {}
32
+ @include_directives ||= JSONAPI::IncludeDirectives.new(@include)
31
33
 
32
- requested_associations = parse_includes(@include)
33
-
34
- process_primary(source, requested_associations)
34
+ process_primary(source, @include_directives.include_directives)
35
35
 
36
36
  included_objects = []
37
37
  primary_objects = []
@@ -47,11 +47,7 @@ module JSONAPI
47
47
 
48
48
  primary_hash = {data: is_resource_collection ? primary_objects : primary_objects[0]}
49
49
 
50
- if included_objects.size > 0
51
- primary_hash[:included] = included_objects
52
- else
53
- primary_hash
54
- end
50
+ primary_hash[:included] = included_objects if included_objects.size > 0
55
51
  primary_hash
56
52
  end
57
53
 
@@ -72,34 +68,11 @@ module JSONAPI
72
68
  end
73
69
 
74
70
  private
75
- # Convert an array of associated objects to include along with the primary document in the form of
76
- # ['comments','author','comments.tags','author.posts'] into a structure that tells what we need to include
77
- # from each association.
78
- def parse_includes(includes)
79
- requested_associations = {}
80
- includes.each do |include|
81
- include = include.to_s.underscore
82
-
83
- pos = include.index('.')
84
- if pos
85
- association_name = include[0, pos].to_sym
86
- requested_associations[association_name] ||= {}
87
- requested_associations[association_name].store(:include_children, true)
88
- requested_associations[association_name].store(:include_related, parse_includes([include[pos+1, include.length]]))
89
- else
90
- association_name = include.to_sym
91
- requested_associations[association_name] ||= {}
92
- requested_associations[association_name].store(:include, true)
93
- end
94
- end if includes.is_a?(Array)
95
- return requested_associations
96
- end
97
-
98
71
  # Process the primary source object(s). This will then serialize associated object recursively based on the
99
72
  # requested includes. Fields are controlled fields option for each resource type, such
100
73
  # as fields: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}
101
74
  # The fields options controls both fields and included links references.
102
- def process_primary(source, requested_associations)
75
+ def process_primary(source, include_directives)
103
76
  if source.respond_to?(:to_ary)
104
77
  source.each do |resource|
105
78
  id = resource.id
@@ -107,25 +80,37 @@ module JSONAPI
107
80
  set_primary(@primary_class_name, id)
108
81
  end
109
82
 
110
- add_included_object(@primary_class_name, id, object_hash(resource, requested_associations), true)
83
+ add_included_object(@primary_class_name, id, object_hash(resource, include_directives), true)
111
84
  end
112
85
  else
113
86
  return {} if source.nil?
114
87
 
115
88
  resource = source
116
89
  id = resource.id
117
- add_included_object(@primary_class_name, id, object_hash(source, requested_associations), true)
90
+ add_included_object(@primary_class_name, id, object_hash(source, include_directives), true)
118
91
  end
119
92
  end
120
93
 
121
94
  # Returns a serialized hash for the source model
122
- def object_hash(source, requested_associations)
123
- obj_hash = attribute_hash(source)
124
- links = links_hash(source, requested_associations)
95
+ def object_hash(source, include_directives)
96
+ obj_hash = {}
97
+
98
+ id_format = source.class._attribute_options(:id)[:format]
99
+ #protect against ids that were declared as an attribute, but did not have a format set.
100
+ id_format = 'id' if id_format == :default
101
+ obj_hash['id'] = format_value(source.id, id_format)
125
102
 
126
103
  obj_hash['type'] = format_key(source.class._type.to_s)
127
- obj_hash['id'] ||= format_value(source.id, :id, source)
128
- obj_hash.merge!({links: links}) unless links.empty?
104
+
105
+ links = relationship_links(source)
106
+ obj_hash['links'] = links unless links.empty?
107
+
108
+ attributes = attribute_hash(source)
109
+ obj_hash['attributes'] = attributes unless attributes.empty?
110
+
111
+ relationships = relationship_data(source, include_directives)
112
+ obj_hash['relationships'] = relationships unless relationships.nil? || relationships.empty?
113
+
129
114
  return obj_hash
130
115
  end
131
116
 
@@ -142,20 +127,13 @@ module JSONAPI
142
127
 
143
128
  fields.each_with_object({}) do |name, hash|
144
129
  format = source.class._attribute_options(name)[:format]
145
- if format == :default && name == :id
146
- format = 'id'
130
+ unless name == :id
131
+ hash[format_key(name)] = format_value(source.send(name), format)
147
132
  end
148
- hash[format_key(name)] = format_value(
149
- source.send(name),
150
- format,
151
- source
152
- )
153
133
  end
154
134
  end
155
135
 
156
- # Returns a hash of links for the requested associations for a resource, filtered by the resource
157
- # class's fetchable method
158
- def links_hash(source, requested_associations)
136
+ def relationship_data(source, include_directives)
159
137
  associations = source.class._associations
160
138
  requested = requested_fields(source.class._type)
161
139
  fields = associations.keys
@@ -167,15 +145,14 @@ module JSONAPI
167
145
 
168
146
  included_associations = source.fetchable_fields & associations.keys
169
147
 
170
- links = {}
171
- links[:self] = self_href(source)
148
+ data = {}
172
149
 
173
- associations.each_with_object(links) do |(name, association), hash|
150
+ associations.each_with_object(data) do |(name, association), hash|
174
151
  if included_associations.include? name
175
- ia = requested_associations.is_a?(Hash) ? requested_associations[name] : nil
152
+ ia = include_directives[:include_related][name]
176
153
 
177
154
  include_linkage = ia && ia[:include]
178
- include_linked_children = ia && ia[:include_children]
155
+ include_linked_children = ia && !ia[:include_related].empty?
179
156
 
180
157
  if field_set.include?(name)
181
158
  hash[format_key(name)] = link_object(source, association, include_linkage)
@@ -193,9 +170,9 @@ module JSONAPI
193
170
  id = resource.id
194
171
  associations_only = already_serialized?(type, id)
195
172
  if include_linkage && !associations_only
196
- add_included_object(type, id, object_hash(resource, ia[:include_related]))
173
+ add_included_object(type, id, object_hash(resource, ia))
197
174
  elsif include_linked_children || associations_only
198
- links_hash(resource, ia[:include_related])
175
+ relationship_data(resource, ia)
199
176
  end
200
177
  end
201
178
  elsif association.is_a?(JSONAPI::Association::HasMany)
@@ -204,9 +181,9 @@ module JSONAPI
204
181
  id = resource.id
205
182
  associations_only = already_serialized?(type, id)
206
183
  if include_linkage && !associations_only
207
- add_included_object(type, id, object_hash(resource, ia[:include_related]))
184
+ add_included_object(type, id, object_hash(resource, ia))
208
185
  elsif include_linked_children || associations_only
209
- links_hash(resource, ia[:include_related])
186
+ relationship_data(resource, ia)
210
187
  end
211
188
  end
212
189
  end
@@ -215,6 +192,13 @@ module JSONAPI
215
192
  end
216
193
  end
217
194
 
195
+ def relationship_links(source)
196
+ links = {}
197
+ links[:self] = self_href(source)
198
+
199
+ links
200
+ end
201
+
218
202
  def formatted_module_path(source)
219
203
  source.class.name =~ /::[^:]+\Z/ ? (@route_formatter.format($`).freeze.gsub('::', '/') + '/').downcase : ''
220
204
  end
@@ -233,7 +217,7 @@ module JSONAPI
233
217
  end
234
218
 
235
219
  def self_link(source, association)
236
- "#{self_href(source)}/links/#{format_route(association.name)}"
220
+ "#{self_href(source)}/relationships/#{format_route(association.name)}"
237
221
  end
238
222
 
239
223
  def related_link(source, association)
@@ -263,17 +247,19 @@ module JSONAPI
263
247
 
264
248
  def link_object_has_one(source, association)
265
249
  link_object_hash = {}
266
- link_object_hash[:self] = self_link(source, association)
267
- link_object_hash[:related] = related_link(source, association)
268
- link_object_hash[:linkage] = has_one_linkage(source, association)
250
+ link_object_hash[:links] = {}
251
+ link_object_hash[:links][:self] = self_link(source, association)
252
+ link_object_hash[:links][:related] = related_link(source, association)
253
+ link_object_hash[:data] = has_one_linkage(source, association)
269
254
  link_object_hash
270
255
  end
271
256
 
272
257
  def link_object_has_many(source, association, include_linkage)
273
258
  link_object_hash = {}
274
- link_object_hash[:self] = self_link(source, association)
275
- link_object_hash[:related] = related_link(source, association)
276
- link_object_hash[:linkage] = has_many_linkage(source, association) if include_linkage
259
+ link_object_hash[:links] = {}
260
+ link_object_hash[:links][:self] = self_link(source, association)
261
+ link_object_hash[:links][:related] = related_link(source, association)
262
+ link_object_hash[:data] = has_many_linkage(source, association) if include_linkage
277
263
  link_object_hash
278
264
  end
279
265
 
@@ -291,9 +277,9 @@ module JSONAPI
291
277
  value = source.send(foreign_key)
292
278
 
293
279
  if association.is_a?(JSONAPI::Association::HasMany)
294
- value.map { |value| IdValueFormatter.format(value, {}) }
280
+ value.map { |value| IdValueFormatter.format(value) }
295
281
  elsif association.is_a?(JSONAPI::Association::HasOne)
296
- IdValueFormatter.format(value, {})
282
+ IdValueFormatter.format(value)
297
283
  end
298
284
  end
299
285
 
@@ -324,9 +310,9 @@ module JSONAPI
324
310
  @key_formatter.format(key)
325
311
  end
326
312
 
327
- def format_value(value, format, source)
313
+ def format_value(value, format)
328
314
  value_formatter = JSONAPI::ValueFormatter.value_formatter_for(format)
329
- value_formatter.format(value, source)
315
+ value_formatter.format(value)
330
316
  end
331
317
  end
332
318
  end
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Resources
3
- VERSION = "0.3.3"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -0,0 +1,87 @@
1
+ module JSONAPI
2
+ class ResponseDocument
3
+ def initialize(operation_results, options = {})
4
+ @operation_results = operation_results
5
+ @options = options
6
+
7
+ @key_formatter = @options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
8
+ end
9
+
10
+ def contents
11
+ hash = results_to_hash
12
+
13
+ meta = top_level_meta
14
+ hash.merge!(meta: meta) unless meta.empty?
15
+
16
+ hash
17
+ end
18
+
19
+ def status
20
+ if @operation_results.has_errors?
21
+ @operation_results.all_errors[0].status
22
+ else
23
+ @operation_results.results[0].code
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def serializer
30
+ @serializer ||= @options.fetch(:resource_serializer_klass, JSONAPI::ResourceSerializer).new(
31
+ @options.fetch(:primary_resource_klass),
32
+ include_directives: @options[:include_directives],
33
+ fields: @options[:fields],
34
+ base_url: @options.fetch(:base_url, ''),
35
+ key_formatter: @key_formatter,
36
+ route_formatter: @options.fetch(:route_formatter, JSONAPI.configuration.route_formatter)
37
+ )
38
+ end
39
+
40
+ def top_level_meta
41
+ meta = @options.fetch(:base_meta, {})
42
+
43
+ meta.merge!(@operation_results.meta)
44
+
45
+ @operation_results.results.each do |result|
46
+ meta.merge!(result.meta)
47
+ end
48
+
49
+ meta.deep_transform_keys { |key| @key_formatter.format(key) }
50
+ end
51
+
52
+ def results_to_hash
53
+ if @operation_results.has_errors?
54
+ {errors: @operation_results.all_errors}
55
+ else
56
+ if @operation_results.results.length == 1
57
+ result = @operation_results.results[0]
58
+
59
+ case result
60
+ when JSONAPI::ResourceOperationResult
61
+ serializer.serialize_to_hash(result.resource)
62
+ when JSONAPI::ResourcesOperationResult
63
+ serializer.serialize_to_hash(result.resources)
64
+ when JSONAPI::LinksObjectOperationResult
65
+ serializer.serialize_to_links_hash(result.parent_resource,
66
+ result.association)
67
+ when JSONAPI::OperationResult
68
+ {}
69
+ end
70
+
71
+ elsif @operation_results.results.length > 1
72
+ resources = []
73
+ @operation_results.results.each do |result|
74
+ case result
75
+ when JSONAPI::ResourceOperationResult
76
+ resources.push(result.resource)
77
+ when JSONAPI::ResourcesOperationResult
78
+ resources.concat(result.resources)
79
+ end
80
+ end
81
+
82
+ serializer.serialize_to_hash(resources)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -58,8 +58,7 @@ module ActionDispatch
58
58
  options[:controller] ||= @resource_type
59
59
  options.merge!(res.routing_resource_options)
60
60
 
61
- # Route using the primary_key. Can be overridden using routing_resource_options
62
- options[:param] ||= res._primary_key
61
+ options[:param] = :id
63
62
 
64
63
  options[:path] = format_route(@resource_type)
65
64
 
@@ -91,21 +90,22 @@ module ActionDispatch
91
90
  options = links.extract_options!.dup
92
91
 
93
92
  res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)
93
+ options[:controller] ||= res._type.to_s
94
94
 
95
95
  methods = links_methods(options)
96
96
 
97
97
  if methods.include?(:show)
98
- match "links/#{formatted_association_name}", controller: res._type.to_s,
98
+ match "relationships/#{formatted_association_name}", controller: options[:controller],
99
99
  action: 'show_association', association: link_type.to_s, via: [:get]
100
100
  end
101
101
 
102
102
  if methods.include?(:update)
103
- match "links/#{formatted_association_name}", controller: res._type.to_s,
103
+ match "relationships/#{formatted_association_name}", controller: options[:controller],
104
104
  action: 'update_association', association: link_type.to_s, via: [:put, :patch]
105
105
  end
106
106
 
107
107
  if methods.include?(:destroy)
108
- match "links/#{formatted_association_name}", controller: res._type.to_s,
108
+ match "relationships/#{formatted_association_name}", controller: options[:controller],
109
109
  action: 'destroy_association', association: link_type.to_s, via: [:delete]
110
110
  end
111
111
  end
@@ -116,54 +116,60 @@ module ActionDispatch
116
116
  options = links.extract_options!.dup
117
117
 
118
118
  res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)
119
+ options[:controller] ||= res._type.to_s
119
120
 
120
121
  methods = links_methods(options)
121
122
 
122
123
  if methods.include?(:show)
123
- match "links/#{formatted_association_name}", controller: res._type.to_s,
124
+ match "relationships/#{formatted_association_name}", controller: options[:controller],
124
125
  action: 'show_association', association: link_type.to_s, via: [:get]
125
126
  end
126
127
 
127
128
  if methods.include?(:create)
128
- match "links/#{formatted_association_name}", controller: res._type.to_s,
129
+ match "relationships/#{formatted_association_name}", controller: options[:controller],
129
130
  action: 'create_association', association: link_type.to_s, via: [:post]
130
131
  end
131
132
 
132
133
  if methods.include?(:update)
133
- match "links/#{formatted_association_name}", controller: res._type.to_s,
134
+ match "relationships/#{formatted_association_name}", controller: options[:controller],
134
135
  action: 'update_association', association: link_type.to_s, via: [:put, :patch]
135
136
  end
136
137
 
137
138
  if methods.include?(:destroy)
138
- match "links/#{formatted_association_name}/:keys", controller: res._type.to_s,
139
+ match "relationships/#{formatted_association_name}/:keys", controller: options[:controller],
139
140
  action: 'destroy_association', association: link_type.to_s, via: [:delete]
140
141
  end
141
142
  end
142
143
 
143
144
  def jsonapi_related_resource(*association)
144
145
  source = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)
146
+ options = association.extract_options!.dup
145
147
 
146
148
  association_name = association.first
147
149
  association = source._associations[association_name]
148
150
 
149
151
  formatted_association_name = format_route(association.name)
150
152
  related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(association.class_name.underscore.pluralize))
153
+ options[:controller] ||= related_resource._type.to_s
151
154
 
152
- match "#{formatted_association_name}", controller: related_resource._type.to_s,
155
+
156
+ match "#{formatted_association_name}", controller: options[:controller],
153
157
  association: association.name, source: resource_type_with_module_prefix(source._type),
154
158
  action: 'get_related_resource', via: [:get]
155
159
  end
156
160
 
157
161
  def jsonapi_related_resources(*association)
158
162
  source = JSONAPI::Resource.resource_for(resource_type_with_module_prefix)
163
+ options = association.extract_options!.dup
159
164
 
160
165
  association_name = association.first
161
166
  association = source._associations[association_name]
162
167
 
163
168
  formatted_association_name = format_route(association.name)
164
169
  related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(association.class_name.underscore))
170
+ options[:controller] ||= related_resource._type.to_s
165
171
 
166
- match "#{formatted_association_name}", controller: related_resource._type.to_s,
172
+ match "#{formatted_association_name}", controller: options[:controller],
167
173
  association: association.name, source: resource_type_with_module_prefix(source._type),
168
174
  action: 'get_related_resources', via: [:get]
169
175
  end
@@ -4,10 +4,6 @@ def set_content_type_header!
4
4
  @request.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
5
5
  end
6
6
 
7
- class ConfigControllerTest < ActionController::TestCase
8
-
9
- end
10
-
11
7
  class PostsControllerTest < ActionController::TestCase
12
8
  def test_index
13
9
  get :index
@@ -57,16 +53,32 @@ class PostsControllerTest < ActionController::TestCase
57
53
  assert_equal 1, json_response['included'].size
58
54
  end
59
55
 
56
+ def test_index_include_one_level_query_count
57
+ query_count = count_queries do
58
+ get :index, {include: 'author'}
59
+ end
60
+ assert_response :success
61
+ assert_equal 2, query_count
62
+ end
63
+
64
+ def test_index_include_two_levels_query_count
65
+ query_count = count_queries do
66
+ get :index, {include: 'author,author.comments'}
67
+ end
68
+ assert_response :success
69
+ assert_equal 3, query_count
70
+ end
71
+
60
72
  def test_index_filter_by_ids_and_fields
61
73
  get :index, {filter: {id: '1,2'}, fields: {posts: 'id,title,author'}}
62
74
  assert_response :success
63
75
  assert_equal 2, json_response['data'].size
64
76
 
65
- # type, id, title, links
66
- assert_equal 4, json_response['data'][0].size
77
+ # type, id, links, attributes, relationships
78
+ assert_equal 5, json_response['data'][0].size
67
79
  assert json_response['data'][0].has_key?('type')
68
80
  assert json_response['data'][0].has_key?('id')
69
- assert json_response['data'][0].has_key?('title')
81
+ assert json_response['data'][0]['attributes'].has_key?('title')
70
82
  assert json_response['data'][0].has_key?('links')
71
83
  end
72
84
 
@@ -75,11 +87,11 @@ class PostsControllerTest < ActionController::TestCase
75
87
  assert_response :success
76
88
  assert_equal 2, json_response['data'].size
77
89
 
78
- # type, id, title, links
79
- assert_equal 4, json_response['data'][0].size
90
+ # type, id, links, attributes, relationships
91
+ assert_equal 5, json_response['data'][0].size
80
92
  assert json_response['data'][0].has_key?('type')
81
93
  assert json_response['data'][0].has_key?('id')
82
- assert json_response['data'][0].has_key?('title')
94
+ assert json_response['data'][0]['attributes'].has_key?('title')
83
95
  assert json_response['data'][0].has_key?('links')
84
96
  end
85
97
 
@@ -94,11 +106,11 @@ class PostsControllerTest < ActionController::TestCase
94
106
  assert_response :success
95
107
  assert_equal 2, json_response['data'].size
96
108
 
97
- # links, id, type
98
- assert_equal 3, json_response['data'][0].size
109
+ # type, id, links, relationships
110
+ assert_equal 4, json_response['data'][0].size
99
111
  assert json_response['data'][0].has_key?('type')
100
112
  assert json_response['data'][0].has_key?('id')
101
- assert json_response['data'][0]['links'].has_key?('author')
113
+ assert json_response['data'][0]['relationships'].has_key?('author')
102
114
  end
103
115
 
104
116
  def test_filter_association_single
@@ -172,58 +184,35 @@ class PostsControllerTest < ActionController::TestCase
172
184
  end
173
185
 
174
186
  def test_sorting_asc
175
- get :index, {sort: '+title'}
176
-
177
- assert_response :success
178
- assert_equal "A First Post", json_response['data'][0]['title']
179
- end
180
-
181
- # Plus symbol may be replaced by a space
182
- def test_sorting_asc_with_space
183
- get :index, {sort: ' title'}
184
-
185
- assert_response :success
186
- assert_equal "A First Post", json_response['data'][0]['title']
187
- end
188
-
189
- # Plus symbol may be sent uriencoded ('%2b')
190
- def test_sorting_asc_with_encoded_plus
191
- get :index, {sort: '%2btitle'}
187
+ get :index, {sort: 'title'}
192
188
 
193
189
  assert_response :success
194
- assert_equal "A First Post", json_response['data'][0]['title']
190
+ assert_equal "A First Post", json_response['data'][0]['attributes']['title']
195
191
  end
196
192
 
197
193
  def test_sorting_desc
198
194
  get :index, {sort: '-title'}
199
195
 
200
196
  assert_response :success
201
- assert_equal "Update This Later - Multiple", json_response['data'][0]['title']
197
+ assert_equal "Update This Later - Multiple", json_response['data'][0]['attributes']['title']
202
198
  end
203
199
 
204
200
  def test_sorting_by_multiple_fields
205
- get :index, {sort: '+title,+body'}
201
+ get :index, {sort: 'title,body'}
206
202
 
207
203
  assert_response :success
208
204
  assert_equal '14', json_response['data'][0]['id']
209
205
  end
210
206
 
211
207
  def test_invalid_sort_param
212
- get :index, {sort: '+asdfg'}
208
+ get :index, {sort: 'asdfg'}
213
209
 
214
210
  assert_response :bad_request
215
211
  assert_match /asdfg is not a valid sort criteria for post/, response.body
216
212
  end
217
213
 
218
- def test_invalid_sort_param_missing_direction
219
- get :index, {sort: 'title'}
220
-
221
- assert_response :bad_request
222
- assert_match /title must start with a direction/, response.body
223
- end
224
-
225
214
  def test_excluded_sort_param
226
- get :index, {sort: '+id'}
215
+ get :index, {sort: 'id'}
227
216
 
228
217
  assert_response :bad_request
229
218
  assert_match /id is not a valid sort criteria for post/, response.body
@@ -233,8 +222,8 @@ class PostsControllerTest < ActionController::TestCase
233
222
  get :show, {id: '1'}
234
223
  assert_response :success
235
224
  assert json_response['data'].is_a?(Hash)
236
- assert_equal 'New post', json_response['data']['title']
237
- assert_equal 'A body!!!', json_response['data']['body']
225
+ assert_equal 'New post', json_response['data']['attributes']['title']
226
+ assert_equal 'A body!!!', json_response['data']['attributes']['body']
238
227
  assert_nil json_response['included']
239
228
  end
240
229
 
@@ -242,11 +231,11 @@ class PostsControllerTest < ActionController::TestCase
242
231
  get :show, {id: '1', include: 'comments'}
243
232
  assert_response :success
244
233
  assert json_response['data'].is_a?(Hash)
245
- assert_equal 'New post', json_response['data']['title']
246
- assert_equal 'A body!!!', json_response['data']['body']
247
- assert_nil json_response['data']['links']['tags']['linkage']
234
+ assert_equal 'New post', json_response['data']['attributes']['title']
235
+ assert_equal 'A body!!!', json_response['data']['attributes']['body']
236
+ assert_nil json_response['data']['relationships']['tags']['data']
248
237
  assert matches_array?([{'type' => 'comments', 'id' => '1'}, {'type' => 'comments', 'id' => '2'}],
249
- json_response['data']['links']['comments']['linkage'])
238
+ json_response['data']['relationships']['comments']['data'])
250
239
  assert_equal 2, json_response['included'].size
251
240
  end
252
241
 
@@ -254,9 +243,8 @@ class PostsControllerTest < ActionController::TestCase
254
243
  get :show, {id: '1', fields: {posts: 'author'}}
255
244
  assert_response :success
256
245
  assert json_response['data'].is_a?(Hash)
257
- assert_nil json_response['data']['title']
258
- assert_nil json_response['data']['body']
259
- assert_equal '1', json_response['data']['links']['author']['linkage']['id']
246
+ assert_nil json_response['data']['attributes']
247
+ assert_equal '1', json_response['data']['relationships']['author']['data']['id']
260
248
  end
261
249
 
262
250
  def test_show_single_with_fields_string
@@ -295,19 +283,21 @@ class PostsControllerTest < ActionController::TestCase
295
283
  {
296
284
  data: {
297
285
  type: 'posts',
298
- title: 'JR is Great',
299
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
300
- links: {
301
- author: {linkage: {type: 'people', id: '3'}}
286
+ attributes: {
287
+ title: 'JR is Great',
288
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
289
+ },
290
+ relationships: {
291
+ author: {data: {type: 'people', id: '3'}}
302
292
  }
303
293
  }
304
294
  }
305
295
 
306
296
  assert_response :created
307
297
  assert json_response['data'].is_a?(Hash)
308
- assert_equal '3', json_response['data']['links']['author']['linkage']['id']
309
- assert_equal 'JR is Great', json_response['data']['title']
310
- assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['body']
298
+ assert_equal '3', json_response['data']['relationships']['author']['data']['id']
299
+ assert_equal 'JR is Great', json_response['data']['attributes']['title']
300
+ assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['attributes']['body']
311
301
  end
312
302
 
313
303
  def test_create_link_to_missing_object
@@ -316,10 +306,12 @@ class PostsControllerTest < ActionController::TestCase
316
306
  {
317
307
  data: {
318
308
  type: 'posts',
319
- title: 'JR is Great',
320
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
321
- links: {
322
- author: {linkage: {type: 'people', id: '304567'}}
309
+ attributes: {
310
+ title: 'JR is Great',
311
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
312
+ },
313
+ relationships: {
314
+ author: {data: {type: 'people', id: '304567'}}
323
315
  }
324
316
  }
325
317
  }
@@ -335,11 +327,13 @@ class PostsControllerTest < ActionController::TestCase
335
327
  {
336
328
  data: {
337
329
  type: 'posts',
338
- asdfg: 'aaaa',
339
- title: 'JR is Great',
340
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
341
- links: {
342
- author: {linkage: {type: 'people', id: '3'}}
330
+ attributes: {
331
+ asdfg: 'aaaa',
332
+ title: 'JR is Great',
333
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
334
+ },
335
+ relationships: {
336
+ author: {data: {type: 'people', id: '3'}}
343
337
  }
344
338
  }
345
339
  }
@@ -354,9 +348,11 @@ class PostsControllerTest < ActionController::TestCase
354
348
  {
355
349
  data: {
356
350
  type: 'posts',
357
- title: 'JSONAPIResources is the greatest thing...',
358
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
359
- links: {
351
+ attributes: {
352
+ title: 'JSONAPIResources is the greatest thing...',
353
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
354
+ },
355
+ relationships: {
360
356
  author: nil
361
357
  }
362
358
  }
@@ -380,18 +376,22 @@ class PostsControllerTest < ActionController::TestCase
380
376
  data: [
381
377
  {
382
378
  type: 'posts',
383
- title: 'JR is Great',
384
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
385
- links: {
386
- author: {linkage: {type: 'people', id: '3'}}
379
+ attributes: {
380
+ title: 'JR is Great',
381
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
382
+ },
383
+ relationships: {
384
+ author: {data: {type: 'people', id: '3'}}
387
385
  }
388
386
  },
389
387
  {
390
388
  type: 'posts',
391
- title: 'Ember is Great',
392
- body: 'Ember is the greatest thing since unsliced bread.',
393
- links: {
394
- author: {linkage: {type: 'people', id: '3'}}
389
+ attributes: {
390
+ title: 'Ember is Great',
391
+ body: 'Ember is the greatest thing since unsliced bread.'
392
+ },
393
+ relationships: {
394
+ author: {data: {type: 'people', id: '3'}}
395
395
  }
396
396
  }
397
397
  ]
@@ -400,7 +400,7 @@ class PostsControllerTest < ActionController::TestCase
400
400
  assert_response :created
401
401
  assert json_response['data'].is_a?(Array)
402
402
  assert_equal json_response['data'].size, 2
403
- assert_equal json_response['data'][0]['links']['author']['linkage']['id'], '3'
403
+ assert_equal json_response['data'][0]['relationships']['author']['data']['id'], '3'
404
404
  assert_match /JR is Great/, response.body
405
405
  assert_match /Ember is Great/, response.body
406
406
  end
@@ -412,18 +412,22 @@ class PostsControllerTest < ActionController::TestCase
412
412
  data: [
413
413
  {
414
414
  type: 'posts',
415
- Title: 'JR is Great',
416
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
417
- links: {
418
- author: {linkage: {type: 'people', id: '3'}}
415
+ attributes: {
416
+ Title: 'JR is Great',
417
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
418
+ },
419
+ relationships: {
420
+ author: {data: {type: 'people', id: '3'}}
419
421
  }
420
422
  },
421
423
  {
422
424
  type: 'posts',
423
- title: 'Ember is Great',
424
- BODY: 'Ember is the greatest thing since unsliced bread.',
425
- links: {
426
- author: {linkage: {type: 'people', id: '3'}}
425
+ attributes: {
426
+ title: 'Ember is Great',
427
+ BODY: 'Ember is the greatest thing since unsliced bread.'
428
+ },
429
+ relationships: {
430
+ author: {data: {type: 'people', id: '3'}}
427
431
  }
428
432
  }
429
433
  ]
@@ -439,10 +443,12 @@ class PostsControllerTest < ActionController::TestCase
439
443
  {
440
444
  data_spelled_wrong: {
441
445
  type: 'posts',
442
- title: 'JR is Great',
443
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
444
- links: {
445
- author: {linkage: {type: 'people', id: '3'}}
446
+ attributes: {
447
+ title: 'JR is Great',
448
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
449
+ },
450
+ relationships: {
451
+ author: {data: {type: 'people', id: '3'}}
446
452
  }
447
453
  }
448
454
  }
@@ -457,10 +463,12 @@ class PostsControllerTest < ActionController::TestCase
457
463
  {
458
464
  data: {
459
465
  type: 'posts_spelled_wrong',
460
- title: 'JR is Great',
461
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
462
- links: {
463
- author: {linkage: {type: 'people', id: '3'}}
466
+ attributes: {
467
+ title: 'JR is Great',
468
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
469
+ },
470
+ relationships: {
471
+ author: {data: {type: 'people', id: '3'}}
464
472
  }
465
473
  }
466
474
  }
@@ -474,10 +482,12 @@ class PostsControllerTest < ActionController::TestCase
474
482
  post :create,
475
483
  {
476
484
  data: {
477
- title: 'JR is Great',
478
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
479
- links: {
480
- author: {linkage: {type: 'people', id: '3'}}
485
+ attributes: {
486
+ title: 'JR is Great',
487
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
488
+ },
489
+ relationships: {
490
+ author: {data: {type: 'people', id: '3'}}
481
491
  }
482
492
  }
483
493
  }
@@ -492,10 +502,12 @@ class PostsControllerTest < ActionController::TestCase
492
502
  {
493
503
  data: {
494
504
  type: 'posts',
495
- subject: 'JR is Great',
496
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
497
- links: {
498
- author: {linkage: {type: 'people', id: '3'}}
505
+ attributes: {
506
+ subject: 'JR is Great',
507
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
508
+ },
509
+ relationships: {
510
+ author: {data: {type: 'people', id: '3'}}
499
511
  }
500
512
  }
501
513
  }
@@ -510,20 +522,22 @@ class PostsControllerTest < ActionController::TestCase
510
522
  {
511
523
  data: {
512
524
  type: 'posts',
513
- title: 'JR is Great',
514
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
515
- links: {
516
- author: {linkage: {type: 'people', id: '3'}},
517
- tags: {linkage: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
525
+ attributes: {
526
+ title: 'JR is Great',
527
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
528
+ },
529
+ relationships: {
530
+ author: {data: {type: 'people', id: '3'}},
531
+ tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
518
532
  }
519
533
  }
520
534
  }
521
535
 
522
536
  assert_response :created
523
537
  assert json_response['data'].is_a?(Hash)
524
- assert_equal '3', json_response['data']['links']['author']['linkage']['id']
525
- assert_equal 'JR is Great', json_response['data']['title']
526
- assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['body']
538
+ assert_equal '3', json_response['data']['relationships']['author']['data']['id']
539
+ assert_equal 'JR is Great', json_response['data']['attributes']['title']
540
+ assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['attributes']['body']
527
541
  end
528
542
 
529
543
  def test_create_with_links_has_many_array
@@ -532,20 +546,22 @@ class PostsControllerTest < ActionController::TestCase
532
546
  {
533
547
  data: {
534
548
  type: 'posts',
535
- title: 'JR is Great',
536
- body: 'JSONAPIResources is the greatest thing since unsliced bread.',
537
- links: {
538
- author: {linkage: {type: 'people', id: '3'}},
539
- tags: {linkage: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
549
+ attributes: {
550
+ title: 'JR is Great',
551
+ body: 'JSONAPIResources is the greatest thing since unsliced bread.'
552
+ },
553
+ relationships: {
554
+ author: {data: {type: 'people', id: '3'}},
555
+ tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
540
556
  }
541
557
  }
542
558
  }
543
559
 
544
560
  assert_response :created
545
561
  assert json_response['data'].is_a?(Hash)
546
- assert_equal '3', json_response['data']['links']['author']['linkage']['id']
547
- assert_equal 'JR is Great', json_response['data']['title']
548
- assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['body']
562
+ assert_equal '3', json_response['data']['relationships']['author']['data']['id']
563
+ assert_equal 'JR is Great', json_response['data']['attributes']['title']
564
+ assert_equal 'JSONAPIResources is the greatest thing since unsliced bread.', json_response['data']['attributes']['body']
549
565
  end
550
566
 
551
567
  def test_create_with_links_include_and_fields
@@ -554,11 +570,13 @@ class PostsControllerTest < ActionController::TestCase
554
570
  {
555
571
  data: {
556
572
  type: 'posts',
557
- title: 'JR is Great!',
558
- body: 'JSONAPIResources is the greatest thing since unsliced bread!',
559
- links: {
560
- author: {linkage: {type: 'people', id: '3'}},
561
- tags: {linkage: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
573
+ attributes: {
574
+ title: 'JR is Great!',
575
+ body: 'JSONAPIResources is the greatest thing since unsliced bread!'
576
+ },
577
+ relationships: {
578
+ author: {data: {type: 'people', id: '3'}},
579
+ tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
562
580
  }
563
581
  },
564
582
  include: 'author,author.posts',
@@ -567,8 +585,8 @@ class PostsControllerTest < ActionController::TestCase
567
585
 
568
586
  assert_response :created
569
587
  assert json_response['data'].is_a?(Hash)
570
- assert_equal '3', json_response['data']['links']['author']['linkage']['id']
571
- assert_equal 'JR is Great!', json_response['data']['title']
588
+ assert_equal '3', json_response['data']['relationships']['author']['data']['id']
589
+ assert_equal 'JR is Great!', json_response['data']['attributes']['title']
572
590
  assert_not_nil json_response['included'].size
573
591
  end
574
592
 
@@ -582,10 +600,12 @@ class PostsControllerTest < ActionController::TestCase
582
600
  data: {
583
601
  id: '3',
584
602
  type: 'posts',
585
- title: 'A great new Post',
586
- links: {
587
- section: {linkage: {type: 'sections', id: "#{javascript.id}"}},
588
- tags: {linkage: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
603
+ attributes: {
604
+ title: 'A great new Post'
605
+ },
606
+ relationships: {
607
+ section: {data: {type: 'sections', id: "#{javascript.id}"}},
608
+ tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
589
609
  }
590
610
  },
591
611
  include: 'tags'
@@ -593,12 +613,12 @@ class PostsControllerTest < ActionController::TestCase
593
613
 
594
614
  assert_response :success
595
615
  assert json_response['data'].is_a?(Hash)
596
- assert_equal '3', json_response['data']['links']['author']['linkage']['id']
597
- assert_equal javascript.id.to_s, json_response['data']['links']['section']['linkage']['id']
598
- assert_equal 'A great new Post', json_response['data']['title']
599
- assert_equal 'AAAA', json_response['data']['body']
616
+ assert_equal '3', json_response['data']['relationships']['author']['data']['id']
617
+ assert_equal javascript.id.to_s, json_response['data']['relationships']['section']['data']['id']
618
+ assert_equal 'A great new Post', json_response['data']['attributes']['title']
619
+ assert_equal 'AAAA', json_response['data']['attributes']['body']
600
620
  assert matches_array?([{'type' => 'tags', 'id' => '3'}, {'type' => 'tags', 'id' => '4'}],
601
- json_response['data']['links']['tags']['linkage'])
621
+ json_response['data']['relationships']['tags']['data'])
602
622
  end
603
623
 
604
624
  def test_update_remove_links
@@ -609,10 +629,12 @@ class PostsControllerTest < ActionController::TestCase
609
629
  data: {
610
630
  id: '3',
611
631
  type: 'posts',
612
- title: 'A great new Post',
613
- links: {
614
- section: {linkage: {type: 'sections', id: 1}},
615
- tags: {linkage: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
632
+ attributes: {
633
+ title: 'A great new Post'
634
+ },
635
+ relationships: {
636
+ section: {data: {type: 'sections', id: 1}},
637
+ tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
616
638
  }
617
639
  },
618
640
  include: 'tags'
@@ -626,8 +648,10 @@ class PostsControllerTest < ActionController::TestCase
626
648
  data: {
627
649
  type: 'posts',
628
650
  id: 3,
629
- title: 'A great new Post',
630
- links: {
651
+ attributes: {
652
+ title: 'A great new Post'
653
+ },
654
+ relationships: {
631
655
  section: nil,
632
656
  tags: []
633
657
  }
@@ -637,12 +661,12 @@ class PostsControllerTest < ActionController::TestCase
637
661
 
638
662
  assert_response :success
639
663
  assert json_response['data'].is_a?(Hash)
640
- assert_equal '3', json_response['data']['links']['author']['linkage']['id']
641
- assert_equal nil, json_response['data']['links']['section']['linkage']
642
- assert_equal 'A great new Post', json_response['data']['title']
643
- assert_equal 'AAAA', json_response['data']['body']
664
+ assert_equal '3', json_response['data']['relationships']['author']['data']['id']
665
+ assert_equal nil, json_response['data']['relationships']['section']['data']
666
+ assert_equal 'A great new Post', json_response['data']['attributes']['title']
667
+ assert_equal 'AAAA', json_response['data']['attributes']['body']
644
668
  assert matches_array?([],
645
- json_response['data']['links']['tags']['linkage'])
669
+ json_response['data']['relationships']['tags']['data'])
646
670
  end
647
671
 
648
672
  def test_update_relationship_has_one
@@ -698,7 +722,7 @@ class PostsControllerTest < ActionController::TestCase
698
722
  data: {
699
723
  type: 'posts',
700
724
  id: 3,
701
- links: {
725
+ relationships: {
702
726
  tags: nil
703
727
  }
704
728
  }
@@ -716,8 +740,8 @@ class PostsControllerTest < ActionController::TestCase
716
740
  data: {
717
741
  type: 'posts',
718
742
  id: 3,
719
- links: {
720
- tags: {linkage: {typ: 'bad link', idd: 'as'}}
743
+ relationships: {
744
+ tags: {data: {typ: 'bad link', idd: 'as'}}
721
745
  }
722
746
  }
723
747
  }
@@ -734,7 +758,7 @@ class PostsControllerTest < ActionController::TestCase
734
758
  data: {
735
759
  type: 'posts',
736
760
  id: 3,
737
- links: {
761
+ relationships: {
738
762
  tags: 'bad link'
739
763
  }
740
764
  }
@@ -744,7 +768,7 @@ class PostsControllerTest < ActionController::TestCase
744
768
  assert_match /Invalid Links Object/, response.body
745
769
  end
746
770
 
747
- def test_update_other_has_many_links_linkage_nil
771
+ def test_update_other_has_many_links_data_nil
748
772
  set_content_type_header!
749
773
  put :update,
750
774
  {
@@ -752,8 +776,8 @@ class PostsControllerTest < ActionController::TestCase
752
776
  data: {
753
777
  type: 'posts',
754
778
  id: 3,
755
- links: {
756
- tags: {linkage: nil}
779
+ relationships: {
780
+ tags: {data: nil}
757
781
  }
758
782
  }
759
783
  }
@@ -979,8 +1003,10 @@ class PostsControllerTest < ActionController::TestCase
979
1003
  data: {
980
1004
  type: 'posts',
981
1005
  id: 2,
982
- title: 'A great new Post',
983
- links: {
1006
+ attributes: {
1007
+ title: 'A great new Post'
1008
+ },
1009
+ relationships: {
984
1010
  section: {type: 'sections', id: "#{javascript.id}"},
985
1011
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
986
1012
  }
@@ -1001,9 +1027,11 @@ class PostsControllerTest < ActionController::TestCase
1001
1027
  data: {
1002
1028
  type: 'posts',
1003
1029
  id: '3',
1004
- asdfg: 'aaaa',
1005
- title: 'A great new Post',
1006
- links: {
1030
+ attributes: {
1031
+ asdfg: 'aaaa',
1032
+ title: 'A great new Post'
1033
+ },
1034
+ relationships: {
1007
1035
  section: {type: 'sections', id: "#{javascript.id}"},
1008
1036
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
1009
1037
  }
@@ -1024,8 +1052,10 @@ class PostsControllerTest < ActionController::TestCase
1024
1052
  data: {
1025
1053
  type: 'posts',
1026
1054
  id: '3',
1027
- title: 'A great new Post',
1028
- links: {
1055
+ attributes: {
1056
+ title: 'A great new Post'
1057
+ },
1058
+ relationships: {
1029
1059
  asdfg: 'aaaa',
1030
1060
  section: {type: 'sections', id: "#{javascript.id}"},
1031
1061
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
@@ -1046,8 +1076,10 @@ class PostsControllerTest < ActionController::TestCase
1046
1076
  id: 3,
1047
1077
  data_spelled_wrong: {
1048
1078
  type: 'posts',
1049
- title: 'A great new Post',
1050
- links: {
1079
+ attributes: {
1080
+ title: 'A great new Post'
1081
+ },
1082
+ relationships: {
1051
1083
  section: {type: 'sections', id: "#{javascript.id}"},
1052
1084
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
1053
1085
  }
@@ -1066,7 +1098,9 @@ class PostsControllerTest < ActionController::TestCase
1066
1098
  id: 3,
1067
1099
  data: {
1068
1100
  type: 'posts',
1069
- title: 'A great new Post'
1101
+ attributes: {
1102
+ title: 'A great new Post'
1103
+ }
1070
1104
  }
1071
1105
  }
1072
1106
 
@@ -1084,8 +1118,10 @@ class PostsControllerTest < ActionController::TestCase
1084
1118
  data: {
1085
1119
  id: '3',
1086
1120
  type_spelled_wrong: 'posts',
1087
- title: 'A great new Post',
1088
- links: {
1121
+ attributes: {
1122
+ title: 'A great new Post'
1123
+ },
1124
+ relationships: {
1089
1125
  section: {type: 'sections', id: "#{javascript.id}"},
1090
1126
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
1091
1127
  }
@@ -1096,6 +1132,31 @@ class PostsControllerTest < ActionController::TestCase
1096
1132
  assert_match /The required parameter, type, is missing./, response.body
1097
1133
  end
1098
1134
 
1135
+ def test_update_unknown_key
1136
+ set_content_type_header!
1137
+ javascript = Section.find_by(name: 'javascript')
1138
+
1139
+ put :update,
1140
+ {
1141
+ id: 3,
1142
+ data: {
1143
+ id: '3',
1144
+ type: 'posts',
1145
+ body: 'asdfg',
1146
+ attributes: {
1147
+ title: 'A great new Post'
1148
+ },
1149
+ relationships: {
1150
+ section: {type: 'sections', id: "#{javascript.id}"},
1151
+ tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ assert_response :bad_request
1157
+ assert_match /body is not allowed/, response.body
1158
+ end
1159
+
1099
1160
  def test_update_multiple
1100
1161
  set_content_type_header!
1101
1162
  javascript = Section.find_by(name: 'javascript')
@@ -1107,19 +1168,23 @@ class PostsControllerTest < ActionController::TestCase
1107
1168
  {
1108
1169
  type: 'posts',
1109
1170
  id: 3,
1110
- title: 'A great new Post QWERTY',
1111
- links: {
1112
- section: {linkage: {type: 'sections', id: "#{javascript.id}"}},
1113
- tags: {linkage: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
1171
+ attributes: {
1172
+ title: 'A great new Post QWERTY'
1173
+ },
1174
+ relationships: {
1175
+ section: {data: {type: 'sections', id: "#{javascript.id}"}},
1176
+ tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
1114
1177
  }
1115
1178
  },
1116
1179
  {
1117
1180
  type: 'posts',
1118
1181
  id: 16,
1119
- title: 'A great new Post ASDFG',
1120
- links: {
1121
- section: {linkage: {type: 'sections', id: "#{javascript.id}"}},
1122
- tags: {linkage: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
1182
+ attributes: {
1183
+ title: 'A great new Post ASDFG'
1184
+ },
1185
+ relationships: {
1186
+ section: {data: {type: 'sections', id: "#{javascript.id}"}},
1187
+ tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
1123
1188
  }
1124
1189
  }
1125
1190
  ],
@@ -1128,19 +1193,19 @@ class PostsControllerTest < ActionController::TestCase
1128
1193
 
1129
1194
  assert_response :success
1130
1195
  assert_equal json_response['data'].size, 2
1131
- assert_equal json_response['data'][0]['links']['author']['linkage']['id'], '3'
1132
- assert_equal json_response['data'][0]['links']['section']['linkage']['id'], javascript.id.to_s
1133
- assert_equal json_response['data'][0]['title'], 'A great new Post QWERTY'
1134
- assert_equal json_response['data'][0]['body'], 'AAAA'
1196
+ assert_equal json_response['data'][0]['relationships']['author']['data']['id'], '3'
1197
+ assert_equal json_response['data'][0]['relationships']['section']['data']['id'], javascript.id.to_s
1198
+ assert_equal json_response['data'][0]['attributes']['title'], 'A great new Post QWERTY'
1199
+ assert_equal json_response['data'][0]['attributes']['body'], 'AAAA'
1135
1200
  assert matches_array?([{'type' => 'tags', 'id' => '3'}, {'type' => 'tags', 'id' => '4'}],
1136
- json_response['data'][0]['links']['tags']['linkage'])
1201
+ json_response['data'][0]['relationships']['tags']['data'])
1137
1202
 
1138
- assert_equal json_response['data'][1]['links']['author']['linkage']['id'], '3'
1139
- assert_equal json_response['data'][1]['links']['section']['linkage']['id'], javascript.id.to_s
1140
- assert_equal json_response['data'][1]['title'], 'A great new Post ASDFG'
1141
- assert_equal json_response['data'][1]['body'], 'Not First!!!!'
1203
+ assert_equal json_response['data'][1]['relationships']['author']['data']['id'], '3'
1204
+ assert_equal json_response['data'][1]['relationships']['section']['data']['id'], javascript.id.to_s
1205
+ assert_equal json_response['data'][1]['attributes']['title'], 'A great new Post ASDFG'
1206
+ assert_equal json_response['data'][1]['attributes']['body'], 'Not First!!!!'
1142
1207
  assert matches_array?([{'type' => 'tags', 'id' => '3'}, {'type' => 'tags', 'id' => '4'}],
1143
- json_response['data'][1]['links']['tags']['linkage'])
1208
+ json_response['data'][1]['relationships']['tags']['data'])
1144
1209
  end
1145
1210
 
1146
1211
  def test_update_multiple_missing_keys
@@ -1153,16 +1218,20 @@ class PostsControllerTest < ActionController::TestCase
1153
1218
  data: [
1154
1219
  {
1155
1220
  type: 'posts',
1156
- title: 'A great new Post ASDFG',
1157
- links: {
1221
+ attributes: {
1222
+ title: 'A great new Post ASDFG'
1223
+ },
1224
+ relationships: {
1158
1225
  section: {type: 'sections', id: "#{javascript.id}"},
1159
1226
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
1160
1227
  }
1161
1228
  },
1162
1229
  {
1163
1230
  type: 'posts',
1164
- title: 'A great new Post QWERTY',
1165
- links: {
1231
+ attributes: {
1232
+ title: 'A great new Post QWERTY'
1233
+ },
1234
+ relationships: {
1166
1235
  section: {type: 'sections', id: "#{javascript.id}"},
1167
1236
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
1168
1237
  }
@@ -1184,19 +1253,23 @@ class PostsControllerTest < ActionController::TestCase
1184
1253
  {
1185
1254
  type: 'posts',
1186
1255
  id: 3,
1187
- title: 'A great new Post ASDFG',
1188
- links: {
1189
- section: {linkage: {type: 'sections', id: "#{javascript.id}"}},
1190
- tags: {linkage: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
1256
+ attributes: {
1257
+ title: 'A great new Post ASDFG'
1258
+ },
1259
+ relationships: {
1260
+ section: {data: {type: 'sections', id: "#{javascript.id}"}},
1261
+ tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
1191
1262
  }
1192
1263
  },
1193
1264
  {
1194
1265
  type: 'posts',
1195
1266
  id: 8,
1196
- title: 'A great new Post QWERTY',
1197
- links: {
1198
- section: {linkage: {type: 'sections', id: "#{javascript.id}"}},
1199
- tags: {linkage: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
1267
+ attributes: {
1268
+ title: 'A great new Post QWERTY'
1269
+ },
1270
+ relationships: {
1271
+ section: {data: {type: 'sections', id: "#{javascript.id}"}},
1272
+ tags: {data: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]}
1200
1273
  }
1201
1274
  }
1202
1275
  ]}
@@ -1216,8 +1289,10 @@ class PostsControllerTest < ActionController::TestCase
1216
1289
  {
1217
1290
  type: 'posts',
1218
1291
  id: 3,
1219
- title: 'A great new Post QWERTY',
1220
- links: {
1292
+ attributes: {
1293
+ title: 'A great new Post QWERTY'
1294
+ },
1295
+ relationships: {
1221
1296
  section: {type: 'sections', id: "#{javascript.id}"},
1222
1297
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
1223
1298
  }
@@ -1225,8 +1300,10 @@ class PostsControllerTest < ActionController::TestCase
1225
1300
  {
1226
1301
  type: 'posts',
1227
1302
  id: 9,
1228
- title: 'A great new Post ASDFG',
1229
- links: {
1303
+ attributes: {
1304
+ title: 'A great new Post ASDFG'
1305
+ },
1306
+ relationships: {
1230
1307
  section: {type: 'sections', id: "#{javascript.id}"},
1231
1308
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
1232
1309
  }
@@ -1245,8 +1322,10 @@ class PostsControllerTest < ActionController::TestCase
1245
1322
  data: {
1246
1323
  type: 'posts',
1247
1324
  id: '3',
1248
- subject: 'A great new Post',
1249
- links: {
1325
+ attributes: {
1326
+ subject: 'A great new Post'
1327
+ },
1328
+ relationships: {
1250
1329
  author: {type: 'people', id: '1'},
1251
1330
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
1252
1331
  }
@@ -1265,7 +1344,9 @@ class PostsControllerTest < ActionController::TestCase
1265
1344
  id: 3,
1266
1345
  data: {
1267
1346
  type: 'posts',
1268
- subject: 'A great new Post',
1347
+ attributes: {
1348
+ subject: 'A great new Post'
1349
+ },
1269
1350
  linked_objects: {
1270
1351
  author: {type: 'people', id: '1'},
1271
1352
  tags: [{type: 'tags', id: 3}, {type: 'tags', id: 4}]
@@ -1313,7 +1394,7 @@ class PostsControllerTest < ActionController::TestCase
1313
1394
  id: '1'
1314
1395
  },
1315
1396
  links: {
1316
- self: 'http://test.host/posts/1/links/author',
1397
+ self: 'http://test.host/posts/1/relationships/author',
1317
1398
  related: 'http://test.host/posts/1/author'
1318
1399
  }
1319
1400
  }
@@ -1328,7 +1409,7 @@ class PostsControllerTest < ActionController::TestCase
1328
1409
  {type: 'tags', id: '5'}
1329
1410
  ],
1330
1411
  links: {
1331
- self: 'http://test.host/posts/2/links/tags',
1412
+ self: 'http://test.host/posts/2/relationships/tags',
1332
1413
  related: 'http://test.host/posts/2/tags'
1333
1414
  }
1334
1415
  }
@@ -1347,7 +1428,7 @@ class PostsControllerTest < ActionController::TestCase
1347
1428
  {
1348
1429
  data: nil,
1349
1430
  links: {
1350
- self: 'http://test.host/posts/17/links/author',
1431
+ self: 'http://test.host/posts/17/relationships/author',
1351
1432
  related: 'http://test.host/posts/17/author'
1352
1433
  }
1353
1434
  }
@@ -1356,10 +1437,10 @@ end
1356
1437
 
1357
1438
  class TagsControllerTest < ActionController::TestCase
1358
1439
  def test_tags_index
1359
- get :index, {filter: {id: '6,7,8,9'}, include: 'posts,posts.tags,posts.author.posts'}
1440
+ get :index, {filter: {id: '6,7,8,9'}, include: 'posts.tags,posts.author.posts'}
1360
1441
  assert_response :success
1361
1442
  assert_equal 4, json_response['data'].size
1362
- assert_equal 2, json_response['included'].size
1443
+ assert_equal 3, json_response['included'].size
1363
1444
  end
1364
1445
 
1365
1446
  def test_tags_show_multiple
@@ -1369,7 +1450,7 @@ class TagsControllerTest < ActionController::TestCase
1369
1450
  end
1370
1451
 
1371
1452
  def test_tags_show_multiple_with_include
1372
- get :show, {id: '6,7,8,9', include: 'posts,posts.tags,posts.author.posts'}
1453
+ get :show, {id: '6,7,8,9', include: 'posts.tags,posts.author.posts'}
1373
1454
  assert_response :bad_request
1374
1455
  assert_match /6,7,8,9 is not a valid value for id/, response.body
1375
1456
  end
@@ -1396,7 +1477,7 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
1396
1477
  JSONAPI.configuration.use_text_errors = true
1397
1478
  get :index, {sort: 'not_in_record'}
1398
1479
  assert_response 400
1399
- assert_equal 'INVALID_SORT_FORMAT', json_response['errors'][0]['code']
1480
+ assert_equal 'INVALID_SORT_CRITERIA', json_response['errors'][0]['code']
1400
1481
  JSONAPI.configuration.use_text_errors = false
1401
1482
  end
1402
1483
 
@@ -1437,7 +1518,7 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
1437
1518
  get :show, {id: 1, include: 'isoCurrency,employee', 'fields' => {'expenseEntries' => 'transactionDate'}}
1438
1519
  assert_response :success
1439
1520
  assert json_response['data'].is_a?(Hash)
1440
- assert json_response['data'].has_key?('transactionDate')
1521
+ assert json_response['data']['attributes'].has_key?('transactionDate')
1441
1522
  assert_equal 2, json_response['included'].size
1442
1523
  end
1443
1524
 
@@ -1446,7 +1527,7 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
1446
1527
  'isoCurrencies' => 'id,name'}}
1447
1528
  assert_response :success
1448
1529
  assert json_response['data'].is_a?(Hash)
1449
- assert json_response['data'].has_key?('transactionDate')
1530
+ assert json_response['data']['attributes'].has_key?('transactionDate')
1450
1531
  assert_equal 2, json_response['included'].size
1451
1532
  end
1452
1533
 
@@ -1458,11 +1539,13 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
1458
1539
  {
1459
1540
  data: {
1460
1541
  type: 'expense_entries',
1461
- transaction_date: '2014/04/15',
1462
- cost: 50.58,
1463
- links: {
1464
- employee: {linkage: {type: 'people', id: '3'}},
1465
- iso_currency: {linkage: {type: 'iso_currencies', id: 'USD'}}
1542
+ attributes: {
1543
+ transaction_date: '2014/04/15',
1544
+ cost: 50.58
1545
+ },
1546
+ relationships: {
1547
+ employee: {data: {type: 'people', id: '3'}},
1548
+ iso_currency: {data: {type: 'iso_currencies', id: 'USD'}}
1466
1549
  }
1467
1550
  },
1468
1551
  include: 'iso_currency',
@@ -1471,9 +1554,9 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
1471
1554
 
1472
1555
  assert_response :created
1473
1556
  assert json_response['data'].is_a?(Hash)
1474
- assert_equal '3', json_response['data']['links']['employee']['linkage']['id']
1475
- assert_equal 'USD', json_response['data']['links']['iso_currency']['linkage']['id']
1476
- assert_equal '50.58', json_response['data']['cost']
1557
+ assert_equal '3', json_response['data']['relationships']['employee']['data']['id']
1558
+ assert_equal 'USD', json_response['data']['relationships']['iso_currency']['data']['id']
1559
+ assert_equal '50.58', json_response['data']['attributes']['cost']
1477
1560
 
1478
1561
  delete :destroy, {id: json_response['data']['id']}
1479
1562
  assert_response :no_content
@@ -1487,11 +1570,13 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
1487
1570
  {
1488
1571
  data: {
1489
1572
  type: 'expense_entries',
1490
- transactionDate: '2014/04/15',
1491
- cost: 50.58,
1492
- links: {
1493
- employee: {linkage: {type: 'people', id: '3'}},
1494
- isoCurrency: {linkage: {type: 'iso_currencies', id: 'USD'}}
1573
+ attributes: {
1574
+ transactionDate: '2014/04/15',
1575
+ cost: 50.58
1576
+ },
1577
+ relationships: {
1578
+ employee: {data: {type: 'people', id: '3'}},
1579
+ isoCurrency: {data: {type: 'iso_currencies', id: 'USD'}}
1495
1580
  }
1496
1581
  },
1497
1582
  include: 'isoCurrency',
@@ -1500,9 +1585,9 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
1500
1585
 
1501
1586
  assert_response :created
1502
1587
  assert json_response['data'].is_a?(Hash)
1503
- assert_equal '3', json_response['data']['links']['employee']['linkage']['id']
1504
- assert_equal 'USD', json_response['data']['links']['isoCurrency']['linkage']['id']
1505
- assert_equal '50.58', json_response['data']['cost']
1588
+ assert_equal '3', json_response['data']['relationships']['employee']['data']['id']
1589
+ assert_equal 'USD', json_response['data']['relationships']['isoCurrency']['data']['id']
1590
+ assert_equal '50.58', json_response['data']['attributes']['cost']
1506
1591
 
1507
1592
  delete :destroy, {id: json_response['data']['id']}
1508
1593
  assert_response :no_content
@@ -1516,11 +1601,13 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
1516
1601
  {
1517
1602
  data: {
1518
1603
  type: 'expense_entries',
1519
- 'transaction-date' => '2014/04/15',
1520
- cost: 50.58,
1521
- links: {
1522
- employee: {linkage: {type: 'people', id: '3'}},
1523
- 'iso-currency' => {linkage: {type: 'iso_currencies', id: 'USD'}}
1604
+ attributes: {
1605
+ 'transaction-date' => '2014/04/15',
1606
+ cost: 50.58
1607
+ },
1608
+ relationships: {
1609
+ employee: {data: {type: 'people', id: '3'}},
1610
+ 'iso-currency' => {data: {type: 'iso_currencies', id: 'USD'}}
1524
1611
  }
1525
1612
  },
1526
1613
  include: 'iso-currency',
@@ -1529,9 +1616,9 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
1529
1616
 
1530
1617
  assert_response :created
1531
1618
  assert json_response['data'].is_a?(Hash)
1532
- assert_equal '3', json_response['data']['links']['employee']['linkage']['id']
1533
- assert_equal 'USD', json_response['data']['links']['iso-currency']['linkage']['id']
1534
- assert_equal '50.58', json_response['data']['cost']
1619
+ assert_equal '3', json_response['data']['relationships']['employee']['data']['id']
1620
+ assert_equal 'USD', json_response['data']['relationships']['iso-currency']['data']['id']
1621
+ assert_equal '50.58', json_response['data']['attributes']['cost']
1535
1622
 
1536
1623
  delete :destroy, {id: json_response['data']['id']}
1537
1624
  assert_response :no_content
@@ -1544,63 +1631,104 @@ class IsoCurrenciesControllerTest < ActionController::TestCase
1544
1631
  end
1545
1632
 
1546
1633
  def test_currencies_show
1547
- get :show, {code: 'USD'}
1634
+ get :show, {id: 'USD'}
1548
1635
  assert_response :success
1549
1636
  assert json_response['data'].is_a?(Hash)
1550
1637
  end
1551
1638
 
1639
+ def test_create_currencies_client_generated_id
1640
+ set_content_type_header!
1641
+ JSONAPI.configuration.json_key_format = :underscored_route
1642
+
1643
+ post :create,
1644
+ {
1645
+ data: {
1646
+ type: 'iso_currencies',
1647
+ id: 'BTC',
1648
+ attributes: {
1649
+ name: 'Bit Coin',
1650
+ 'country_name' => 'global',
1651
+ 'minor_unit' => 'satoshi'
1652
+ }
1653
+ }
1654
+ }
1655
+
1656
+ assert_response :created
1657
+ assert_equal 'BTC', json_response['data']['id']
1658
+ assert_equal 'Bit Coin', json_response['data']['attributes']['name']
1659
+ assert_equal 'global', json_response['data']['attributes']['country_name']
1660
+ assert_equal 'satoshi', json_response['data']['attributes']['minor_unit']
1661
+
1662
+ delete :destroy, {id: json_response['data']['id']}
1663
+ assert_response :no_content
1664
+ end
1665
+
1666
+ def test_currencies_primary_key_sort
1667
+ get :index, {sort: 'id'}
1668
+ assert_response :success
1669
+ assert_equal 3, json_response['data'].size
1670
+ assert_equal 'CAD', json_response['data'][0]['id']
1671
+ assert_equal 'EUR', json_response['data'][1]['id']
1672
+ assert_equal 'USD', json_response['data'][2]['id']
1673
+ end
1674
+
1675
+ def test_currencies_code_sort
1676
+ get :index, {sort: 'code'}
1677
+ assert_response :bad_request
1678
+ end
1679
+
1552
1680
  def test_currencies_json_key_underscored_sort
1553
1681
  JSONAPI.configuration.json_key_format = :underscored_key
1554
- get :index, {sort: '+country_name'}
1682
+ get :index, {sort: 'country_name'}
1555
1683
  assert_response :success
1556
1684
  assert_equal 3, json_response['data'].size
1557
- assert_equal 'Canada', json_response['data'][0]['country_name']
1558
- assert_equal 'Euro Member Countries', json_response['data'][1]['country_name']
1559
- assert_equal 'United States', json_response['data'][2]['country_name']
1685
+ assert_equal 'Canada', json_response['data'][0]['attributes']['country_name']
1686
+ assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['country_name']
1687
+ assert_equal 'United States', json_response['data'][2]['attributes']['country_name']
1560
1688
 
1561
1689
  # reverse sort
1562
1690
  get :index, {sort: '-country_name'}
1563
1691
  assert_response :success
1564
1692
  assert_equal 3, json_response['data'].size
1565
- assert_equal 'United States', json_response['data'][0]['country_name']
1566
- assert_equal 'Euro Member Countries', json_response['data'][1]['country_name']
1567
- assert_equal 'Canada', json_response['data'][2]['country_name']
1693
+ assert_equal 'United States', json_response['data'][0]['attributes']['country_name']
1694
+ assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['country_name']
1695
+ assert_equal 'Canada', json_response['data'][2]['attributes']['country_name']
1568
1696
  end
1569
1697
 
1570
1698
  def test_currencies_json_key_dasherized_sort
1571
1699
  JSONAPI.configuration.json_key_format = :dasherized_key
1572
- get :index, {sort: '+country-name'}
1700
+ get :index, {sort: 'country-name'}
1573
1701
  assert_response :success
1574
1702
  assert_equal 3, json_response['data'].size
1575
- assert_equal 'Canada', json_response['data'][0]['country-name']
1576
- assert_equal 'Euro Member Countries', json_response['data'][1]['country-name']
1577
- assert_equal 'United States', json_response['data'][2]['country-name']
1703
+ assert_equal 'Canada', json_response['data'][0]['attributes']['country-name']
1704
+ assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['country-name']
1705
+ assert_equal 'United States', json_response['data'][2]['attributes']['country-name']
1578
1706
 
1579
1707
  # reverse sort
1580
1708
  get :index, {sort: '-country-name'}
1581
1709
  assert_response :success
1582
1710
  assert_equal 3, json_response['data'].size
1583
- assert_equal 'United States', json_response['data'][0]['country-name']
1584
- assert_equal 'Euro Member Countries', json_response['data'][1]['country-name']
1585
- assert_equal 'Canada', json_response['data'][2]['country-name']
1711
+ assert_equal 'United States', json_response['data'][0]['attributes']['country-name']
1712
+ assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['country-name']
1713
+ assert_equal 'Canada', json_response['data'][2]['attributes']['country-name']
1586
1714
  end
1587
1715
 
1588
1716
  def test_currencies_json_key_custom_json_key_sort
1589
1717
  JSONAPI.configuration.json_key_format = :upper_camelized_key
1590
- get :index, {sort: '+CountryName'}
1718
+ get :index, {sort: 'CountryName'}
1591
1719
  assert_response :success
1592
1720
  assert_equal 3, json_response['data'].size
1593
- assert_equal 'Canada', json_response['data'][0]['CountryName']
1594
- assert_equal 'Euro Member Countries', json_response['data'][1]['CountryName']
1595
- assert_equal 'United States', json_response['data'][2]['CountryName']
1721
+ assert_equal 'Canada', json_response['data'][0]['attributes']['CountryName']
1722
+ assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['CountryName']
1723
+ assert_equal 'United States', json_response['data'][2]['attributes']['CountryName']
1596
1724
 
1597
1725
  # reverse sort
1598
1726
  get :index, {sort: '-CountryName'}
1599
1727
  assert_response :success
1600
1728
  assert_equal 3, json_response['data'].size
1601
- assert_equal 'United States', json_response['data'][0]['CountryName']
1602
- assert_equal 'Euro Member Countries', json_response['data'][1]['CountryName']
1603
- assert_equal 'Canada', json_response['data'][2]['CountryName']
1729
+ assert_equal 'United States', json_response['data'][0]['attributes']['CountryName']
1730
+ assert_equal 'Euro Member Countries', json_response['data'][1]['attributes']['CountryName']
1731
+ assert_equal 'Canada', json_response['data'][2]['attributes']['CountryName']
1604
1732
  end
1605
1733
 
1606
1734
  def test_currencies_json_key_underscored_filter
@@ -1608,7 +1736,7 @@ class IsoCurrenciesControllerTest < ActionController::TestCase
1608
1736
  get :index, {filter: {country_name: 'Canada'}}
1609
1737
  assert_response :success
1610
1738
  assert_equal 1, json_response['data'].size
1611
- assert_equal 'Canada', json_response['data'][0]['country_name']
1739
+ assert_equal 'Canada', json_response['data'][0]['attributes']['country_name']
1612
1740
  end
1613
1741
 
1614
1742
  def test_currencies_json_key_camelized_key_filter
@@ -1616,7 +1744,7 @@ class IsoCurrenciesControllerTest < ActionController::TestCase
1616
1744
  get :index, {filter: {'countryName' => 'Canada'}}
1617
1745
  assert_response :success
1618
1746
  assert_equal 1, json_response['data'].size
1619
- assert_equal 'Canada', json_response['data'][0]['countryName']
1747
+ assert_equal 'Canada', json_response['data'][0]['attributes']['countryName']
1620
1748
  end
1621
1749
 
1622
1750
  def test_currencies_json_key_custom_json_key_filter
@@ -1624,7 +1752,7 @@ class IsoCurrenciesControllerTest < ActionController::TestCase
1624
1752
  get :index, {filter: {'CountryName' => 'Canada'}}
1625
1753
  assert_response :success
1626
1754
  assert_equal 1, json_response['data'].size
1627
- assert_equal 'Canada', json_response['data'][0]['CountryName']
1755
+ assert_equal 'Canada', json_response['data'][0]['attributes']['CountryName']
1628
1756
  end
1629
1757
  end
1630
1758
 
@@ -1639,9 +1767,11 @@ class PeopleControllerTest < ActionController::TestCase
1639
1767
  {
1640
1768
  data: {
1641
1769
  type: 'people',
1642
- name: 'Steve Jobs',
1643
- email: 'sj@email.zzz',
1644
- dateJoined: DateTime.parse('2014-1-30 4:20:00 UTC +00:00')
1770
+ attributes: {
1771
+ name: 'Steve Jobs',
1772
+ email: 'sj@email.zzz',
1773
+ dateJoined: DateTime.parse('2014-1-30 4:20:00 UTC +00:00')
1774
+ }
1645
1775
  }
1646
1776
  }
1647
1777
 
@@ -1657,9 +1787,9 @@ class PeopleControllerTest < ActionController::TestCase
1657
1787
  data: {
1658
1788
  id: '3',
1659
1789
  type: 'people',
1660
- links: {
1790
+ relationships: {
1661
1791
  'hair-cut' => {
1662
- linkage: {
1792
+ data: {
1663
1793
  type: 'hair-cuts',
1664
1794
  id: '1'
1665
1795
  }
@@ -1676,7 +1806,9 @@ class PeopleControllerTest < ActionController::TestCase
1676
1806
  {
1677
1807
  data: {
1678
1808
  type: 'people',
1679
- email: 'sj@email.zzz'
1809
+ attributes: {
1810
+ email: 'sj@email.zzz'
1811
+ }
1680
1812
  }
1681
1813
  }
1682
1814
 
@@ -1696,7 +1828,9 @@ class PeopleControllerTest < ActionController::TestCase
1696
1828
  data: {
1697
1829
  id: '3',
1698
1830
  type: 'people',
1699
- name: ''
1831
+ attributes: {
1832
+ name: ''
1833
+ }
1700
1834
  }
1701
1835
  }
1702
1836
 
@@ -1723,7 +1857,7 @@ class PeopleControllerTest < ActionController::TestCase
1723
1857
  assert_response :success
1724
1858
  assert_equal json_response['data'].size, 1
1725
1859
  assert_equal json_response['data'][0]['id'], '1'
1726
- assert_equal json_response['data'][0]['name'], 'Joe Author'
1860
+ assert_equal json_response['data'][0]['attributes']['name'], 'Joe Author'
1727
1861
  end
1728
1862
 
1729
1863
  def test_get_related_resource
@@ -1736,31 +1870,43 @@ class PeopleControllerTest < ActionController::TestCase
1736
1870
  data: {
1737
1871
  id: '1',
1738
1872
  type: 'people',
1739
- name: 'Joe Author',
1740
- email: 'joe@xyz.fake',
1741
- "date-joined" => '2013-08-07 16:25:00 -0400',
1873
+ attributes: {
1874
+ name: 'Joe Author',
1875
+ email: 'joe@xyz.fake',
1876
+ "date-joined" => '2013-08-07 16:25:00 -0400'
1877
+ },
1742
1878
  links: {
1743
- self: 'http://test.host/people/1',
1879
+ self: 'http://test.host/people/1'
1880
+ },
1881
+ relationships: {
1744
1882
  comments: {
1745
- self: 'http://test.host/people/1/links/comments',
1746
- related: 'http://test.host/people/1/comments'
1883
+ links: {
1884
+ self: 'http://test.host/people/1/relationships/comments',
1885
+ related: 'http://test.host/people/1/comments'
1886
+ }
1747
1887
  },
1748
1888
  posts: {
1749
- self: 'http://test.host/people/1/links/posts',
1750
- related: 'http://test.host/people/1/posts'
1889
+ links: {
1890
+ self: 'http://test.host/people/1/relationships/posts',
1891
+ related: 'http://test.host/people/1/posts'
1892
+ }
1751
1893
  },
1752
1894
  preferences: {
1753
- self: 'http://test.host/people/1/links/preferences',
1754
- related: 'http://test.host/people/1/preferences',
1755
- linkage: {
1895
+ links: {
1896
+ self: 'http://test.host/people/1/relationships/preferences',
1897
+ related: 'http://test.host/people/1/preferences'
1898
+ },
1899
+ data: {
1756
1900
  type: 'preferences',
1757
1901
  id: '1'
1758
1902
  }
1759
1903
  },
1760
1904
  "hair-cut" => {
1761
- "self" => "http://test.host/people/1/links/hair_cut",
1762
- "related" => "http://test.host/people/1/hair_cut",
1763
- "linkage" => nil
1905
+ "links" => {
1906
+ "self" => "http://test.host/people/1/relationships/hair_cut",
1907
+ "related" => "http://test.host/people/1/hair_cut"
1908
+ },
1909
+ "data" => nil
1764
1910
  }
1765
1911
  }
1766
1912
  }
@@ -1787,8 +1933,8 @@ class Api::V5::AuthorsControllerTest < ActionController::TestCase
1787
1933
  assert_equal 1, json_response['data'].size
1788
1934
  assert_equal '1', json_response['data'][0]['id']
1789
1935
  assert_equal 'authors', json_response['data'][0]['type']
1790
- assert_equal 'Joe Author', json_response['data'][0]['name']
1791
- assert_equal nil, json_response['data'][0]['email']
1936
+ assert_equal 'Joe Author', json_response['data'][0]['attributes']['name']
1937
+ assert_equal nil, json_response['data'][0]['attributes']['email']
1792
1938
  end
1793
1939
 
1794
1940
  def test_get_person_as_author_by_name_filter
@@ -1796,7 +1942,7 @@ class Api::V5::AuthorsControllerTest < ActionController::TestCase
1796
1942
  assert_response :success
1797
1943
  assert_equal 3, json_response['data'].size
1798
1944
  assert_equal '1', json_response['data'][0]['id']
1799
- assert_equal 'Joe Author', json_response['data'][0]['name']
1945
+ assert_equal 'Joe Author', json_response['data'][0]['attributes']['name']
1800
1946
  end
1801
1947
  end
1802
1948
 
@@ -1807,7 +1953,7 @@ class BreedsControllerTest < ActionController::TestCase
1807
1953
  get :index
1808
1954
  assert_response :success
1809
1955
  assert_equal '0', json_response['data'][0]['id']
1810
- assert_equal 'Persian', json_response['data'][0]['name']
1956
+ assert_equal 'Persian', json_response['data'][0]['attributes']['name']
1811
1957
  end
1812
1958
 
1813
1959
  def test_poro_show
@@ -1815,7 +1961,7 @@ class BreedsControllerTest < ActionController::TestCase
1815
1961
  assert_response :success
1816
1962
  assert json_response['data'].is_a?(Hash)
1817
1963
  assert_equal '0', json_response['data']['id']
1818
- assert_equal 'Persian', json_response['data']['name']
1964
+ assert_equal 'Persian', json_response['data']['attributes']['name']
1819
1965
  end
1820
1966
 
1821
1967
  def test_poro_show_multiple
@@ -1831,13 +1977,32 @@ class BreedsControllerTest < ActionController::TestCase
1831
1977
  {
1832
1978
  data: {
1833
1979
  type: 'breeds',
1834
- name: 'tabby'
1980
+ attributes: {
1981
+ name: 'tabby'
1982
+ }
1835
1983
  }
1836
1984
  }
1837
1985
 
1838
- assert_response :created
1986
+ assert_response :accepted
1839
1987
  assert json_response['data'].is_a?(Hash)
1840
- assert_equal 'Tabby', json_response['data']['name']
1988
+ assert_equal 'Tabby', json_response['data']['attributes']['name']
1989
+ end
1990
+
1991
+ def test_poro_create_validation_error
1992
+ set_content_type_header!
1993
+ post :create,
1994
+ {
1995
+ data: {
1996
+ type: 'breeds',
1997
+ attributes: {
1998
+ name: ''
1999
+ }
2000
+ }
2001
+ }
2002
+
2003
+ assert_equal 1, json_response['errors'].size
2004
+ assert_equal JSONAPI::VALIDATION_ERROR, json_response['errors'][0]['code']
2005
+ assert_match /name - can't be blank/, response.body
1841
2006
  end
1842
2007
 
1843
2008
  def test_poro_create_update
@@ -1846,13 +2011,15 @@ class BreedsControllerTest < ActionController::TestCase
1846
2011
  {
1847
2012
  data: {
1848
2013
  type: 'breeds',
1849
- name: 'CALIC'
2014
+ attributes: {
2015
+ name: 'CALIC'
2016
+ }
1850
2017
  }
1851
2018
  }
1852
2019
 
1853
- assert_response :created
2020
+ assert_response :accepted
1854
2021
  assert json_response['data'].is_a?(Hash)
1855
- assert_equal 'Calic', json_response['data']['name']
2022
+ assert_equal 'Calic', json_response['data']['attributes']['name']
1856
2023
 
1857
2024
  put :update,
1858
2025
  {
@@ -1860,12 +2027,14 @@ class BreedsControllerTest < ActionController::TestCase
1860
2027
  data: {
1861
2028
  id: json_response['data']['id'],
1862
2029
  type: 'breeds',
1863
- name: 'calico'
2030
+ attributes: {
2031
+ name: 'calico'
2032
+ }
1864
2033
  }
1865
2034
  }
1866
2035
  assert_response :success
1867
2036
  assert json_response['data'].is_a?(Hash)
1868
- assert_equal 'Calico', json_response['data']['name']
2037
+ assert_equal 'Calico', json_response['data']['attributes']['name']
1869
2038
  end
1870
2039
 
1871
2040
  def test_poro_delete
@@ -1888,17 +2057,17 @@ class Api::V1::PostsControllerTest < ActionController::TestCase
1888
2057
  def test_show_post_namespaced
1889
2058
  get :show, {id: '1'}
1890
2059
  assert_response :success
1891
- assert_equal 'http://test.host/api/v1/posts/1/links/writer', json_response['data']['links']['writer']['self']
2060
+ assert_equal 'http://test.host/api/v1/posts/1/relationships/writer', json_response['data']['relationships']['writer']['links']['self']
1892
2061
  end
1893
2062
 
1894
2063
  def test_show_post_namespaced_include
1895
2064
  get :show, {id: '1', include: 'writer'}
1896
2065
  assert_response :success
1897
- assert_equal '1', json_response['data']['links']['writer']['linkage']['id']
1898
- assert_nil json_response['data']['links']['tags']
2066
+ assert_equal '1', json_response['data']['relationships']['writer']['data']['id']
2067
+ assert_nil json_response['data']['relationships']['tags']
1899
2068
  assert_equal '1', json_response['included'][0]['id']
1900
2069
  assert_equal 'writers', json_response['included'][0]['type']
1901
- assert_equal 'joe@xyz.fake', json_response['included'][0]['email']
2070
+ assert_equal 'joe@xyz.fake', json_response['included'][0]['attributes']['email']
1902
2071
  end
1903
2072
 
1904
2073
  def test_index_filter_on_association_namespaced
@@ -1911,7 +2080,7 @@ class Api::V1::PostsControllerTest < ActionController::TestCase
1911
2080
  get :index, {sort: '-title'}
1912
2081
 
1913
2082
  assert_response :success
1914
- assert_equal "Update This Later - Multiple", json_response['data'][0]['title']
2083
+ assert_equal "Update This Later - Multiple", json_response['data'][0]['attributes']['title']
1915
2084
  end
1916
2085
 
1917
2086
  def test_create_simple_namespaced
@@ -1920,20 +2089,22 @@ class Api::V1::PostsControllerTest < ActionController::TestCase
1920
2089
  {
1921
2090
  data: {
1922
2091
  type: 'posts',
1923
- title: 'JR - now with Namespacing',
1924
- body: 'JSONAPIResources is the greatest thing since unsliced bread now that it has namespaced resources.',
1925
- links: {
1926
- writer: { linkage: {type: 'writers', id: '3'}}
2092
+ attributes: {
2093
+ title: 'JR - now with Namespacing',
2094
+ body: 'JSONAPIResources is the greatest thing since unsliced bread now that it has namespaced resources.'
2095
+ },
2096
+ relationships: {
2097
+ writer: { data: {type: 'writers', id: '3'}}
1927
2098
  }
1928
2099
  }
1929
2100
  }
1930
2101
 
1931
2102
  assert_response :created
1932
2103
  assert json_response['data'].is_a?(Hash)
1933
- assert_equal '3', json_response['data']['links']['writer']['linkage']['id']
1934
- assert_equal 'JR - now with Namespacing', json_response['data']['title']
2104
+ assert_equal '3', json_response['data']['relationships']['writer']['data']['id']
2105
+ assert_equal 'JR - now with Namespacing', json_response['data']['attributes']['title']
1935
2106
  assert_equal 'JSONAPIResources is the greatest thing since unsliced bread now that it has namespaced resources.',
1936
- json_response['data']['body']
2107
+ json_response['data']['attributes']['body']
1937
2108
  end
1938
2109
 
1939
2110
  end
@@ -1947,19 +2118,23 @@ class FactsControllerTest < ActionController::TestCase
1947
2118
  get :show, {id: '1'}
1948
2119
  assert_response :success
1949
2120
  assert json_response['data'].is_a?(Hash)
1950
- assert_equal 'Jane Author', json_response['data']['spouseName']
1951
- assert_equal 'First man to run across Antartica.', json_response['data']['bio']
1952
- assert_equal 23.89/45.6, json_response['data']['qualityRating']
1953
- assert_equal '47000.56', json_response['data']['salary']
1954
- assert_equal '2013-08-07T20:25:00Z', json_response['data']['dateTimeJoined']
1955
- assert_equal '1965-06-30', json_response['data']['birthday']
1956
- assert_equal '2000-01-01T20:00:00Z', json_response['data']['bedtime']
1957
- assert_equal 'abc', json_response['data']['photo']
1958
- assert_equal false, json_response['data']['cool']
2121
+ assert_equal 'Jane Author', json_response['data']['attributes']['spouseName']
2122
+ assert_equal 'First man to run across Antartica.', json_response['data']['attributes']['bio']
2123
+ assert_equal 23.89/45.6, json_response['data']['attributes']['qualityRating']
2124
+ assert_equal '47000.56', json_response['data']['attributes']['salary']
2125
+ assert_equal '2013-08-07T20:25:00Z', json_response['data']['attributes']['dateTimeJoined']
2126
+ assert_equal '1965-06-30', json_response['data']['attributes']['birthday']
2127
+ assert_equal '2000-01-01T20:00:00Z', json_response['data']['attributes']['bedtime']
2128
+ assert_equal 'abc', json_response['data']['attributes']['photo']
2129
+ assert_equal false, json_response['data']['attributes']['cool']
1959
2130
  end
1960
2131
  end
1961
2132
 
1962
2133
  class Api::V2::BooksControllerTest < ActionController::TestCase
2134
+ def setup
2135
+ JSONAPI.configuration.json_key_format = :dasherized_key
2136
+ end
2137
+
1963
2138
  def after_teardown
1964
2139
  Api::V2::BookResource.paginator :offset
1965
2140
  end
@@ -1970,7 +2145,31 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
1970
2145
  get :index
1971
2146
  assert_response :success
1972
2147
  assert_equal 10, json_response['data'].size
1973
- assert_equal 'Book 0', json_response['data'][0]['title']
2148
+ assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
2149
+ end
2150
+
2151
+ def test_books_offset_pagination_no_params_includes_query_count_one_level
2152
+ Api::V2::BookResource.paginator :offset
2153
+
2154
+ query_count = count_queries do
2155
+ get :index, {include: 'book-comments'}
2156
+ end
2157
+ assert_response :success
2158
+ assert_equal 10, json_response['data'].size
2159
+ assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
2160
+ assert_equal 2, query_count
2161
+ end
2162
+
2163
+ def test_books_offset_pagination_no_params_includes_query_count_two_levels
2164
+ Api::V2::BookResource.paginator :offset
2165
+
2166
+ query_count = count_queries do
2167
+ get :index, {include: 'book-comments,book-comments.author'}
2168
+ end
2169
+ assert_response :success
2170
+ assert_equal 10, json_response['data'].size
2171
+ assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
2172
+ assert_equal 3, query_count
1974
2173
  end
1975
2174
 
1976
2175
  def test_books_offset_pagination
@@ -1979,7 +2178,7 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
1979
2178
  get :index, {page: {offset: 50, limit: 12}}
1980
2179
  assert_response :success
1981
2180
  assert_equal 12, json_response['data'].size
1982
- assert_equal 'Book 50', json_response['data'][0]['title']
2181
+ assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
1983
2182
  end
1984
2183
 
1985
2184
  def test_books_offset_pagination_bad_page_param
@@ -2006,6 +2205,14 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
2006
2205
  assert_match /-1 is not a valid value for limit page parameter./, json_response['errors'][0]['detail']
2007
2206
  end
2008
2207
 
2208
+ def test_books_offset_pagination_bad_param_offset_less_than_zero
2209
+ Api::V2::BookResource.paginator :offset
2210
+
2211
+ get :index, {page: {offset: -1, limit: 20}}
2212
+ assert_response :bad_request
2213
+ assert_match /-1 is not a valid value for offset page parameter./, json_response['errors'][0]['detail']
2214
+ end
2215
+
2009
2216
  def test_books_offset_pagination_invalid_page_format
2010
2217
  Api::V2::BookResource.paginator :offset
2011
2218
 
@@ -2020,7 +2227,7 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
2020
2227
  get :index
2021
2228
  assert_response :success
2022
2229
  assert_equal 10, json_response['data'].size
2023
- assert_equal 'Book 0', json_response['data'][0]['title']
2230
+ assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
2024
2231
  end
2025
2232
 
2026
2233
  def test_books_paged_pagination_no_page
@@ -2029,7 +2236,7 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
2029
2236
  get :index, {page: {size: 12}}
2030
2237
  assert_response :success
2031
2238
  assert_equal 12, json_response['data'].size
2032
- assert_equal 'Book 0', json_response['data'][0]['title']
2239
+ assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
2033
2240
  end
2034
2241
 
2035
2242
  def test_books_paged_pagination
@@ -2038,7 +2245,7 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
2038
2245
  get :index, {page: {number: 3, size: 12}}
2039
2246
  assert_response :success
2040
2247
  assert_equal 12, json_response['data'].size
2041
- assert_equal 'Book 24', json_response['data'][0]['title']
2248
+ assert_equal 'Book 24', json_response['data'][0]['attributes']['title']
2042
2249
  end
2043
2250
 
2044
2251
  def test_books_paged_pagination_bad_page_param
@@ -2065,13 +2272,12 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
2065
2272
  assert_match /-1 is not a valid value for size page parameter./, json_response['errors'][0]['detail']
2066
2273
  end
2067
2274
 
2068
- def test_books_paged_pagination_invalid_page_format_interpret_int_text
2275
+ def test_books_paged_pagination_invalid_page_format_incorrect
2069
2276
  Api::V2::BookResource.paginator :paged
2070
2277
 
2071
2278
  get :index, {page: 'qwerty'}
2072
- assert_response :success
2073
- assert_equal 10, json_response['data'].size
2074
- assert_equal 'Book 0', json_response['data'][0]['title']
2279
+ assert_response :bad_request
2280
+ assert_match /0 is not a valid value for number page parameter./, json_response['errors'][0]['detail']
2075
2281
  end
2076
2282
 
2077
2283
  def test_books_paged_pagination_invalid_page_format_interpret_int
@@ -2080,6 +2286,76 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
2080
2286
  get :index, {page: 3}
2081
2287
  assert_response :success
2082
2288
  assert_equal 10, json_response['data'].size
2083
- assert_equal 'Book 20', json_response['data'][0]['title']
2289
+ assert_equal 'Book 20', json_response['data'][0]['attributes']['title']
2290
+ end
2291
+ end
2292
+
2293
+ class Api::V4::BooksControllerTest < ActionController::TestCase
2294
+ def setup
2295
+ JSONAPI.configuration.json_key_format = :camelized_key
2296
+ end
2297
+
2298
+ def test_books_offset_pagination_meta
2299
+ JSONAPI.configuration.operations_processor = :counting_active_record
2300
+ Api::V4::BookResource.paginator :offset
2301
+ get :index, {page: {offset: 50, limit: 12}}
2302
+ assert_response :success
2303
+ assert_equal 12, json_response['data'].size
2304
+ assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
2305
+ assert_equal 1000, json_response['meta']['totalRecords']
2306
+ JSONAPI.configuration.operations_processor = :active_record
2307
+ end
2308
+ end
2309
+
2310
+ class CategoriesControllerTest < ActionController::TestCase
2311
+ def test_index_default_filter
2312
+ get :index
2313
+ assert_response :success
2314
+ assert json_response['data'].is_a?(Array)
2315
+ assert_equal 3, json_response['data'].size
2316
+ end
2317
+
2318
+ def test_index_default_filter_override
2319
+ get :index, { filter: { status: 'inactive' } }
2320
+ assert_response :success
2321
+ assert json_response['data'].is_a?(Array)
2322
+ assert_equal 4, json_response['data'].size
2084
2323
  end
2085
2324
  end
2325
+
2326
+ class Api::V1::PlanetsControllerTest < ActionController::TestCase
2327
+ def test_save_model_callbacks
2328
+ set_content_type_header!
2329
+ post :create,
2330
+ {
2331
+ data: {
2332
+ type: 'planets',
2333
+ attributes: {
2334
+ name: 'Zeus',
2335
+ description: 'The largest planet in the solar system. Discovered in 2015.'
2336
+ }
2337
+ }
2338
+ }
2339
+
2340
+ assert_response :created
2341
+ assert json_response['data'].is_a?(Hash)
2342
+ assert_equal 'Zeus', json_response['data']['attributes']['name']
2343
+ end
2344
+
2345
+ def test_save_model_callbacks_fail
2346
+ set_content_type_header!
2347
+ post :create,
2348
+ {
2349
+ data: {
2350
+ type: 'planets',
2351
+ attributes: {
2352
+ name: 'Pluto',
2353
+ description: 'Yes, it is a planet.'
2354
+ }
2355
+ }
2356
+ }
2357
+
2358
+ assert_response :unprocessable_entity
2359
+ assert_match /Save failed or was cancelled/, json_response['errors'][0]['detail']
2360
+ end
2361
+ end