Actions and Views in Hanami
In our Getting Started with Hanami 2 guide, we set up a new Hanami application and explored the basic project structure. Now it’s time to dive into one of the most important concepts in any web framework: how to handle requests and return responses.
Hanami uses a pattern called actions for handling HTTP requests and views for rendering responses. This separation of concerns keeps your code clean and maintainable. In this guide, you’ll learn how to create actions, work with parameters, render templates, and return JSON responses.
Understanding Actions in Hanami
Actions are the entry point for HTTP requests in Hanami. Each action corresponds to a specific URL and HTTP method combination. When a request comes in, Hanami dispatches it to the appropriate action, which then processes the request and returns a response.
Here’s a simple action in Hanami:
# app/actions/books/index.rb
module Bookshelf
module Actions
module Books
class Index < Bookshelf::Action
def handle(request, response)
response[:books] = ['Ruby Programming', 'Rails 7 Guide']
end
end
end
end
end
The action inherits from Bookshelf::Action, which provides the handle method with two parameters: the request object and the response object. The request contains all the information about the incoming HTTP request, while the response is what you’ll use to build the response.
Routing and Parameters
Hanami uses a DSL for defining routes. Each route maps a URL pattern to an action. Let’s look at how to set up routing:
# config/routes.rb
module Bookshelf
class Routes
define do
root to: 'home#index'
get '/books', to: 'books#index'
get '/books/:id', to: 'books#show'
post '/books', to: 'books#create'
put '/books/:id', to: 'books#update'
delete '/books/:id', to: 'books#destroy'
end
end
end
The :id part of the route is a route parameter. You can access it in your action through the request object:
# app/actions/books/show.rb
module Bookshelf
module Actions
module Books
class Show < Bookshelf::Action
def handle(request, response)
book_id = request.params[:id]
book = BookRepository.new.find(book_id)
if book
response[:book] = book
else
response.status = 404
response[:error] = 'Book not found'
end
end
end
end
end
end
The request.params method gives you access to all parameters, including:
- Route parameters (like
:idfrom the URL) - Query string parameters (like
?page=2) - POST body parameters (from form submissions)
Query Parameters vs Route Parameters
Hanami combines all types of parameters into a single params object. Here’s how to work with query parameters:
# app/actions/books/index.rb
module Bookshelf
module Actions
module Books
class Index < Bookshelf::Action
def handle(request, response)
page = request.params[:page] || 1
per_page = request.params[:per_page] || 10
books = BookRepository.new.all(paginate: (page.to_i - 1) * per_page.to_i, limit: per_page.to_i)
response[:books] = books
response[:pagination] = {
page: page.to_i,
per_page: per_page.to_i
}
end
end
end
end
end
Now you can filter books using URLs like /books?page=2&per_page=25.
Working with Views
In Hanami, actions and views are separate components. The action handles the request logic, and the view handles rendering the response. This separation of concerns keeps your code clean and easier to test.
To render a view from your action, use the render method:
# app/actions/books/index.rb
module Bookshelf
module Actions
module Books
class Index < Bookshelf::Action
def handle(request, response)
books = BookRepository.new.all
render response, :index, books: books
end
end
end
end
end
The corresponding view would look like this:
# app/views/books/index.rb
module Bookshelf
module Views
module Books
class Index < Bookshelf::View
def template
html do
ul do
books.each do |book|
li book.title
end
end
end
end
end
end
end
end
Hanami views use a DSL that lets you build HTML programmatically. This is safer than using inline ERB templates because it’s less prone to XSS attacks.
Returning JSON Responses
Modern web applications often need to return JSON instead of HTML. Hanami makes this straightforward:
# app/actions/books/show.rb
module Bookshelf
module Actions
module Books
class Show < Bookshelf::Action
def handle(request, response)
book = BookRepository.new.find(request.params[:id])
if book
response.format = :json
response.body = JSON.generate({
id: book.id,
title: book.title,
author: book.author,
published_year: book.published_year
})
else
response.status = 404
response.format = :json
response.body = JSON.generate({ error: 'Book not found' })
end
end
end
end
end
end
For better organization, you can also create a dedicated JSON view:
# app/views/books/json_show.rb
module Bookshelf
module Views
module Books
class JsonShow < Bookshelf::View
format :json
def template
render json: {
id: book.id,
title: book.title,
author: book.author
}
end
end
end
end
end
Handling Form Submissions
Let’s see how to handle a POST request with form data:
# app/actions/books/create.rb
module Bookshelf
module Actions
module Books
class Create < Bookshelf::Action
def handle(request, response)
book_params = request.params[:book]
if book_params[:title].nil? || book_params[:title].empty?
response.status = 422
response[:errors] = { title: ['is required'] }
return
end
book = BookRepository.new.create(book_params)
response.status = 201
response[:book] = book
end
end
end
end
end
The form would submit to /books with the parameters nested under the book key:
<form action="/books" method="POST">
<input type="text" name="book[title]" />
<input type="text" name="book[author]" />
<button type="submit">Create Book</button>
</form>
When to Use Actions and Views
Use actions when you need to:
- Process HTTP requests
- Validate input parameters
- Interact with your database or external services
- Return different response types (HTML, JSON, etc.)
Use views when you need to:
- Render HTML templates
- Format data for display
- Reuse presentation logic across multiple pages
The key principle is that actions should be thin — they handle request routing and coordination, while views handle the presentation logic. This makes your application easier to test and maintain.
Summary
You’ve learned how to create actions and views in Hanami 2. Actions handle incoming HTTP requests and route them to the appropriate logic. Views handle rendering the response, whether it’s HTML or JSON. The key takeaways are:
- Actions inherit from
Bookshelf::Actionand implement ahandlemethod - Use
request.paramsto access route parameters, query strings, and POST data - Views use a DSL for building HTML safely
- Set
response.format = :jsonand useresponse.bodyfor JSON responses - Keep actions thin — delegate presentation logic to views
In the next tutorial, we’ll explore persistence with ROM in Hanami, showing you how to interact with databases using Hanami’s data mapping layer.
Next Tutorial: Persistence with ROM in Hanami