An action is an endpoint that handles incoming HTTP requests for a specific route. In a Hanami application, an action is an object, while a controller is a Ruby module that groups them.
This design provides self contained actions that don’t share their context accidentally with other actions. It also prevents gigantic controllers. It has several advantages in terms of testability and control of an action.
A Simple Action
Hanami ships with a generator for actions. Let’s create a new one:
$ hanami generate action web dashboard#index
Let’s examine the action:
# apps/web/controllers/dashboard/index.rb module Web module Controllers module Dashboard class Index include Web::Action def call(params) end end end end end
That file begins with a module declaration.
The first token is the name of our application:
Hanami can run multiple applications within the same Ruby process.
They are located under
Their name is used as a top-level module to contain inner components like actions and views, in order to avoid naming collisions.
If we have another action
Home::Index under an application
Admin, the two of them can coexist inside the same codebase.
The second token is a conventional name:
All the controllers are nested under it.
This module is generated at runtime for us, when the application starts.
For a given application named
Web, controllers are available under
The last bit is
Dashboard, which is our controller.
The whole action name is
You should avoid giving your action modules the same name as your application, e.g. avoid naming a controller
Web in an app named
Web. If you have a controller name like
Web::Controllers::Web then some code across your app will break with errors about constants not being found, for example in views which
include Web::Layout. This is because Ruby starts constant lookup with the current module, so a constant like
Web::Layout referenced by code in the
Web::Controllers::Web::MyAction module will be converted to
Web::Controllers::Web::Layout, which can't be found and causes a constant lookup error.
If you absolutely must name a controller with the same name as your application, you'll need to explicitly set the namespace lookup for things which should be included from immediately under the app, not the controller by prefixing those names with
::, e.g. change your views to include
::Web::Layout instead of
include Web::Layout, and using
include ::Web::Action in your controllers.
Hanami philosophy emphasizes composition over inheritance and avoids the framework superclass antipattern. For this reason, all the components are provided as modules to include instead of base classes to inherit from.
Like we said before, Hanami can run multiple apps within the same Ruby process.
Each of them has its own configuration.
To keep separated actions from an application named
Web and an application named
Admin, we include
In our example, we have a directive
That means our action will behave according to the configuration of the
For a given application named
Web, the action mixin to include is
When we include
Web::Action, we made our object compliant with Hanami::Controller’s actions.
We need to implement
#call, which is a method that accepts only one argument:
That is the object that carries the payload that comes from incoming HTTP requests from the router.
This interface reminds us of Rack. Indeed, our action is compatible with the Rack protocol.