sanger-jsonapi-resources 0.1.1 → 0.2.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +35 -12
  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 +26 -0
  7. data/lib/jsonapi/active_relation/join_manager.rb +297 -0
  8. data/lib/jsonapi/active_relation_resource.rb +898 -0
  9. data/lib/jsonapi/acts_as_resource_controller.rb +130 -113
  10. data/lib/jsonapi/basic_resource.rb +1164 -0
  11. data/lib/jsonapi/cached_response_fragment.rb +129 -0
  12. data/lib/jsonapi/callbacks.rb +2 -0
  13. data/lib/jsonapi/compatibility_helper.rb +29 -0
  14. data/lib/jsonapi/compiled_json.rb +13 -1
  15. data/lib/jsonapi/configuration.rb +88 -21
  16. data/lib/jsonapi/error.rb +29 -0
  17. data/lib/jsonapi/error_codes.rb +4 -0
  18. data/lib/jsonapi/exceptions.rb +82 -50
  19. data/lib/jsonapi/formatter.rb +5 -3
  20. data/lib/jsonapi/include_directives.rb +22 -67
  21. data/lib/jsonapi/link_builder.rb +76 -80
  22. data/lib/jsonapi/mime_types.rb +6 -10
  23. data/lib/jsonapi/naive_cache.rb +2 -0
  24. data/lib/jsonapi/operation.rb +18 -5
  25. data/lib/jsonapi/operation_result.rb +76 -16
  26. data/lib/jsonapi/paginator.rb +2 -0
  27. data/lib/jsonapi/path.rb +45 -0
  28. data/lib/jsonapi/path_segment.rb +78 -0
  29. data/lib/jsonapi/processor.rb +193 -115
  30. data/lib/jsonapi/relationship.rb +145 -14
  31. data/lib/jsonapi/request.rb +734 -0
  32. data/lib/jsonapi/resource.rb +3 -1251
  33. data/lib/jsonapi/resource_controller.rb +2 -0
  34. data/lib/jsonapi/resource_controller_metal.rb +7 -1
  35. data/lib/jsonapi/resource_fragment.rb +56 -0
  36. data/lib/jsonapi/resource_identity.rb +44 -0
  37. data/lib/jsonapi/resource_serializer.rb +158 -284
  38. data/lib/jsonapi/resource_set.rb +196 -0
  39. data/lib/jsonapi/resource_tree.rb +236 -0
  40. data/lib/jsonapi/resources/railtie.rb +9 -0
  41. data/lib/jsonapi/resources/version.rb +1 -1
  42. data/lib/jsonapi/response_document.rb +107 -83
  43. data/lib/jsonapi/routing_ext.rb +50 -26
  44. data/lib/jsonapi-resources.rb +23 -5
  45. data/lib/tasks/check_upgrade.rake +52 -0
  46. metadata +43 -31
  47. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  48. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  49. data/lib/jsonapi/operation_results.rb +0 -35
  50. data/lib/jsonapi/relationship_builder.rb +0 -167
  51. data/lib/jsonapi/request_parser.rb +0 -678
@@ -1,5 +1,7 @@
1
- require 'csv'
1
+ # frozen_string_literal: true
2
2
 
3
+ require 'csv'
4
+ require_relative 'compatibility_helper'
3
5
  module JSONAPI
4
6
  module ActsAsResourceController
5
7
  MEDIA_TYPE_MATCHER = /.+".+"[^,]*|[^,]+/
@@ -9,9 +11,12 @@ module JSONAPI
9
11
  base.extend ClassMethods
10
12
  base.include Callbacks
11
13
  base.cattr_reader :server_error_callbacks
12
- base.define_jsonapi_resources_callbacks :process_operations
14
+ base.define_jsonapi_resources_callbacks :process_operations,
15
+ :transaction
13
16
  end
14
17
 
18
+ attr_reader :response_document, :jsonapi_request
19
+
15
20
  def index
16
21
  process_request
17
22
  end
@@ -25,22 +30,18 @@ module JSONAPI
25
30
  end
26
31
 
27
32
  def create
28
- return unless verify_content_type_header
29
33
  process_request
30
34
  end
31
35
 
32
36
  def create_relationship
33
- return unless verify_content_type_header
34
37
  process_request
35
38
  end
36
39
 
37
40
  def update_relationship
38
- return unless verify_content_type_header
39
41
  process_request
40
42
  end
41
43
 
42
44
  def update
43
- return unless verify_content_type_header
44
45
  process_request
45
46
  end
46
47
 
@@ -52,59 +53,100 @@ module JSONAPI
52
53
  process_request
53
54
  end
54
55
 
55
- def get_related_resource
56
+ def show_related_resource
56
57
  process_request
57
58
  end
58
59
 
59
- def get_related_resources
60
+ def index_related_resources
60
61
  process_request
61
62
  end
62
63
 
63
- def process_request
64
- return unless verify_accept_header
64
+ def get_related_resource
65
+ # :nocov:
66
+ JSONAPI::CompatibilityHelper.deprecation_warn("In #{self.class.name} you exposed a `get_related_resource`"\
67
+ " action. Please use `show_related_resource` instead.")
68
+ show_related_resource
69
+ # :nocov:
70
+ end
65
71
 
