jsonapi-resources 0.9.0 → 0.10.6

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 (44) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +34 -11
  4. data/lib/bug_report_templates/rails_5_latest.rb +125 -0
  5. data/lib/bug_report_templates/rails_5_master.rb +140 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +27 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +303 -0
  8. data/lib/jsonapi/active_relation_resource.rb +884 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +122 -105
  10. data/lib/jsonapi/basic_resource.rb +1162 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +127 -0
  12. data/lib/jsonapi/compiled_json.rb +11 -1
  13. data/lib/jsonapi/configuration.rb +71 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +80 -50
  17. data/lib/jsonapi/formatter.rb +3 -3
  18. data/lib/jsonapi/include_directives.rb +18 -65
  19. data/lib/jsonapi/link_builder.rb +74 -80
  20. data/lib/jsonapi/operation.rb +16 -5
  21. data/lib/jsonapi/operation_result.rb +74 -16
  22. data/lib/jsonapi/path.rb +43 -0
  23. data/lib/jsonapi/path_segment.rb +76 -0
  24. data/lib/jsonapi/processor.rb +239 -111
  25. data/lib/jsonapi/relationship.rb +153 -15
  26. data/lib/jsonapi/request_parser.rb +430 -367
  27. data/lib/jsonapi/resource.rb +3 -1253
  28. data/lib/jsonapi/resource_controller_metal.rb +5 -2
  29. data/lib/jsonapi/resource_fragment.rb +47 -0
  30. data/lib/jsonapi/resource_id_tree.rb +112 -0
  31. data/lib/jsonapi/resource_identity.rb +42 -0
  32. data/lib/jsonapi/resource_serializer.rb +143 -285
  33. data/lib/jsonapi/resource_set.rb +176 -0
  34. data/lib/jsonapi/resources/railtie.rb +9 -0
  35. data/lib/jsonapi/resources/version.rb +1 -1
  36. data/lib/jsonapi/response_document.rb +105 -83
  37. data/lib/jsonapi/routing_ext.rb +48 -26
  38. data/lib/jsonapi-resources.rb +20 -4
  39. data/lib/tasks/check_upgrade.rake +52 -0
  40. metadata +50 -20
  41. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  42. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  43. data/lib/jsonapi/operation_results.rb +0 -35
  44. data/lib/jsonapi/relationship_builder.rb +0 -167
@@ -9,9 +9,12 @@ module JSONAPI
9
9
  base.extend ClassMethods
10
10
  base.include Callbacks
11
11
  base.cattr_reader :server_error_callbacks
12
- base.define_jsonapi_resources_callbacks :process_operations
12
+ base.define_jsonapi_resources_callbacks :process_operations,
13
+ :transaction
13
14
  end
14
15
 
16
+ attr_reader :response_document
17
+
15
18
  def index
16
19
  process_request
17
20
  end
@@ -25,22 +28,18 @@ module JSONAPI
25
28
  end
26
29
 
27
30
  def create
28
- return unless verify_content_type_header
29
31
  process_request
30
32
  end
31
33
 
32
34
  def create_relationship
33
- return unless verify_content_type_header
34
35
  process_request
35
36
  end
36
37
 
37
38
  def update_relationship
38
- return unless verify_content_type_header
39
39
  process_request
40
40
  end
41
41
 
42
42
  def update
43
- return unless verify_content_type_header
44
43
  process_request
45
44
  end
46
45
 
@@ -52,59 +51,94 @@ module JSONAPI
52
51
  process_request
53
52
  end
54
53
 
55
- def get_related_resource
54
+ def show_related_resource
56
55
  process_request
57
56
  end
58
57
 
59
- def get_related_resources
58
+ def index_related_resources
60
59
  process_request
61
60
  end
62
61
 
63
- def process_request
64
- return unless verify_accept_header
65
-
66
- @request = JSONAPI::RequestParser.new(params, context: context,
67
- key_formatter: key_formatter,
68
- server_error_callbacks: (self.class.server_error_callbacks || []))
62
+ def get_related_resource
63
+ # :nocov:
64
+ ActiveSupport::Deprecation.warn "In #{self.class.name} you exposed a `get_related_resource`"\
65
+ " action. Please use `show_related_resource` instead."
66
+ show_related_resource
67
+ # :nocov:
68
+ end
69
69
 
70
- unless @request.errors.empty?
71
- render_errors(@request.errors)
72
- else
73
- operations = @request.operations
74
- unless JSONAPI.configuration.resource_cache.nil?
75
- operations.each {|op| op.options[:cache_serializer] = resource_serializer }
76
- end
77
- results = process_operations(operations)
78
- render_results(results)
79
- end
80
- rescue => e
81
- handle_exceptions(e)
70
+ def get_related_resources
71
+ # :nocov:
72
+ ActiveSupport::Deprecation.warn "In #{self.class.name} you exposed a `get_related_resources`"\
73
+ " action. Please use `index_related_resources` instead."
74
+ index_related_resources
75
+ # :nocov:
82
76
  end
