Proposal: Configure additional options to pass to fields_for from form builder

I’ve built a custom form builder base for our application: ApplicationFormBuilder < ActionView::Helpers::FormBuilder by subclassing ActionView’s FormBuilder as documented by Rails. I’ve added some custom options to the subclass. In particular, I’ll focus on a simple animations: true option that will add predefined css classes appropriately to the various inputs to add micro-animations to invalid fields.

I got tripped up with a form using fields_for. Ideally, we can set the options (animations: true) once on the parent form object and have it apply to the entire form. However, the implementation of this method is very specific about passing only :builder and :namespace from the parent options. So form inputs generated by fields_for are not inheriting the :animations option.

Workarounds include:

  1. Require passing the same options to fields_for. This can add a lot of unnecessary bookkeeping especially for dynamic forms that aren’t fully specified by a couple of view partials.
  2. (I’m currently doing this) Override fields_for to pass in additional options. This is mostly fine, but requires duplicating a little bit of code that puts this solution at risk to any future changes to the duplicated code:
  # Override default to pass :animations option to fields_for
  def fields_for(record_name, record_object = nil, fields_options = {}, &block)
    # copied from Rails FormBuilder#fields_for to account for variable method signature support
    # rubocop:disable Style/ParallelAssignment
    fields_options, record_object = record_object, nil if fields_options.nil? &&
      record_object.is_a?(Hash) && record_object.extractable_options?
    # rubocop:enable Style/ParallelAssignment

    field_options = fields_options.reverse_merge(animations: true) if options[:animations]
    super(record_name, record_object, field_options, &block)
  end

In a sense, the more general problem is that the variable signature of the fields_for method and similar make subclass overrides challenging as any override must duplicate the argument handling before being able to safely work with the arguments. But I don’t have a proposal for that!

Here, if we defined, say, inheritable_options to be an array of keys that we want to pass to nested fields, then we could add

fields_options.reverse_merge!(options.slice(*inheritable_options))

to the source of fields_for as a general solution that makes it easier to build and maintain feature-rich form builders without brittle method overrides.