66
- @request = JSONAPI::RequestParser.new(params, context: context,
67
- key_formatter: key_formatter,
68
- server_error_callbacks: (self.class.server_error_callbacks || []))
72
+ def get_related_resources
73
+ # :nocov:
74
+ JSONAPI::CompatibilityHelper.deprecation_warn("In #{self.class.name} you exposed a `get_related_resources`"\
75
+ " action. Please use `index_related_resources` instead.")
76
+ index_related_resources
77
+ # :nocov:
78
+ end
69
79
 
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)
80
+ def process_request
81
+ begin
82
+ setup_response_document
83
+ verify_content_type_header
84
+ verify_accept_header
85
+ parse_request
86
+ execute_request
87
+ rescue => e
88
+ handle_exceptions(e)
79
89
  end
80
- rescue => e
81
- handle_exceptions(e)
90
+ render_response_document
82
91
  end
83
92
 
84
- def process_operations(operations)
85
- run_callbacks :process_operations do
86
- operation_dispatcher.process(operations)
87
- end
93
+ def setup_response_document
94
+ @response_document = create_response_document
88
95
  end
89
96
 
90
- def transaction
91
- lambda { |&block|
92
- ActiveRecord::Base.transaction do
93
- block.yield
97
+ def parse_request
98
+ @jsonapi_request = JSONAPI::Request.new(
99
+ params,
100
+ context: context,
101
+ key_formatter: key_formatter,
102
+ server_error_callbacks: (self.class.server_error_callbacks || []))
103
+ fail JSONAPI::Exceptions::Errors.new(@jsonapi_request.errors) if @jsonapi_request.errors.any?
104
+ end
105
+
106
+ def execute_request
107
+ process_operations(jsonapi_request.transactional?) do
108
+ run_callbacks :process_operations do
109
+ jsonapi_request.operations.each do |op|
110
+ op.options[:serializer] = resource_serializer_klass.new(
111
+ op.resource_klass,
112
+ include_directives: op.options[:include_directives],
113
+ fields: op.options[:fields],
114
+ base_url: base_url,
115
+ key_formatter: key_formatter,
116
+ route_formatter: route_formatter,
117
+ serialization_options: serialization_options,
118
+ controller: self
119
+ )
120
+ op.options[:cache_serializer_output] = !JSONAPI.configuration.resource_cache.nil?
121
+
122
+ process_operation(op)
123
+ end
94
124
  end
95
- }
125
+ if response_document.has_errors?
126
+ raise ActiveRecord::Rollback
127
+ end
128
+ end
96
129
  end
97
130
 
98
- def rollback
99
- lambda {
100
- fail ActiveRecord::Rollback
101
- }
131
+ def process_operations(transactional)
132
+ if transactional
133
+ run_callbacks :transaction do
134
+ ActiveRecord::Base.transaction do
135
+ yield
136
+ end
137
+ end
138
+ else
139
+ begin
140
+ yield
141
+ rescue ActiveRecord::Rollback
142
+ # Can't rollback without transaction, so just ignore it
143
+ end
144
+ end
102
145
  end
103
146
 
104
- def operation_dispatcher
105
- @operation_dispatcher ||= JSONAPI::OperationDispatcher.new(transaction: transaction,
106
- rollback: rollback,
107
- server_error_callbacks: @request.server_error_callbacks)
147
+ def process_operation(operation)
148
+ result = operation.process
149
+ response_document.add_result(result, operation)
108
150
  end
109
151
 
110
152
  private
@@ -117,21 +159,8 @@ module JSONAPI
117
159
  @resource_serializer_klass ||= JSONAPI::ResourceSerializer
118
160
  end
119
161
 
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
162
  def base_url
134
- @base_url ||= request.protocol + request.host_with_port
163
+ @base_url ||= "#{request.protocol}#{request.host_with_port}#{Rails.application.config.relative_url_root}"
135
164
  end
136
165
 
137
166
  def resource_klass_name
@@ -139,32 +168,25 @@ module JSONAPI
139
168
  end
140
169
 
141
170
  def verify_content_type_header
142
- unless request.content_type == JSONAPI::MEDIA_TYPE
143
- fail JSONAPI::Exceptions::UnsupportedMediaTypeError.new(request.content_type)
171
+ if ['create', 'create_relationship', 'update_relationship', 'update'].include?(params[:action])
172
+ unless request.media_type == JSONAPI::MEDIA_TYPE
173
+ fail JSONAPI::Exceptions::UnsupportedMediaTypeError.new(request.media_type)
174
+ end
144
175
  end
145
- true
146
- rescue => e
147
- handle_exceptions(e)
148
- false
149
176
  end
150
177
 
151
178
  def verify_accept_header
152
179
  unless valid_accept_media_type?
153
180
  fail JSONAPI::Exceptions::NotAcceptableError.new(request.accept)
154
181
  end
155
- true
156
- rescue => e
157
- handle_exceptions(e)
158
- false
159
182
  end
160
183
 
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_allowed?(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
 
@@ -291,7 +308,7 @@ module JSONAPI
291
308
  # caught that is not a JSONAPI::Exceptions::Error
292
309
  # Useful for additional logging or notification configuration that
293
310
  # would normally depend on rails catching and rendering an exception.
294
- # Ignores whitelist exceptions from config
311
+ # Ignores allowlist exceptions from config
295
312
 
296
313
  module ClassMethods
297
314