DEV Community

Andy Leverenz
Andy Leverenz

Posted on • Originally published at web-crunch.com on

How to use FormBuilder in Ruby on Rails

Ruby on Rails ships with form helpers based in Ruby out of the box. These helpers are helpful for dynamically generating HTML fields on forms that pertain to the data layer backed by ActiveRecord in a given application.

When generating a new resource in rails a common path for many developers is to scaffold a new model. A scaffold contains everything you need from the data side to the front end views. This practice is a big time saver for a developer looking to be extremely productive.

When generating such a resource there is commonly a new form view partial included. The form is how data goes from an end-user to a database. There's a lot to know about how Rails handles forms in a given app but this guide is focused on customizing the default form helper methods and/or creating your own from scratch.

Goals of automation with Tailwind CSS

In the video, I reference my kickoff_tailwind Ruby on Rails application template. The template uses Tailwind CSS for stylesheets. Tailwind is a highly productive CSS framework but it comes with a lot of repetitive qualities that inspired me to create a custom FormBuilder.

Ultimately, the goal here is to allow any new resource that gets generated come already set up with Tailwind CSS classes. Doing this would allow me to not have to go back and add them every time.

My template comes with some form styles by default but they still need to be applied manually. Having a FormBuilder such as this could automate that.

Creating a custom FormBuilder class

Inside the app directory of a Ruby on Rails app you can create custom extensions in addition to what comes stock. This folder gets auto-loaded so there's very little to configure outside of whatever you're adding.

For the purposes of this guide I'll make a new folder called builders. Inside it I'll create a new file called tailwind_builder.rb.

This file will inherit from what's already part of the framework.

# app/builders/tailwind_builder.rb class TailwindBuilder < ActionView::Helpers::FormBuilder # include ActionView::Helpers::TagHelper # include ActionView::Context def text_field(attribute, options={}) super(attribute, options.reverse_merge(class: "input")) end def text_area(attribute, options={}) super(attribute, options.reverse_merge(class: "input")) end def select(object_name, method_name, template_object, options={}) super(object_name, method_name, template_object, options.reverse_merge(class: "select")) end def div_radio_button(method, tag_value, options = {}) @template.content_tag(:div, @template.radio_button( @object_name, method, tag_value, objectify_options(options) ) ) end end 
Enter fullscreen mode Exit fullscreen mode

In this file, you can invent new form helpers or extend existing helpers using the super() method. Since my goal is to only modify the class attribute on the generated HTML I'll need to extend existing fields to accommodate.

If you look at the source code for a text_field form helper, for example, you can get a sense of what parameters need to be passed through.

# https://github.com/rails/rails/blob/914caca2d31bd753f47f9168f2a375921d9e91cc/actionview/lib/action_view/helpers/tags/text_field.rb # frozen_string_literal: true require "action_view/helpers/tags/placeholderable" module ActionView module Helpers module Tags # :nodoc: class TextField < Base # :nodoc: include Placeholderable def render options = @options.stringify_keys options["size"] = options["maxlength"] unless options.key?("size") options["type"] ||= field_type options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file" add_default_name_and_id(options) tag("input", options) end class << self def field_type @field_type ||= name.split("::").last.sub("Field", "").downcase end end private def field_type self.class.field_type end end end end end 
Enter fullscreen mode Exit fullscreen mode

There is a lot to unpack here but the line tag("input", options) is about all we need to know.

Unfortunately, there is little documentation on the API docs so I would suggest using the actual code as your guide.

There is a large list of form helper tags you can view the source on via Github to get a better sense of what's going on under the hood.

Putting the builder to use

We haven't declared the builder in our view just yet so nothing should change from the default rendering. To do so you can simply pass a builder: TailwindBuilder option on the form.

For this guide, I generated a Post model scaffold by running

rails g scaffold Post title content:text 
Enter fullscreen mode Exit fullscreen mode

And modified the form in app/views/posts/_form.html.erb.

<%= form_with(model: post, builder: TailwindBuilder) do |form| %> <% if post.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2> <ul> <% post.errors.each do |error| %> <li><%= error.full_message %></li> <% end %> </ul> </div> <% end %> <div class="mb-6"> <%= form.label :title %> <%= form.text_field :title %> </div> <div class="mb-6"> <%= form.label :content %> <%= form.text_area :content %> </div> <%= form.div_radio_button :title, "Test" %> <div class="actions"> <%= form.submit %> </div> <% end %> 
Enter fullscreen mode Exit fullscreen mode

With this code in place our basic text_field and text_area fields not have an input class I created prior that stems from a style sheet in my template.

/* app/javascript/stylesheets/components/_forms.scss */ %focus-style { @apply shadow outline-none border-gray-500; box-shadow: 0 0 0 0.2rem theme("colors.gray.200"); background-clip: padding-box; } .input { @apply appearance-none block w-full text-gray-700 border border-gray-400 rounded px-3 leading-tight bg-white shadow-inner; padding-top: .65rem; padding-bottom: .65rem; } .input:focus, .input:hover { @extend %focus-style; } ... 
Enter fullscreen mode Exit fullscreen mode

Opting into a form builder app-wide

Adding a form builder option to every form you create could get tedious and repetitive. Luckily, there is a way to enable a default builder on the application configration level.

# app/config/application.rb require_relative "boot" require "rails/all" Bundler.require(*Rails.groups) module CustomFormBuilder class Application < Rails::Application ... config.action_view.default_form_builder = TailwindBuilder ... end end 
Enter fullscreen mode Exit fullscreen mode

Don't overdo it

I think form helpers solve a lot of problems for a rails developer looking to move fast. Extending them is a great way to increase speed but it comes at cost if you want more customization down the line. Generating large blocks of HTML might be overkill or it might be desired given your team size and how often you're creating form data. Hopefully this guide proved useful!

Happy Coding!

New to Ruby on Rails? I made a course for you.

hello rails

Hello Rails is a modern course designed to help you start using and understanding Ruby on Rails fast. Get 10% off the master version of the course using the promo code RAILZ2021

Top comments (0)