Advanced forms in Hanami 2

Hello! I have published the episode around approach how to leverage parts and scopes in order to get rid of logic in templates.

This is not meant to be the best and only way, but as always with Hanami Mastery episodes, it’s a starting point for feedback and discussion, so we can all learn together.

If anyone is interested in hanami-view, I encourage checking it out and if sth could be done better, I would love to learn!

Especially one thing drives me crazy :sweat_smile:.

I have decided to define scope and call render with partial, all within part’s method.

I wonder if this is sth that should be kept in template

# slices/main/templates/forms/registration.html.erb

<%= form_for :registration, routes.path(:register_account) do |f| %>
  <%= form.username_input(f) %>
  <%= form.password_input(f) %>
  <%= form.password_confirmation_input(f) %>
  <%= form.tac_input(f) %>

  <div class="field is-grouped">
    <div class="control">
      <%= f.submit "Register account", class: "button is-link" %>
    </div>
    <div class="control">
      <%= link_to "Already have an account", "#", class: "button is-link is-light"%>
    </div>
  </div>
<% end %>
# slices/main/views/parts/forms/registration.rb

module Main
  module Views
    module Parts
      module Forms
        class Registration < Part
          SCOPE_CLASS = Scopes::Shared::Forms::Input
          
          def username_input(f)
            prepare_scope(f, :username).
              render("shared/forms/text_field")
          end

          def password_input(f)
            prepare_scope(f, :password).
              render("shared/forms/password_field")
          end

          def password_confirmation_input(f)
            prepare_scope(f, :password_confirmation).
              render("shared/forms/password_field")
          end

          def tac_input(f)
            # TODO: TAC input field
          end

          private

          def prepare_scope(f, field_name, **options)
            scope(
              SCOPE_CLASS,
              f: f,
              field_name: field_name,
              errors: errors(field_name),
              **options
            )
          end

          def errors(key)
            value.dig(:errors, key).to_a
          end
        end
      end
    end
  end
end

What do you think about this approach?

4 Likes