@@ -9,9 +9,13 @@ def self.included(base)
99 base . extend ClassMethods
1010 base . include Callbacks
1111 base . cattr_reader :server_error_callbacks
12- base . define_jsonapi_resources_callbacks :process_operations
12+ base . define_jsonapi_resources_callbacks :process_operations ,
13+ :transaction ,
14+ :rollback
1315 end
1416
17+ attr_reader :response_document
18+
1519 def index
1620 process_request
1721 end
@@ -25,22 +29,18 @@ def show_relationship
2529 end
2630
2731 def create
28- return unless verify_content_type_header
2932 process_request
3033 end
3134
3235 def create_relationship
33- return unless verify_content_type_header
3436 process_request
3537 end
3638
3739 def update_relationship
38- return unless verify_content_type_header
3940 process_request
4041 end
4142
4243 def update
43- return unless verify_content_type_header
4444 process_request
4545 end
4646
@@ -61,50 +61,89 @@ def get_related_resources
6161 end
6262
6363 def process_request
64- return unless verify_accept_header
64+ @response_document = create_response_document
6565
66- @request = JSONAPI ::RequestParser . new ( params , context : context ,
67- key_formatter : key_formatter ,
68- server_error_callbacks : ( self . class . server_error_callbacks || [ ] ) )
66+ unless verify_content_type_header && verify_accept_header
67+ render_response_document
68+ return
69+ end
6970
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 }
71+ request_parser = JSONAPI ::RequestParser . new (
72+ params ,
73+ context : context ,
74+ key_formatter : key_formatter ,
75+ server_error_callbacks : ( self . class . server_error_callbacks || [ ] ) )
76+
77+ transactional = request_parser . transactional?
78+
79+ force_rollback = false
80+ run_in_transaction ( transactional ) do
81+ begin
82+ run_callbacks :process_operations do
83+ begin
84+ request_parser . each ( response_document ) do |op |
85+ op . options [ :serializer ] = resource_serializer_klass . new (
86+ op . resource_klass ,
87+ include_directives : op . options [ :include_directives ] ,
88+ fields : op . options [ :fields ] ,
89+ base_url : base_url ,
90+ key_formatter : key_formatter ,
91+ route_formatter : route_formatter ,
92+ serialization_options : serialization_options
93+ )
94+ op . options [ :cache_serializer_output ] = !JSONAPI . configuration . resource_cache . nil?
95+
96+ process_operation ( op )
97+ end
98+ rescue => e
99+ handle_exceptions ( e )
100+ end
101+ end
102+ rescue => e
103+ force_rollback = true
104+ raise e
105+ ensure
106+ if response_document . has_errors? || force_rollback
107+ rollback_transaction ( transactional )
108+ end
76109 end
77- results = process_operations ( operations )
78- render_results ( results )
79110 end
80- rescue => e
81- handle_exceptions ( e )
111+ render_response_document
82112 end
83113
84- def process_operations ( operations )
85- run_callbacks :process_operations do
86- operation_dispatcher . process ( operations )
114+ def run_in_transaction ( transactional )
115+ if transactional
116+ run_callbacks :transaction do
117+ transaction do
118+ yield
119+ end
120+ end
121+ else
122+ yield
87123 end
88124 end
89125
90- def transaction
91- lambda { |& block |
92- ActiveRecord :: Base . transaction do
93- block . yield
126+ def rollback_transaction ( transactional )
127+ if transactional
128+ run_callbacks :rollback do
129+ rollback
94130 end
95- }
131+ end
96132 end
97133
98- def rollback
99- lambda {
100- fail ActiveRecord ::Rollback
101- }
134+ def process_operation ( operation )
135+ result = operation . process
136+ response_document . add_result ( result , operation )
102137 end
103138
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 transaction
140+ ActiveRecord ::Base . transaction do
141+ yield
142+ end
143+ end
144+
145+ def rollback
146+ fail ActiveRecord ::Rollback
108147 end
109148
110149 private
@@ -117,19 +156,6 @@ def resource_serializer_klass
117156 @resource_serializer_klass ||= JSONAPI ::ResourceSerializer
118157 end
119158
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-
133159 def base_url
134160 @base_url ||= request . protocol + request . host_with_port
135161 end
@@ -139,8 +165,10 @@ def resource_klass_name
139165 end
140166
141167 def verify_content_type_header
142- unless request . content_type == JSONAPI ::MEDIA_TYPE
143- fail JSONAPI ::Exceptions ::UnsupportedMediaTypeError . new ( request . content_type )
168+ if [ 'create' , 'create_relationship' , 'update_relationship' , 'update' ] . include? ( params [ :action ] )
169+ unless request . content_type == JSONAPI ::MEDIA_TYPE
170+ fail JSONAPI ::Exceptions ::UnsupportedMediaTypeError . new ( request . content_type )
171+ end
144172 end
145173 true
146174 rescue => e
@@ -161,13 +189,12 @@ def verify_accept_header
161189 def valid_accept_media_type?
162190 media_types = media_types_for ( 'Accept' )
163191
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
192+ media_types . blank? || media_types . any? do |media_type |
193+ ( media_type == JSONAPI ::MEDIA_TYPE || media_type . start_with? ( ALL_MEDIA_TYPES ) )
194+ end
168195 end
169196
170- def media_types_for ( header )
197+ def media_types_for ( header )
171198 ( request . headers [ header ] || '' )
172199 . scan ( MEDIA_TYPE_MATCHER )
173200 . to_a
@@ -202,79 +229,68 @@ def base_response_meta
202229 end
203230
204231 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
232+ base_response_meta
210233 end
211234
212235 def base_response_links
213236 { }
214237 end
215238
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
239+ def render_response_document
240+ content = response_document . contents
227241
228242 render_options = { }
229- if operation_results . has_errors?
243+ if response_document . has_errors?
230244 render_options [ :json ] = content
231245 else
232246 # Bypasing ActiveSupport allows us to use CompiledJson objects for cached response fragments
233247 render_options [ :body ] = JSON . generate ( content )
234- end
235248
236- render_options [ :location ] = content [ :data ] [ "links" ] [ :self ] if (
237- response_doc . status == :created && content [ :data ] . class != Array
238- )
249+ render_options [ :location ] = content [ 'data' ] [ 'links' ] [ 'self' ] if ( response_document . status == 201 && content [ :data ] . class != Array )
250+ end
239251
240252 # For whatever reason, `render` ignores :status and :content_type when :body is set.
241253 # But, we can just set those values directly in the Response object instead.
242- response . status = response_doc . status
254+ response . status = response_document . status
243255 response . headers [ 'Content-Type' ] = JSONAPI ::MEDIA_TYPE
244256
245257 render ( render_options )
246258 end
247259
248- def create_response_document ( operation_results )
260+ def create_response_document
249261 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
262+ key_formatter : key_formatter ,
263+ base_meta : base_meta ,
264+ base_links : base_response_links ,
265+ request : request
256266 )
257267 end
258268
259269 # override this to process other exceptions
260270 # Note: Be sure to either call super(e) or handle JSONAPI::Exceptions::Error and raise unhandled exceptions
261271 def handle_exceptions ( e )
262272 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
273+ when JSONAPI ::Exceptions ::Error
274+ errors = e . errors
275+ when ActionController ::ParameterMissing
276+ errors = JSONAPI ::Exceptions ::ParameterMissing . new ( e . param ) . errors
268277 else
269- ( self . class . server_error_callbacks || [ ] ) . each { |callback |
270- safe_run_callback ( callback , e )
271- }
278+ if JSONAPI . configuration . exception_class_whitelisted? ( e )
279+ fail e
280+ else
281+ if self . class . server_error_callbacks
282+ self . class . server_error_callbacks . each { |callback |
283+ safe_run_callback ( callback , e )
284+ }
285+ end
272286
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
287+ internal_server_error = JSONAPI ::Exceptions ::InternalServerError . new ( e )
288+ Rails . logger . error { "Internal Server Error: #{ e . message } #{ e . backtrace . join ( "\n " ) } " }
289+ errors = internal_server_error . errors
290+ end
277291 end
292+
293+ response_document . add_result ( JSONAPI ::ErrorsOperationResult . new ( errors [ 0 ] . status , errors ) , nil )
278294 end
279295
280296 def safe_run_callback ( callback , error )
@@ -283,7 +299,7 @@ def safe_run_callback(callback, error)
283299 rescue => e
284300 Rails . logger . error { "Error in error handling callback: #{ e . message } #{ e . backtrace . join ( "\n " ) } " }
285301 internal_server_error = JSONAPI ::Exceptions ::InternalServerError . new ( e )
286- render_errors ( internal_server_error . errors )
302+ return JSONAPI :: ErrorsOperationResult . new ( internal_server_error . errors [ 0 ] . code , internal_server_error . errors )
287303 end
288304 end
289305
0 commit comments