Callbacks allow logic to be executed either before or after an action’s method. They are useful for encapsulating code used for tasks like checking whether a user is signed in, handling 404 responses or tidying up a response.

A callback can be added using the before or after methods. These methods accept either a symbol representing the name of a method to be called, or a proc.

Like the #handle method, callbacks receive the request and response as arguments.

If the action above, the validate_params method will be called before the action’s handler. The callback ensures that if the :id parameter cannot be coerced to an integer, the action will be halted and a 422 Unprocessable response returned.

With this callback in place, the #handle method can now proceed knowing that all parameters are valid.

Proc

  1. # app/actions/books/show.rb
  2. module Bookshelf
  3. module Actions
  4. module Books
  5. class Show < Bookshelf::Action
  6. before { |request, response| halt 422, request.params.errors.to_h unless request.params.valid? }
  7. params do
  8. required(:id).filled(:integer)
  9. end
  10. def handle(request, response)
  11. # ...
  12. end
  13. end
  14. end
  15. end
  16. end

Halting an action interrupts its flow and returns control to the framework, which then returns a response based on the status code and body passed to the halt call.

  1. halt 401, "You are not authorized"

Internally, Hanami uses Ruby’s throw and catch mechanisms to provide this behaviour, which is a lightweight approach compared to using exceptions.

This action will return a 401 Unauthorized response when the #authenticated? method returns false:

When halt is invoked, subsequent instructions within the action are entirely skipped. That means that halt will skip the #handle invocation entirely when triggered in a callback.

  1. # app/actions/books/index.rb
  2. module Bookshelf
  3. module Books
  4. class Index < Action
  5. before :authenticate_user!
  6. def handle(request, response)
  7. # ...
  8. end
  9. private
  10. def authenticate_user!(request, response)
  11. halt 401 unless request.session[:user_id]
  12. end
  13. end
  14. end
  15. end
  16. end

#halt accepts an HTTP status code as the first argument and an optional body as its second argument. If no body is provided, the body will be set to a message corresponding to the status code (for example the body of a 401 response will be "Unauthorized").

  1. # app/actions/books/index.rb
  2. module Bookshelf
  3. module Actions
  4. module Books
  5. class Index < Action
  6. def handle(request, response)
  7. halt 404, "These aren't the droids you're looking for"
  8. end
  9. end
  10. end

To redirect a request to another location, call the #redirect_to method on the response object.

When you call redirect_to, control flow is stopped and subsequent code in the action is not executed.

redirect_to accepts a url and an optional HTTP status, which defaults to 302.

This action below shows an example of redirecting unauthenticated users to a sign in page:

  1. # app/actions/books/index.rb
  2. module Bookshelf
  3. module Actions
  4. module Books
  5. class Index < Action
  6. before :authenticate_user!
  7. def handle(request, response)
  8. # ...
  9. end
  10. private
  11. def authenticate_user!(request, response)
  12. response.redirect_to("/sign-in") unless request.session[:user_id]
  13. end
  14. end
  15. end
  16. end
  17. end

If you have given the route you wish to redirect to a name, you can also use the routes helper, which is automatically available to actions.

  1. # config/routes.rb