jsonapi-resources 0.9.11 → 0.10.7

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 (45) hide show
  1. checksums.yaml +4 -4
  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 -106
  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 +57 -8
  14. data/lib/jsonapi/error.rb +27 -0
  15. data/lib/jsonapi/error_codes.rb +2 -0
  16. data/lib/jsonapi/exceptions.rb +63 -40
  17. data/lib/jsonapi/formatter.rb +3 -3
  18. data/lib/jsonapi/include_directives.rb +18 -75
  19. data/lib/jsonapi/link_builder.rb +16 -19
  20. data/lib/jsonapi/operation.rb +16 -5
  21. data/lib/jsonapi/operation_result.rb +73 -15
  22. data/lib/jsonapi/paginator.rb +17 -0
  23. data/lib/jsonapi/path.rb +43 -0
  24. data/lib/jsonapi/path_segment.rb +76 -0
  25. data/lib/jsonapi/processor.rb +246 -111
  26. data/lib/jsonapi/relationship.rb +117 -18
  27. data/lib/jsonapi/request_parser.rb +383 -396
  28. data/lib/jsonapi/resource.rb +3 -1376
  29. data/lib/jsonapi/resource_controller_metal.rb +5 -2
  30. data/lib/jsonapi/resource_fragment.rb +47 -0
  31. data/lib/jsonapi/resource_id_tree.rb +112 -0
  32. data/lib/jsonapi/resource_identity.rb +42 -0
  33. data/lib/jsonapi/resource_serializer.rb +124 -286
  34. data/lib/jsonapi/resource_set.rb +176 -0
  35. data/lib/jsonapi/resources/railtie.rb +9 -0
  36. data/lib/jsonapi/resources/version.rb +1 -1
  37. data/lib/jsonapi/response_document.rb +104 -87
  38. data/lib/jsonapi/routing_ext.rb +19 -21
  39. data/lib/jsonapi-resources.rb +20 -4
  40. data/lib/tasks/check_upgrade.rake +52 -0
  41. metadata +36 -33
  42. data/lib/jsonapi/cached_resource_fragment.rb +0 -127
  43. data/lib/jsonapi/operation_dispatcher.rb +0 -88
  44. data/lib/jsonapi/operation_results.rb +0 -35
  45. 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,22 +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
- controller: self
130
- )
131
- @resource_serializer
132
- end
133
-
134
154
  def base_url
135
- @base_url ||= request.protocol + request.host_with_port
155
+ @base_url ||= "#{request.protocol}#{request.host_with_port}#{Rails.application.config.relative_url_root}"
136
156
  end
137
157
 
138
158
  def resource_klass_name
@@ -140,8 +160,10 @@ module JSONAPI
140
160
  end
141
161
 
142
162
  def verify_content_type_header
143
- unless request.content_type == JSONAPI::MEDIA_TYPE
144
- 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
145
167
  end
146
168
  true
147
169
  rescue => e
@@ -162,10 +184,9 @@ module JSONAPI
162
184
  def valid_accept_media_type?
163
185
  media_types = media_types_for('Accept')
164
186
 
165
- media_types.blank? ||
166
- media_types.any? do |media_type|
167
- (media_type == JSONAPI::MEDIA_TYPE || media_type.start_with?(ALL_MEDIA_TYPES))
168
- 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
169
190
  end
170
191
 
171
192
  def media_types_for(header)
@@ -203,57 +224,43 @@ module JSONAPI
203
224
  end
204
225
 
205
226
  def base_meta
206
- if @request.nil? || @request.warnings.empty?
207
- base_response_meta
208
- else
209
- base_response_meta.merge(warnings: @request.warnings)
210
- end
227
+ base_response_meta
211
228
  end
212
229
 
213
230
  def base_response_links
214
231
  {}
215
232
  end
216
233
 
217
- def render_errors(errors)
218
- operation_results = JSONAPI::OperationResults.new
219
- result = JSONAPI::ErrorsOperationResult.new(errors[0].status, errors)
220
- operation_results.add_result(result)
221
-
222
- render_results(operation_results)
223
- end
224
-
225
- def render_results(operation_results)
226
- response_doc = create_response_document(operation_results)
227
- content = response_doc.contents
234
+ def render_response_document
235
+ content = response_document.contents
228
236
 
229
237
  render_options = {}
230
- if operation_results.has_errors?
238
+ if response_document.has_errors?
231
239
  render_options[:json] = content
232
240
  else
233
- # 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
234
242
  render_options[:body] = JSON.generate(content)
235
- end
236
243
 
237
- render_options[:location] = content[:data]["links"][:self] if (
238
- response_doc.status == :created && content[:data].class != Array && content[:data]["links"]
239
- )
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
240
249
 
241
250
  # For whatever reason, `render` ignores :status and :content_type when :body is set.
242
251
  # But, we can just set those values directly in the Response object instead.
243
- response.status = response_doc.status
252
+ response.status = response_document.status
244
253
  response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
245
254
 
246
255
  render(render_options)
247
256
  end
248
257
 
249
- def create_response_document(operation_results)
258
+ def create_response_document
250
259
  JSONAPI::ResponseDocument.new(
251
- operation_results,
252
- operation_results.has_errors? ? nil : resource_serializer,
253
- key_formatter: key_formatter,
254
- base_meta: base_meta,
255
- base_links: base_response_links,
256
- request: @request
260
+ key_formatter: key_formatter,
261
+ base_meta: base_meta,
262
+ base_links: base_response_links,
263
+ request: request
257
264
  )
258
265
  end
259
266
 
@@ -261,21 +268,30 @@ module JSONAPI
261
268
  # Note: Be sure to either call super(e) or handle JSONAPI::Exceptions::Error and raise unhandled exceptions
262
269
  def handle_exceptions(e)
263
270
  case e
264
- when JSONAPI::Exceptions::Error
265
- render_errors(e.errors)
266
- else
267
- if JSONAPI.configuration.exception_class_whitelisted?(e)
268
- fail e
271
+ when JSONAPI::Exceptions::Error
272
+ errors = e.errors
273
+ when ActionController::ParameterMissing
274
+ errors = JSONAPI::Exceptions::ParameterMissing.new(e.param).errors
269
275
  else
270
- (self.class.server_error_callbacks || []).each { |callback|
271
- safe_run_callback(callback, e)
272
- }
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
273
284
 
274
- internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
275
- Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
276
- render_errors(internal_server_error.errors)
277
- 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
278
292
  end
293
+
294
+ response_document.add_result(JSONAPI::ErrorsOperationResult.new(errors[0].status, errors), nil)
279
295
  end
280
296
 
281
297
  def safe_run_callback(callback, error)
@@ -284,7 +300,7 @@ module JSONAPI
284
300
  rescue => e
285
301
  Rails.logger.error { "Error in error handling callback: #{e.message} #{e.backtrace.join("\n")}" }
286
302
  internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
287
- render_errors(internal_server_error.errors)
303
+ return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
288
304
  end
289
305
  end
290
306