Introduce a standard FormBuilder that leverages partials?

Request for feedback

I am looking for feedback on the following proposal. I would love to contribute a pull request which introduces a new FormBuilder that uses partials for customizing form field rendering. My main focus is on figuring out if this idea has the potential to be merged to Rails or if it should rather live as a gem. Your comments are highly appreciated!

The Problem

Forms are an integral part of web apps. Almost every application needs to add to the behavior of form field helpers. Here a few examples of adaptations:

  • Show error messages right next to the input field
  • Render labels for inputs
  • Indicate if a field is required or optional
  • Keep the field markup & CSS classes consistent throughout the app
  • Add more complex input fields (such as a barcode input or a custom date range picker)

Currently, there are multiple ways to solve these problems. There is a dedicated section in the Action View Form Helpers Guide on how to customize form elements. Some cases can even be solved with dedicated configuration options such as field_error_proc.

These approaches are either verbose or too limited to solve the above cases.

Current solutions

Here are alternatives how to solve the adaptations above:

  • Copy the required logic & markup into templates Repeating the markup in some views may keep the cross-dependencies low. However, copying the same (faulty) markup/logic to all forms, decreases maintainability.
  • Introduce helper methods Helpers are a very convenient way to refactor repeating code. However, specifying markup in helpers can easily become unreadable if the logic becomes more involved. Also, helpers do not have access to the form object unless passed explicitly.
  • Add a form builder Form builders are made for adapting and extending form field rendering. They are a great tool. However, similar to introducing helper methods, specifying the markup can become cumbersome (example gist) and hard to read.
  • Refactor to partials Partials are geared towards defining markup structures. They keep code readable even if logic becomes more complicated. However, partials do not have access to the form object per se. Also, using the render syntax does not communicate the type of input as well as a form builder method (render "forms/text_field", method: :title, builder: f vs. f.text_field :title).
  • Pull in a form builder gem There are many gems, that allow customizing form rendering. Each of them, has its own way to adapt the behavior. This requires the developer to learn these (sometimes complicated) concepts. Also, some gems provide additional functionality which may not be needed (e.g. automatic collection population).

I would love to combine the form builder concept with the advantages of partials.

The Idea

Provide a standard form builder that leverages partials.

I would like to provide an alternative to the existing ActionView::Helpers::FormBuilder. This additional builder (let’s call it PartialFormBuilder for now), would inherit from the existing FormBuilder. Yet, instead of assembling the field markup directly, it would render partials (the gem Kaminari also uses this concept, example partial).

Here an example how this might look like (mind the _ prefix):

<%# app/views/blog/_form.html.erb %>
<%= form_with model: @blog do |f| %>
  <%= f._text_field :title # renders app/views/form/_text_field.html.erb %>
  <%= f._text_field :author, hint: "The main writer of the blog" %>
  <%= f.text_field :slug # calling without prefix calls the default form helper %>
<% end %>

The PartialFormBuilder allows to render partials when using the _. Calls to these prefixed methods receive the given arguments, infer additional information (e.g. the field’s error messages) and pass them to a partial:

<%# app/views/form/_text_field.html.erb %>
<%# recieves +f+, +method+ and +errors+ and all options (keyword arguments) passed in the original template %>
<% if !defined?(label) || label != false %>
  <% f.label(method) do %>
<% end %>
<% if errors.any? %>
  %span.text-red <%= errors.to_sentence %>
<% end %>
<% class = class_names(local_assigns[:class], "bg-red-200": errors.any?) %>
<%= f.text_field(method, **local_assigns, class:) %>

Providing such a form builder, allows the developer to adapt and extend the rendering of form elements. By using partials, developers to focus on the templates themselves rather than developing builders. By using templates instead of methods, this approach avoids specifying markup with tag helper methods.
Ideally, we add the PartialFormBuilder as part of the framework. This would introduce a “standard way” for solving the recurring problem of customizing the form elements.

What do you think?

Next steps

You can ignore this section for now.

  • Decide if this could be an addition to Rails itself or a gem
  • Discuss syntax and necessity of prefix
  • Implement PartialFromBuilder
  • Devise standard partials and add generator
  • Adapt Rails guide
  • Add more extensive examples (tailwind, nested, etc.) as part of the documentation
  • Devise & run performance benchmarks
1 Like

I’d say build the gem first and when we have the code to compare and adoption to make sure people want this feature, we can discuss if the idea makes sense or not be be in framework.

2 Likes

Good to see something about FormBuilders.

However, I love tag_helper methods and dislike .erb templates in particular.

I have recently written a custom formBuilder that wraps form fields in a container. An options list determines where and how various elements are written.

The container will, by default, have

  • the input element
  • a label (field name, before/after)
  • a hint field
  • if requested, an icon/button which will show more detailed help (from locale/field data)
  • if a collection, then a header and a select/deselect-all element

It is not (yet) beautiful elegant code - it started out as a solution to too much repetitive erb code everywhere. It does the job.

I can see that a using partials is another way of applying the templates implied by my formbuilder. Your example might look like this

<%# app/views/blog/form.html.erb %>
  <%= form_with model: @blog do |f| %>
    <%= f.text_field :title, class: :major %>
    <%= f.text_field :author   # hint: defaulting to true, would fetch hint from locale   %> 
    <%= f.text_field :slug label: false, hint: false  %>
  <% end %>

I would prefer to write a different field_wrapper method than to have separate partials for each type of input field.

Cheers, Anita

A little similar to this: should the form_with method use an ApplicationForm by default? I find it cumbersome to introduce custom form builders because I have to pass it on every call or create a custom helper that does it for me.

I hear you @MatheusRich, introducing the same boilerplate may become cumbersome. Do you know about the default_form_builder? This does not solve the entire problem, but at least the custom helper part.

Seems others are playing with this idea

@motine that’s cool. I didn’t know about it.

Ah, this is very interesting. Thanks for sharing! I am very close to releasing something similar.

I made a few different decisions along the way. More concretely, there will be generators for the partials. I think this will make it a little easier for the developer to get started.