What is the prescribed way to handle/render UI errors?

When making an HTTP POST request for a HTML form, I’d like to render the show view upon success and a new (with errors) upon failure. Here’s a simple code snippet:

class Create < Main::Action
  include Deps[
    tasks: "repositories.task",
    users: "repositories.user",
    show_view: "views.tasks.show",
    new_view: "views.tasks.new"
  ]

  params do
    required(:task).hash do
      required(:user_id).filled :integer
      required(:description).filled :string
    end
  end

  def handle request, response
    parameters = request.params

    if parameters.valid?
      task = tasks.create parameters[:task]
      response.render show_view, task: tasks.find(task.id)
    else
      response.render new_view, users: users.all, error: parameters.errors[:task]
    end
  end
end

As you can see, upon success, the show view will be rendered but have a question:

  • When using ROM, are you suppose to create and find a record in order to load all associated (combined) data? Is there a better way to create and answer back a record with all associations at the same time?

Upon failure, I attempt to render the new view to force the user to supply valid information but have a couple of questions:

  • Is using the parameters errors the right way to do this?
  • Should I be sending an empty task record which is merged with errors or would a tuple of task record plus errors hash be the right way to handle this?

Since the Hanami 2.0.0 View layer isn’t fully ready, maybe I’m asking the wrong kind of question entirely since maybe this should be given to some kind of form object that the view renders. If a form object is ideal then how would one pull that off in a basic flow like this?

I know these are a bunch of questions but am trying to understand what the ideal create and/or update error handling flow would look like in Hanami for basic CRUD actions like this.

Yes, we’re thinking how to approach this. It was missing in Hanami 1.

If you had to imagine an API, how that would look like?

My naive initial stab – since I’m trying to rapidly get up to speed on how Dry View works – would be something along these lines:

# Action
parameters  # Syntactic sugar wrapper for `request.params`

response.render view, errors: parameters.errors[:my_record_key]  # Dry Schema top-level error key.
response.render_with_errors view                                 # Syntactic sugar

# View
expose :errors  # Falls back to Dry::Core::EMPTY_HASH by default if not provided but can be overwritten if given.

Alternatively, it could be quite desirable to avoid primitive obsession entirely by leveraging a data object for errors. Example:

# Action - Automatically converts `parameters.errors[:my_record_key]` into a `Data` object for all attributes.
response.render_with_errors view

# View
expose(:error) { Dry::Core::EMPTY_DATA }  # Doesn't exist but would equate to `Data.define`.

The above would mean that within your template – as exposed via your view – you could do this:

error.descripton
error.name
...etc...

I’m not for adding another method to the response.

I was thinking of passing the errors automatically to the view. Then on the view side to expose the error by default.

So that in the template, you can use a new helper like <%= render_errors(errors) %>.

Yeah, that makes sense and more elegant since the view layer is where you’d want to interact with this information anyway. Much better.

I assume, when you use the example of <%= render_errors(errors) %> then that would be one of possibly a couple helper methods within the view? I ask because I often need finer grain control within the view because I need to display errors next to the form field that is most relevant. Expanding upon your example, that would look a bit like this I think (using an outline to visually explain the shape):

  • Form Group 1
    • Label 1
    • Field 1
    • Error 1 (i.e. <%= render_error :field_1 %>
  • Form Group 2
    • Label 1
    • Field 2
    • Error 2 (i.e. <%= render_error :field_2 %>

In truth, there can be a bit more conditional logic to the above because sometimes you might need to conditionally render the error. Example:

<p><%= render_error :description if errors.key? :description %></p>

Does that make sense or is there a better way to tackle this?