83
77
 
84
- def process_operations(operations)
85
- run_callbacks :process_operations do
86
- operation_dispatcher.process(operations)
78
+ def process_request
79
+ @response_document = create_response_document
80
+
81
+ unless verify_content_type_header && verify_accept_header
82
+ render_response_document
83
+ return
87
84
  end
88
- end
89
85
 
90
- def transaction
91
- lambda { |&block|
92
- ActiveRecord::Base.transaction do
93
- block.yield
86
+ request_parser = JSONAPI::RequestParser.new(
87
+ params,
88
+ context: context,
89
+ key_formatter: key_formatter,
90
+ server_error_callbacks: (self.class.server_error_callbacks || []))
91
+
92
+ transactional = request_parser.transactional?
93
+
94
+ begin
95
+ process_operations(transactional) do
96
+ run_callbacks :process_operations do
97
+ request_parser.each(response_document) do |op|
98
+ op.options[:serializer] = resource_serializer_klass.new(
99
+ op.resource_klass,
100
+ include_directives: op.options[:include_directives],
101
+ fields: op.options[:fields],
102
+ base_url: base_url,
103
+ key_formatter: key_formatter,
104
+ route_formatter: route_formatter,
105
+ serialization_options: serialization_options,
106
+ controller: self
107
+ )
108
+ op.options[:cache_serializer_output] = !JSONAPI.configuration.resource_cache.nil?
109
+
110
+ process_operation(op)
111
+ end
112
+ end
113
+ if response_document.has_errors?
114
+ raise ActiveRecord::Rollback
115
+ end
94
116
  end
95
- }
117
+ rescue => e
118
+ handle_exceptions(e)
119
+ end
120
+ render_response_document
96
121
  end
97
122
 
98
- def rollback
99
- lambda {
100
- fail ActiveRecord::Rollback
101
- }
123
+ def process_operations(transactional)
124
+ if transactional
125
+ run_callbacks :transaction do
126
+ ActiveRecord::Base.transaction do
127
+ yield
128
+ end
129
+ end
130
+ else
131
+ begin
132
+ yield
133
+ rescue ActiveRecord::Rollback
134
+ # Can't rollback without transaction, so just ignore it
135
+ end
136
+ end
102
137
  end
103
138
 
104
- def operation_dispatcher
105
- @operation_dispatcher ||= JSONAPI::OperationDispatcher.new(transaction: transaction,
106
- rollback: rollback,
107
- server_error_callbacks: @request.server_error_callbacks)
139
+ def process_operation(operation)
140
+ result = operation.process
141
+ response_document.add_result(result, operation)
108
142
  end
109
143
 
110
144
  private
@@ -117,21 +151,8 @@ module JSONAPI
117
151
  @resource_serializer_klass ||= JSONAPI::ResourceSerializer
118
152
  end
119
153
 
120
- def resource_serializer
121
- @resource_serializer ||= resource_serializer_klass.new(
122
- resource_klass,
123
- include_directives: @request ? @request.include_directives : nil,
124
- fields: @request ? @request.fields : {},
125
- base_url: base_url,
126
- key_formatter: key_formatter,
127
- route_formatter: route_formatter,
128
- serialization_options: serialization_options
129
- )
130
- @resource_serializer
131
- end
132
-
133
154
  def base_url
134
- @base_url ||= request.protocol + request.host_with_port
155
+ @base_url ||= "#{request.protocol}#{request.host_with_port}#{Rails.application.config.relative_url_root}"
135
156
  end
136
157
 
137
158
  def resource_klass_name
@@ -139,8 +160,10 @@ module JSONAPI
139
160
  end
140
161
 
141
162
  def verify_content_type_header
142
- unless request.content_type == JSONAPI::MEDIA_TYPE
143
- fail JSONAPI::Exceptions::UnsupportedMediaTypeError.new(request.content_type)
163
+ if ['create', 'create_relationship', 'update_relationship', 'update'].include?(params[:action])
164
+ unless request.content_type == JSONAPI::MEDIA_TYPE
165
+ fail JSONAPI::Exceptions::UnsupportedMediaTypeError.new(request.content_type)
166
+ end
144
167
  end
145
168
  true
146
169
  rescue => e
@@ -161,10 +184,9 @@ module JSONAPI
161
184
  def valid_accept_media_type?
162
185
  media_types = media_types_for('Accept')
163
186
 
164
- media_types.blank? ||
165
- media_types.any? do |media_type|
166
- (media_type == JSONAPI::MEDIA_TYPE || media_type.start_with?(ALL_MEDIA_TYPES))
167
- end
187
+ media_types.blank? || media_types.any? do |media_type|
188
+ (media_type == JSONAPI::MEDIA_TYPE || media_type.start_with?(ALL_MEDIA_TYPES))
189
+ end
168
190
  end
