# Unlocking the power of forms in Rails

I've had this idea in the back of my head for a while that can potentially unlock a bunch of possibilities with Rails' Form Helpers. But to present it, I want to write about what you can do with them today, the limitations and what I want as a developer.

I'll be releasing 3 articles in quick succession over the next few days.

1. Part 1: An intro/refresher on `FormBuilder`
    
2. Part 2: A deep dive into how `FormBuilder` and Tag Helper Classes build tags like `<input>` or `<select>`. Here we'll uncover what I believe is the opportunity to unlock new stuff.
    
3. Part 3: A proposal for a solution with a link to a Rails Fork that's in progress.
    

# You're using a FormBuilder even if you don't know what it is

> Most of what I'll mention in this series and some of the examples come from the awesome Rails Guide: [Action View Form Helpers](https://guides.rubyonrails.org/form_helpers.html)

I have to be honest; I haven't written a plain HTML form in many years and I don't really know how to do it from memory any more. Rails form helpers provide a beautiful developer experience while keeping us true to the fundamentals of the web. What a combo!

When I learnt Rails for the first time I wrote forms that looked something like this one.

```erb
<%= form_with url: "/search", method: :get do |form| %>
  <%= form.label :query, "Search for:" %>
  <%= form.text_field :query %>
  <%= form.submit "Search" %>
<% end %>
```

