jsonapi-resources 0.9.0 → 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
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