Hanami 2 actions/views structure

Option 1

Directory Structure

lib/
  app_template/
    action.rb                  # AppTemplate::Action < Hanami::Action
    view.rb                    # AppTemplate::View < Hanami::View
    views/
      context.rb               # AppTemplate::Views::Context < Hanami::View::Context
      part.rb                  # AppTemplate::Views::Part < Hanami::View::Part
slices/
  main/
    lib/
       action.rb               # Main::Action < AppTemplate::Action
       view.rb                 # Main::View < AppTemplate::View
       actions/
         authentication.rb     # Main::Actions::Authentication # CUSTOM MODULE
       views/
         context.rb            # Main::Views::Context < AppTemplate::Views::Context
         part.rb               # Main::Views::Part < AppTemplate::Views::Part
         part_builder.rb       # Main::Views::PartBuilder < Hanami::View::PartBuilder # OPTIONAL
         helpers/
           authentication.rb   # Main::Views::Helpers::Authentication # CUSTOM MODULE
         parts/
           article.rb          # Main::Views::Parts::Article < Main::Views::Part
    actions/
      home/
        index.rb               # Main::Actions::Home::Index < Main::Action
    views/
      home/
        index.rb               # Main::Views::Home::Index < Main::View

CLI UX

bundle exec hanami generate view main parts.foo
# => Error `parts' is a reserved namespace

bundle exec hanami generate view main helpers.foo
# => Error `helpers' is a reserved namespace

Notes

  • Views and Actions have almost identical structure
  • lib/app_template contains action/view code that is inherited by all the slices’ actions/views
  • slices/main/lib contains action/view code that is inherited by all the actions/views of the main slice
  • slices/main/{actions,views} contains concrete actions/views for the main slice
  • Main::Views::{Helpers,Parts} are reserved namespaces to avoid name clashes with concrete views
  • It’s recommended to put custom action modules in slices/main/lib/actions under the Main::Actions namespace
  • It’s recommended to put custom view modules in slices/main/lib/views/helpers under the Main::Views::Helpers namespace

Option 2

Directory Structure

lib/
  app_template/
    action.rb               # AppTemplate::Action < Hanami::Action
    view.rb                 # AppTemplate::View < Hanami::View
    views/
      context.rb            # AppTemplate::Views::Context < Hanami::View::Context
      part.rb               # AppTemplate::Views::Part < Hanami::View::Part
slices/
  main/
    lib/
       action.rb            # Main::Action < AppTemplate::Action
       view.rb              # Main::View < AppTemplate::View
    actions/
      authentication.rb     # Main::Actions::Authentication # CUSTOM MODULE
      home/
        index.rb            # Main::Actions::Home::Index < Main::Action
    views/
      context.rb            # Main::Views::Context < AppTemplate::Views::Context
      part.rb               # Main::Views::Part < AppTemplate::Views::Part
      part_builder.rb       # Main::Views::PartBuilder < Hanami::View::PartBuilder # OPTIONAL
      helpers/
        authentication.rb   # Main::Views::Helpers::Authentication # CUSTOM MODULE
      parts/
        article.rb          # Main::Views::Parts::Article < Main::Views::Part
      home/
        index.rb            # Main::Views::Home::Index < Main::View

CLI UX

bundle exec hanami generate view main parts.foo
# => Error `parts' is a reserved namespace

bundle exec hanami generate view main helpers.foo
# => Error `helpers' is a reserved namespace

Notes

  • Views and Actions have almost identical structure
  • lib/app_template contains action/view code that is inherited by all the slices’ actions/views
  • slices/main/{actions,views} contains concrete actions/views AND base classes for the main slice
  • Main::Views::{Helpers,Parts} are reserved namespaces to avoid name clashes with concrete views
  • It’s recommended to put custom action modules in slices/main/actions under the Main::Actions namespace
  • It’s recommended to put custom view modules in slices/main/views/helpers under the Main::Views::Helpers namespace

Option 3

Directory Structure

lib/
  app_template/
    action/
      base.rb                  # AppTemplate::Action::Base < Hanami::Action
    view/
      base.rb                  # AppTemplate::View::Base < Hanami::View
      context.rb               # AppTemplate::View::Context < Hanami::View::Context
      part.rb                  # AppTemplate::View::Part < Hanami::View::Part
slices/
  main/
    lib/
       action/
         base.rb               # Main::Action::Base < AppTemplate::Action::Base
         authentication.rb     # Main::Action::Authentication # CUSTOM MODULE
       view/
         base.rb               # Main::View::Base < AppTemplate::View::Base
         context.rb            # Main::View::Context < AppTemplate::View::Context
         part.rb               # Main::View::Part < AppTemplate::View::Part
         part_builder.rb       # Main::View::PartBuilder < Hanami::View::PartBuilder # OPTIONAL
         helpers/
           authentication.rb   # Main::View::Helpers::Authentication # CUSTOM MODULE
         parts/
           article.rb          # Main::View::Parts::Article < Main::View::Part
    actions/
      home/
        index.rb               # Main::Actions::Home::Index < Main::Action::Base
    views/
      home/
        index.rb               # Main::Views::Home::Index < Main::View::Base

Notes

  • Views and Actions have almost identical structure
  • Singular namespaces are for base classes, plural namespaces are for concrete actions/views
  • lib/app_template contains action/view code that is inherited by all the slices’ actions/views
  • slices/main/{action,view} contains base classes AND utility code for the main slice
  • It’s recommended to put custom action modules in slices/main/action under the Main::Action namespace
  • It’s recommended to put custom view modules in slices/main/view/helpers under the Main::View::Helpers namespace

Thanks for laying these out, @jodosha, it’s supremely helpful!

I think Option 3 is the most extensible, but at the cost of it perhaps not feeling as nice if you never add extra classes related to your base action or view, etc.

However we know this will definitely happen for nearly every non-trivial Hanami app, because we’ll be generating a per-slice context class, and the views will want part classes, etc.

Option 1 might feel nicer in some usages, since you can just inherit from < Main::View instead of < Main::View::Base , but it comes with added cost about having to worry about name pollution of your pluralised Views namespace, plus the fact that some users may wonder why we’re populating Main::Views classes via two different directories.

My own instinctive lean is towards Option 3, but I realise I probably have a different sense for what feels good in application structures to what we might want to present to our users by default.

OTOH, that could be a signal, and perhaps we should lean into being different here, because we can teach our users something via that difference, that e.g. having a whole namespace for your base action and view classes is useful because you should be decomposing that behaviour into smaller units (which is in fact what is encouraged by our action/view library design too).

With all of that said, I do acknowledge that how the Hanami framework presents itself to our users via its file and class structures is something you care a lot about, so I don’t want to push you down a road that feels uncomfortable to you. We should each trust each other to make these decisions like these :smile:

3 Likes