Links in slice templates

I wonder what is the recommended way to work with links in slice templates. Let’s say I have an Admin slice that is mounted like this:

 slice :admin, at: "/admin"

It has a very simple routing itself:

module Admin
  class Routes < Hanami::Routes
    get "/pages", to: "pages.index"
    get "/page/:id", to: "pages.show", as: :page
  end
end

Now on the pages.index template I want to render a collection of links to pages. When I use slice routing, it produces links without the prefix:

> Admin::Slice["routes"].path(:page, id: 1)
=> "/page/1"

This makes sense, because why would a slice need to know how it’s mounted. Although maybe it could for practical reasons?

Using “global” routing I can do:

> Hanami.app["routes"].path(:admin_page, id: 1)
=> "/admin/page/1"

This is, indeed, what I want, but it turns out that the route name is not based on the slice name, but actually on the prefix on which the slice is mounted. If I change to this:

slice :admin, at: "/admin_panel"

… then :admin_page no longer works. It is now :admin_panel_page.

Is there a way to make links in slices really independent from how they are mounted, but also correct? For context, this would be extremely useful in slices distributed as gems, as in that case the slice really does not know where it would be mounted.

But maybe I missed something simple and there is a way to do that already?

I think you do not missing something, this is the way it is right now.

From my point of view slice local routes should be generated using the slice
routes and Hanami ensures that the mount path is included in the path.
With this the slice can be mounted in arbitrary apps without changing routes.

For example:

# config/routes.rb
slice :admin, at: "/admin"
...

# slices/admin/config/routes.rb
module Admin
  class Routes < Hanami::Routes
    get "/page/:id", to: "pages.show", as: :page
    ...
  end
end
...

Admin::Slice["routes"].path(:page, id: 1)
=> "/admin/page/1"

In case the slice is mounted under /admin_panel:

Admin::Slice["routes"].path(:page, id: 1)
=> "/admin_panel/page/1"

I use a wrapper per slice which adds the mount path, for example:

routes = SliceRoutes.new(Admin::Slice["routes"], mount_path: 'admin')
routes.path(:page, id: 1)
=> "/admin/page/1"

I did not find a way yet to take mount_path from the routes config, e.g.:

SliceRoutes.new(..., mount_path: Admin::Slice["routes"].mount_path)

Sometimes we might need routes of the app or other slices inside the slice.
There should be few dependencies between slices or a slice and the app.

For app routes in a slice I use the app routes for now:

Hanami.app["routes"].path(:about)
=> "/about"

For routes of other slices I would probably go though the app routes, via a
custom provider:

Hanami.app["routes.help"].path(:registration)
=> "/help/registration/howto"
1 Like

One thing I figured is that currently it’s possible to mount a slice twice at two different scopes:

    slice :admin, at: "/admin_panel"
    slice :admin, at: "/godmode"

I have no idea why would someone want to do that, but it’s possible. Any change with which slice becomes knowledgeable about where it’s mounted would require forbidding that, I think. And this is a breaking change.

Same goes for a mapping between slices and scopes in the router.

So I reckon currently the only way for externally provided slices to work is to rely on a naming convention, i.e. if you use an admin slice from a gem, you have to mount it at /admin (or provide some additional configuration).