Actions have an elegant API for exception handling. The behavior changes according to the current Hanami environment and the custom settings in our configuration.

Default Behavior

  1. # apps/web/controllers/dashboard/index.rb
  2. module Web
  3. module Controllers
  4. module Dashboard
  5. class Index
  6. include Web::Action
  7. def call(params)
  8. raise 'boom'
  9. end
  10. end
  11. end
  12. end
  13. end

Exceptions are automatically caught when in production mode, but not in development. In production, for our example, the application returns a 500 (Internal Server Error); in development, we’ll see the stack trace and all the information to debug the code.

This behavior can be changed with the handle_exceptions setting in apps/web/application.rb.

Custom HTTP Status

If we want to map an exception to a specific HTTP status code, we can use handle_exception DSL.

  1. # apps/web/controllers/dashboard/index.rb
  2. module Web
  3. module Controllers
  4. module Dashboard
  5. class Index
  6. include Web::Action
  7. handle_exception ArgumentError => 400
  8. def call(params)
  9. raise ArgumentError
  10. end
  11. end
  12. end
  13. end
  14. end

handle_exception accepts a Hash where the key is the exception to handle, and the value is the corresponding HTTP status code. In our example, when ArgumentError is raised, it will be handled as a 400 (Bad Request).

Custom Handlers

If the mapping with a custom HTTP status doesn’t fit our needs, we can specify a custom handler and manage the exception by ourselves.

  1. # apps/web/controllers/dashboard/index.rb
  2. module Web
  3. module Controllers
  4. module Dashboard
  5. class PermissionDenied < StandardError
  6. def initialize(role)
  7. super "You must be admin, but you are: #{ role }"
  8. end
  9. end
  10. class Index
  11. include Web::Action
  12. handle_exception PermissionDenied => :handle_permission_error
  13. def call(params)
  14. unless current_user.admin?
  15. raise PermissionDenied.new(current_user.role)
  16. end
  17. # ...
  18. end
  19. private
  20. def handle_permission_error(exception)
  21. status 403, exception.message
  22. end
  23. end
  24. end
  25. end
  26. end

If we specify the name of a method (as a symbol) as the value for handle_exception, this method will be used to respond to the exception. In the example above we want to protect the action from unwanted access: only admins are allowed.

When a PermissionDenied exception is raised it will be handled by :handle_permission_error. It MUST accept an exception argument—the exception instance raised inside #call.

When specifying a custom exception handler, it MUST accept an exception argument.