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
Naming
That file begins with a module declaration.
The first token is the name of our application: Web
. Hanami can run multiple applications within the same Ruby process. They are located under apps/
. 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: Controllers
. 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 Web::Controllers
.
The last bit is Dashboard
, which is our controller.
The whole action name is Web::Controllers::Dashboard::Index
.
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
or 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.
Action Module
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 Web::Action
and Admin::Action
respectively.
In our example, we have a directive include Web::Action
. That means our action will behave according to the configuration of the Web
application.
For a given application named Web
, the action mixin to include is Web::Action
.
Interface
When we include Web::Action
, we make our object compliant with Hanami::Controller’s actions. We need to implement #call
, which is a method that accepts only one argument: params
. 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.