From f511995ba6baf064f14f1027b41cd2b1d3d48ae5 Mon Sep 17 00:00:00 2001 From: Thorsten Eckel Date: Fri, 20 Jan 2017 10:45:19 +0100 Subject: [PATCH] Application controller refactoring: Moving error methods to proper concern. --- app/controllers/application_controller.rb | 97 +--------------------- app/controllers/concerns/error_handling.rb | 81 ++++++++++++++++++ 2 files changed, 83 insertions(+), 95 deletions(-) create mode 100644 app/controllers/concerns/error_handling.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f45bb507b..da92d096e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,8 @@ require 'exceptions' class ApplicationController < ActionController::Base + include ErrorHandling + # http_basic_authenticate_with :name => "test", :password => "ttt" helper_method :current_user, @@ -18,15 +20,6 @@ class ApplicationController < ActionController::Base before_action :transaction_begin, :set_user, :session_update, :user_device_check, :cors_preflight_check after_action :transaction_end, :http_log, :set_access_control_headers - rescue_from StandardError, with: :server_error - rescue_from ExecJS::RuntimeError, with: :server_error - rescue_from ActiveRecord::RecordNotFound, with: :not_found - rescue_from ActiveRecord::StatementInvalid, with: :unprocessable_entity - rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity - rescue_from ArgumentError, with: :unprocessable_entity - rescue_from Exceptions::UnprocessableEntity, with: :unprocessable_entity - rescue_from Exceptions::NotAuthorized, with: :unauthorized - # For all responses in this controller, return the CORS access control headers. def set_access_control_headers headers['Access-Control-Allow-Origin'] = '*' @@ -658,30 +651,6 @@ class ApplicationController < ActionController::Base render json: generic_objects, status: :ok end - def model_match_error(error) - data = { - error: error - } - if error =~ /Validation failed: (.+?)(,|$)/i - data[:error_human] = $1 - end - if error =~ /(already exists|duplicate key|duplicate entry)/i - data[:error_human] = 'Object already exists!' - end - if error =~ /null value in column "(.+?)" violates not-null constraint/i - data[:error_human] = "Attribute '#{$1}' required!" - end - if error =~ /Field '(.+?)' doesn't have a default value/i - data[:error_human] = "Attribute '#{$1}' required!" - end - - if Rails.env.production? && !data[:error_human].empty? - data[:error] = data[:error_human] - data.delete('error_human') - end - data - end - def model_references_check(object, params) generic_object = object.find(params[:id]) result = Models.references(object, generic_object.id) @@ -691,68 +660,6 @@ class ApplicationController < ActionController::Base raise Exceptions::UnprocessableEntity, e end - def not_found(e) - logger.error e.message - logger.error e.backtrace.inspect - respond_to do |format| - format.json { render json: model_match_error(e.message), status: :not_found } - format.any { - @exception = e - @traceback = !Rails.env.production? - file = File.open(Rails.root.join('public', '404.html'), 'r') - render inline: file.read, status: :not_found - } - end - end - - def unprocessable_entity(e) - logger.error e.message - logger.error e.backtrace.inspect - respond_to do |format| - format.json { render json: model_match_error(e.message), status: :unprocessable_entity } - format.any { - @exception = e - @traceback = !Rails.env.production? - file = File.open(Rails.root.join('public', '422.html'), 'r') - render inline: file.read, status: :unprocessable_entity - } - end - end - - def server_error(e) - logger.error e.message - logger.error e.backtrace.inspect - respond_to do |format| - format.json { render json: model_match_error(e.message), status: 500 } - format.any { - @exception = e - @traceback = !Rails.env.production? - file = File.open(Rails.root.join('public', '500.html'), 'r') - render inline: file.read, status: 500 - } - end - end - - def unauthorized(e) - message = e.message - if message == 'Exceptions::NotAuthorized' - message = 'Not authorized' - end - error = model_match_error(message) - if error && error[:error] - response.headers['X-Failure'] = error[:error_human] || error[:error] - end - respond_to do |format| - format.json { render json: error, status: :unauthorized } - format.any { - @exception = e - @traceback = !Rails.env.production? - file = File.open(Rails.root.join('public', '401.html'), 'r') - render inline: file.read, status: :unauthorized - } - end - end - # check maintenance mode def check_maintenance_only(user) return false if Setting.get('maintenance_mode') != true diff --git a/app/controllers/concerns/error_handling.rb b/app/controllers/concerns/error_handling.rb new file mode 100644 index 000000000..020f0593c --- /dev/null +++ b/app/controllers/concerns/error_handling.rb @@ -0,0 +1,81 @@ +module ErrorHandling + extend ActiveSupport::Concern + + included do + rescue_from StandardError, with: :internal_server_error + rescue_from ExecJS::RuntimeError, with: :internal_server_error + rescue_from ActiveRecord::RecordNotFound, with: :not_found + rescue_from ActiveRecord::StatementInvalid, with: :unprocessable_entity + rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity + rescue_from ArgumentError, with: :unprocessable_entity + rescue_from Exceptions::UnprocessableEntity, with: :unprocessable_entity + rescue_from Exceptions::NotAuthorized, with: :unauthorized + end + + def not_found(e) + log_error_exception(e) + respond_to_exception(e, :not_found) + end + + def unprocessable_entity(e) + log_error_exception(e) + respond_to_exception(e, :unprocessable_entity) + end + + def internal_server_error(e) + log_error_exception(e) + respond_to_exception(e, :internal_server_error) + end + + def unauthorized(e) + error = humanize_error(e.message) + response.headers['X-Failure'] = error.fetch(:error_human, error[:error]) + respond_to_exception(e, :unauthorized) + end + + private + + def log_error_exception(e) + logger.error e.message + logger.error e.backtrace.inspect + end + + def respond_to_exception(e, status) + status_code = Rack::Utils.status_code(status) + + respond_to do |format| + format.json { render json: humanize_error(e.message), status: status } + format.any { + @exception = e + @traceback = !Rails.env.production? + file = File.open(Rails.root.join('public', "#{status_code}.html"), 'r') + render inline: file.read, status: status + } + end + end + + def humanize_error(error) + data = { + error: error + } + + case error + when /Validation failed: (.+?)(,|$)/i + data[:error_human] = $1 + when /(already exists|duplicate key|duplicate entry)/i + data[:error_human] = 'Object already exists!' + when /null value in column "(.+?)" violates not-null constraint/i + data[:error_human] = "Attribute '#{$1}' required!" + when /Field '(.+?)' doesn't have a default value/i + data[:error_human] = "Attribute '#{$1}' required!" + when 'Exceptions::NotAuthorized' + data[:error] = 'Not authorized' + data[:error_human] = data[:error] + end + + if Rails.env.production? && !data[:error_human].empty? + data[:error] = data.delete(:error_human) + end + data + end +end