169
191
 
170
192
  def media_types_for(header)
@@ -202,57 +224,43 @@ module JSONAPI
202
224
  end
203
225
 
204
226
  def base_meta
205
- if @request.nil? || @request.warnings.empty?
206
- base_response_meta
207
- else
208
- base_response_meta.merge(warnings: @request.warnings)
209
- end
227
+ base_response_meta
210
228
  end
211
229
 
212
230
  def base_response_links
213
231
  {}
214
232
  end
215
233
 
216
- def render_errors(errors)
217
- operation_results = JSONAPI::OperationResults.new
218
- result = JSONAPI::ErrorsOperationResult.new(errors[0].status, errors)
219
- operation_results.add_result(result)
220
-
221
- render_results(operation_results)
222
- end
223
-
224
- def render_results(operation_results)
225
- response_doc = create_response_document(operation_results)
226
- content = response_doc.contents
234
+ def render_response_document
235
+ content = response_document.contents
227
236
 
228
237
  render_options = {}
229
- if operation_results.has_errors?
238
+ if response_document.has_errors?
230
239
  render_options[:json] = content
231
240
  else
232
- # Bypasing ActiveSupport allows us to use CompiledJson objects for cached response fragments
241
+ # Bypassing ActiveSupport allows us to use CompiledJson objects for cached response fragments
233
242
  render_options[:body] = JSON.generate(content)
234
- end
235
243
 
236
- render_options[:location] = content[:data]["links"][:self] if (
237
- response_doc.status == :created && content[:data].class != Array
238
- )
244
+ if (response_document.status == 201 && content[:data].class != Array) &&
245
+ content['data'] && content['data']['links'] && content['data']['links']['self']
246
+ render_options[:location] = content['data']['links']['self']
247
+ end
248
+ end
239
249
 
240
250
  # For whatever reason, `render` ignores :status and :content_type when :body is set.
241
251
  # But, we can just set those values directly in the Response object instead.
242
- response.status = response_doc.status
252
+ response.status = response_document.status
243
253
  response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
244
254
 
245
255
  render(render_options)
246
256
  end
247
257
 
248
- def create_response_document(operation_results)
258
+ def create_response_document
249
259
  JSONAPI::ResponseDocument.new(
250
- operation_results,
251
- operation_results.has_errors? ? nil : resource_serializer,
252
- key_formatter: key_formatter,
253
- base_meta: base_meta,
254
- base_links: base_response_links,
255
- request: @request
260
+ key_formatter: key_formatter,
261
+ base_meta: base_meta,
262
+ base_links: base_response_links,
263
+ request: request
256
264
  )
257
265
  end
258
266
 
@@ -260,21 +268,30 @@ module JSONAPI
260
268
  # Note: Be sure to either call super(e) or handle JSONAPI::Exceptions::Error and raise unhandled exceptions
261
269
  def handle_exceptions(e)
262
270
  case e
263
- when JSONAPI::Exceptions::Error
264
- render_errors(e.errors)
265
- else
266
- if JSONAPI.configuration.exception_class_whitelisted?(e)
267
- fail e
271
+ when JSONAPI::Exceptions::Error
272
+ errors = e.errors
273
+ when ActionController::ParameterMissing
274
+ errors = JSONAPI::Exceptions::ParameterMissing.new(e.param).errors
268
275
  else
269
- (self.class.server_error_callbacks || []).each { |callback|
270
- safe_run_callback(callback, e)
271
- }
276
+ if JSONAPI.configuration.exception_class_whitelisted?(e)
277
+ raise e
278
+ else
279
+ if self.class.server_error_callbacks
280
+ self.class.server_error_callbacks.each { |callback|
281
+ safe_run_callback(callback, e)
282
+ }
283
+ end
272
284
 
273
- internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
274
- Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
275
- render_errors(internal_server_error.errors)
276
- end
285
+ # Store exception for other middlewares
286
+ request.env['action_dispatch.exception'] ||= e
287
+
288
+ internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
289
+ Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
290
+ errors = internal_server_error.errors
291
+ end
277
292
  end
293
+
294
+ response_document.add_result(JSONAPI::ErrorsOperationResult.new(errors[0].status, errors), nil)
278
295
  end
279
296
 
280
297
  def safe_run_callback(callback, error)
@@ -283,7 +300,7 @@ module JSONAPI
283
300
  rescue => e
284
301
  Rails.logger.error { "Error in error handling callback: #{e.message} #{e.backtrace.join("\n")}" }
285
302
  internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
286
- render_errors(internal_server_error.errors)
303
+ return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
287
304
  end
288
305
  end
289
306