Slices in a namespace

Just for fun and science I tried generating a slice in a namespace like this:

hanami generate slice utils/healthcheck

This is what I found:

  1. It created a file structure as I expected
Updated config/routes.rb
Created slices/utils/healthcheck/
Created slices/utils/healthcheck/action.rb
Created slices/utils/healthcheck/view.rb
Created slices/utils/healthcheck/views/helpers.rb
Created slices/utils/healthcheck/templates/layouts/app.html.erb
Created slices/utils/healthcheck/operation.rb
Created slices/utils/healthcheck/assets/js/app.js
Created slices/utils/healthcheck/assets/css/app.css
Created slices/utils/healthcheck/assets/images/favicon.ico
Created slices/utils/healthcheck/db/relation.rb
Created slices/utils/healthcheck/relations/.keep
Created slices/utils/healthcheck/db/repo.rb
Created slices/utils/healthcheck/repos/.keep
Created slices/utils/healthcheck/db/struct.rb
Created slices/utils/healthcheck/structs/.keep
Created slices/utils/healthcheck/actions/.keep
Created slices/utils/healthcheck/views/.keep
Created slices/utils/healthcheck/templates/.keep
Created slices/utils/healthcheck/templates/layouts/.keep
Created spec/slices/utils/healthcheck/action_spec.rb
Created spec/slices/utils/healthcheck/actions/.keep
  1. The generated classes were correctly namespaced too:
# auto_register: false
# frozen_string_literal: true

module Utils::Healthcheck
  class Action < HanamiTest::Action
  end
end
  1. The updated router was quite bad:
  class Routes < Hanami::Routes
    # Add your routes here. See https://guides.hanamirb.org/routing/overview/ for details.

    slice :utils/healthcheck, at: "/utils/healthcheck" do
    end
  end

(this is actually a syntax error)

  1. It gets registered as :utils
hanami_test[development]> Hanami.app.slices
=> #<Hanami::SliceRegistrar:0x00007f08a7bd3f38 @parent=HanamiTest::App, @slices={utils: Utils::Slice}>
  1. Generators for things inside such slice generally do not work.

My questions after this experiment are:

  1. Should the slice generator forbid slashes for now?
  2. Is there an appetite to make it work? It seems we are more or less half way to support it, with most difficult thing being how to symbolize such slice names (at least that’s my feeling).
  3. Do namespaced slices even make sense from Hanami philosophy point of view?

I think it could be useful in growing apps, especially when slices are used to divide by domain boundaries. Such apps likely still would need some “utils” or “dev” slices, would be nice to group them. But it’s also not the end of the world if they are not supported.

1 Like

I too have been experimenting with something similar, nested slices. A valid use case being AccessControl::IdentityProvider, and AccessControl::PermissionManager my use case is different in that AccessControl, AccessControl::IdentityProvider and AccessControl::PermissionManager are all slices. I’ve yet to get a generator to work correctly for this use case though and generally create files manually.

Do these slices have actions? If yes, how do you mount them in the router?

Right now all of the actions are in AccessControl and I do:

# config/routes.rb

module MyApp
  class Routes < Hanami::Routes
    slice :access_control, at: "/"
  end
end
# slices/access_control/config/routes.rb

module AccessControl
  class Routes < Hanami::Routes
    post "v1/identities/", to: "v1.identities.create"
    #...
  end
end

I did experiment with having actions in the lower level slices (and it works much the same) but one of the key reasons for having the parent slice was to allow for coordination between the two child slices. Ultimately I decided that the parent slice should own actions.

1 Like

The actions end up looking something like this:

module AccessControl
  module Actions
    module V1
      module Identities
        class Create < Action
          include Deps[
                    "slices.identity_provider.operations.create_identity",
                    "slices.permission_manager.operations.setup_initial_permissions",
                  ]

          def handle(request, response)
            case create_identity.call(request.params.to_h)
            in Success[identity]
              case setup_initial_permissions.call(identity.id)
              in Success[permissions] then handle_success(identity, permissions, response)
              else handle_permission_setup_failure(response)
              end
            else handle_identity_creation_failure(response)
            end
          end
        end
      end
    end
  end
end