jsonapi-resources 0.9.12 → 0.10.7

Sign up to get free protection for your applications and to get access to all the features.
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 +18 -25
  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 -24
  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 +3 -0
  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 +34 -31
  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