I typed the stuff and a form popped up. However, I never really understood what `form` was, especially when working with Stack Overflow examples and older code that used just `f`. What the hell is `f`? (please, don't use `f`).

Well, that `form` is an instance of a class called [`FormBuilder`](https://github.com/rails/rails/blob/main/actionview/lib/action_view/helpers/form_helper.rb#L1674). The documentation has a great description of this class:

> The +FormBuilder+ object can be thought of as serving as a proxy for the # methods in the +FormHelper+ module.

In other words, an instance of `FormBuilder`, which is initialized when you call `form_with`, wraps all the different form elements you can use to build a form in nice methods like `label`, `text_field`, `button` that return plain HTML.

Usually, though, we don't have a plain URL to point our form to. More often than not, `form_with` is called with a model. Like an instance of a `@user` or `@article` and this is where the benefits start multiplying.

```erb
<%= form_with model: @article do |form| %>
  <%= form.text_field :title %>
  <%= form.text_area :body, size: "60x10" %>
  <%= form.submit %>
<% end %>
```

The docs continue with the following:

> This class, however, allows you to # call methods with the model object you are building the form for.

To understand this, let's zoom in on the `text_field` method in the example above. Assuming there is an `Article` model with a `title` attribute the output of `form.text_field :title` will be:

```xml
<input type="text" 
       name="article[title]" 
       id="article_title" 
       value="My Title" />
```

Without you having to think about it, Rails added the following attributes to the resulting HTML tag:

* name: with a convention of the model's class and then the attribute in squared brackets
    
* id: with the model name first then the attribut ename
    
* value
    

These are crucial for this element's interactions with things like [an associated `label` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label#defining_an_explicit_label_with_the_for_attribute), JavaScript interactions that require a unique ID or generating a conventional and predictable `params` hash in your controller to manipulate and persist data.

This is **the most important part of forms in Ruby on Rails.** The framework has already put in place everything you need to build a form specifically catered to a model and to receive the information back from that form in a predictable way.

(Keep this in mind because this is going to be crucial for this whole series.)

# Styling form helpers

## Directly in a view template

To style form helpers you can use the `class` parameter and give it some value of a CSS class. In this case, we'll use the class `"input"`

```erb
<%= form.text_field 
      :title, 
      class: "input" %>
```

But what if all of your forms look the same and you always use the same class?

```erb
<%= form.text_field :title, class: "input" %>
<%= form.text_field :subtitle, class: "input" %>
<%= form.text_field :body, class: "input" %>
<%= form.text_field :footer, class: "input" %>
```

## By using a Form Builder

You can also go one level deeper and customize a `FormBuilder` with the styles you want. To do it though, you need to understand how a `FormBuilder` method works. Let’s take text\_field as an example ([Source](https://github.dev/rails/rails/blob/4f70750c076715fe4a474f0295bc0d8a849ba0a7/actionview/lib/action_view/helpers/form_helper.rb#L1171)):

```ruby
def text_field(object_name, method, options = {})
  Tags::TextField.new(object_name, method, self, options).render
end
```

The method receives a few arguments and immediately delegates them to a new instance of a `Tags::TextField` class and then calls `render` on it. These `Tag` classes (which are nested under the [**helpers** folder](https://github.com/rails/rails/tree/main/actionview/lib/action_view/helpers) in the Action View codebase) are kind of equivalent to what we now call a **component**: a class whose purpose is to render a bit of HTML.

Without going any deeper, we can just use our own `FormBuilder`, modify the arguments to add our `input` CSS class and call `super` so Rails keeps doing its job.

```ruby
class SomeCustomFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(object_name, method, options = {})
    options.merge!(class: "input")
    super(object_name, method, options)
  end
end
```

This method is fine for simple interventions like adding a few classes and it cleans up your code quite a bit. It also makes it easy to change your code in a single place and affect every form.

```erb
<%= form.text_field :title %>
<%= form.text_field :subtitle %>
<%= form.text_field :body %>
<%= form.text_field :footer %>
```

# More complex form elements

But form elements can become very complex. You may want to add a trailing and/or leading icon, a hint, an error message, or complex markup to create a checkbox that looks like a switch. Here are a few ways to solve that

## Complex form builder methods

*\*There's going to be a bit of pseudo-code in this section to make my point so it may contain some errors.*

You can create markup directly in the form builder like so:

```ruby
class SomeCustomFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(method, **args)
    @template.capture do
      @template.concat super(attribute, args.merge(class: 'a long string of classes if you use tailwind'))
        if some_var = args.fetch(:some_argument, nil)
          @template.concat @template.content_tag(:p, some_var., class: 'another long string of classes')
        end
      end  
  end
end
```

Even though this option works, it's super complex and hard to read. First, you need to be aware that you're writing stuff to a `@template` instance variable to which you send the method `capture` to encapsulate more HTML that you then have to `concat`. This is not a nice way to write markup at all.

## Delegating to a partial

To solve the above, you can just render a partial and create your markup there:

```ruby
class SomeCustomFormBuilder < ActionView::Helpers::FormBuilder
  def text_field(method, **args)
    @template.render(
      "some_custom_form_builder/text_field", 
      locals: { form: self, method: method, args: args)
    )
  end
end
```

```erb
<div>
  <span>Some icon</span>
  <%= self.text_field %>
  <span><%= self.object.error_messages[method] %>
</div>
```

But there's a problem here. I'm calling `self.text_field` in the template and that will reference the `text_field` method in the form builder which will create an infinite loop and a stack overflow error.

To solve it you have two options. You either create an `<input>` field manually and lose all of the work Rails has done to add an `id`, `name` and `value` or rename the `text_field` method in `SomeCustomeFormBuilder` by prefixing it with something else like... `some_custom_<method_name>` so that we can freely call `text_field`. Let's take that second option:

```ruby
class SomeCustomFormBuilder < ActionView::Helpers::FormBuilder
  def some_custom_text_field(method, **args) # 👎
    @template.render(
      "some_custom_form_builder/some_custom_text_field", 
      locals: { form: self, method: method, args: args)
    )
  end
end
```

It's not bad. But it isn't great either. For a framework and a language that excels at ergonomics and naming, this is not very nice. This is how you would use this in a form:

```erb
<%= form_with model: @articel do |form| %>
  <%= form.some_custom_text_field :article %>
<% end %>
```

# What I want

I just want to be able to call `form.text_field :article` and have it return whatever markup I want. For some people that might be just an `<input type="text">` but for others that might be a full-on component with labels icons, hints, errors etc.

# Finding the problem

I've done some digging and I think I know what's forcing developers to do this. Now that you know all about Form Builders, in the next article we'll dive even deeper into how something as simple as a `TextField` is rendered and we'll uncover some coupling that I believe is holding form builders up.
