Action-Packed ActionView Helpers

The Rails ActionView Helpers class es seems to be a gift and a curse. While its sub-classes provide a incredible amount of flexibility, its overlapping and inconsistent naming conventions and fragile parameter passing are a constant headache for me when working with forms. The sub-classes seem to have significant overlap, and if I knew enough to propose a code-change, it may just be worth taking a shot at cleaning up some of this confusion.

I think that I have yet to achieve the simple goal of having successfully constructed a form in the “first-go”. The first part (after constructing your controller and model) to constructing a form opening tag is to use one of the following methods:

The easiest is form where you pass it the name of a model, and it auto-magically generates your form for you. Not too shabby, but it is pretty inflexible – mostly used for prototyping so I tend to avoid it.

Next is form_for – the method I most commonly use. You can optionally pass it a block, which I will *discuss* in a moment. It is important to note that this is the recommend method per the docs if you are working with a form that represents a model in your code.

remote_form_for is the AJAXish version of form_for, however its naming convention is inconsistent with its cousins link_to_remote and button_to_remote. How about form_for_remote so I don’t pull out my hair when I get a method not found error?

form_tag is intended to help create a form when there is no model defined to represent the form data. In other words, this does very little in the way of magic (*sigh*)

Using form_for, I begin coding my form, and the next step for me is to look at the other helper methods. In part:

These two methods may initially look like there is quite a bit of duplication going on inside the Rails helpers. A closer inspection reveals that the non-“tag” method is geared towards representing a model in the form_for context, whereas the other method text_field_tag is our model-less counterpart. More magic occurs here when the text_field helper method automatically includes the current objects value when constructing the tag. This is useful for sharing the same form (in a partial view) for both the create and edit views:

This would create a form for the User model, and generate a textbox for the username method. This is weird to me – that the method text_field would be aware of anything passed to form_for.

Here we are using the block argument of the form_for tag. This block variable then has methods such as text_field (I assume this is an identically named method and not some other block magic). Also, note form_for assumes that our instance of a User class is contained inside an instance variable named @user – so we can omit that.

Its pretty cool to be able to manipulate forms with this amount of flexibility – three ways that are increasingly terse and assumptious. However, this mechanism of choosing your level of “integration” with a model comes at the cost of confusion to users (or at least me). Looking at the problem and trying to come up with a solution, I decided a possible alternate approach would have been to have onetext_field , and and oneform_for helper method (that does the work of any other similar methods) that could take in a hash of options to modify its behavior. (like ActiveRecord). A possible usage example might be:

The hole goes deeper as we get into more advanced form helper methods such as the select:

select(object, method, choices, options, html_options)

This method is beautifully terse – but approaches being unreadable. The choices parameter (which I think would be better passed along with the other options in one argument as a hash) is typically an iteration of an ActiveRecord find, building an array each pass. For example:

This seems awkward to me. Perhaps if the select could be a block instead? For the block, you could indicate the population of values intended to be generated as option tags. You could then write something similar to:

:user, :options => Role.all} do |s| %>

If this is done in a generic way, you could potentially eliminate the methods of collection_select, grouped_options_for_select, and options_for_select.