When you inherit from an action class, you also inherit much of its behavior. This includes:

  • Dependencies specified with the Deps mixin
  • Config
  • Callbacks
  • Parameter validation schemas

You can use action inheritance to ensure that broad collections of actions all have consistent key behaviors.

For example, consider the following base action class.

  1. # apps/action.rb
  2. module Bookshelf
  3. class Action < Hanami::Action
  4. include Deps["authenticator"]
  5. format :json
  6. before :authenticate_user!
  7. private
  8. def authenticate_user!(request, response)
  9. halt 401 unless authenticator.valid_api_token?(request.headers["X-API-Token"])
  10. end
  11. end
  12. end

Any class that inherits from this will:

  • Have the authenticator object available to its instance methods
  • Expect JSON requests and return JSON respnses
  • Ensure that requests are authenticated based on the X-API-Token and return a 401 error if not

Courtesty of the inheritance, subclasses gain all this behavior without any additional code.

  1. # app/actions/books/index.rb
  2. module Bookshelf
  3. module Actions
  4. module Books
  5. class Index < Bookshelf::Action
  6. end
  7. end
  8. end
  9. end

Standard base classes

When you create a new Hanami app, Hanami generates a standard base class for all your actions to inherit from.

  1. # apps/action.rb
  2. module Bookshelf
  3. class Action < Hanami::Action
  4. end
  5. end

Hanami also generates a base action class for each slice.

  1. # slices/admin/action.rb
  2. module Admin
  3. class Action < Hanami::Action
  4. end
  5. end

These base classes can be a useful place to put any config or behavior that you want for every action in your app or slice.

Leveraging action inheritance

Since actions are just regular Ruby classes, you can also use all the standard Ruby techniques to share behaviour across various subsets of your actions.

For example, you could make a new base class for a specific group of actions:

  1. # app/actions/user_profile/base.rb
  2. module Bookshelf
  3. module Actions
  4. module UserProfile
  5. class Base < Bookshelf::Action
  6. before :authenticate_user!
  7. private
  8. def authenticate_user!(request, response)
  9. # halt 401 unless ...
  10. end
  11. end
  12. end
  13. end
  14. end

Any classes within this namespace could inherit from Actions::UserProfile::Base and share common user authentication behaviour.

Alternatively, for authentication behaviour that may need to be used across a range of disparate actions, you could also consider a module.

  1. # app/actions/authenticated_action.rb
  2. module Bookshelf
  3. module Actions
  4. module AuthenticatedAction
  5. def self.included(action_class)
  6. action_class.before :authenticate_user!
  7. end
  8. private
  9. def authenticate_user!(request, response)
  10. # halt 401 unless ...
  11. end
  12. end
  13. end
  14. end

With this, any action requiring authentication can include the module.

  1. module Bookshelf
  2. module Actions
  3. module UserProfile
  4. class Update < Bookshelf::Action
  5. include AuthenticatedAction
  6. end
  7. end
  8. end
  9. end