Actions support control flow through callbacks, halting and redirects.

Callbacks

Callbacks allow logic to be executed either before or after an action’s #handle 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.

Method callbacks

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

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

The example above could also be implemented using a proc. Procs are bound to the instance context of the action.

  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

Halt

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:

  1. # app/actions/books/index.rb
  2. module Bookshellf
  3. module Actions
  4. module Books
  5. class Index < Action
  6. def handle(request, response)
  7. halt 401 unless authenticated?(request)
  8. # ...
  9. end
  10. private
  11. def authenticated?(request)
  12. # ...
  13. end
  14. end
  15. end
  16. end
  17. end

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 before callback.

  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. halt 401 unless request.session[:user_id]
  13. end
  14. end
  15. end
  16. end
  17. 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
  11. end
  12. end

Hanami 404 response

Redirects

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.

  1. request.redirect_to("/sign-in")
  2. request.redirect_to("https://hanamirb.org", status: 301)

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
  2. get "/sign-in", to: "sign_in", as: :sign_in
  1. # Within your action
  2. response.redirect_to routes.path(:sign_in)

See the Routing guide for more information on named routes.