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:
form(record_name, options) form_for(record_or_name_or_array, *args, &proc) remote_form_for(record_or_name_or_array, *args, &proc) form_tag(url_for_options, options, *parameters_for_url, &block)
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:
text_field(object_name, method, options) text_field_tag(name, value, options)
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.
To make it even more terse, this can be refactored in DRYer way as:
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 one text_field , and and one form_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:
# This will not actually work :user, :instance => @user. :html => {}, :url => {}, :errors => flash[:error] do |f| %> # As a block method :username}) %> # Outside the block - passing model and attribute :user, :attribute=> :username}) %> # Without being associated with a model at all :username, :value => '', :html => {}}) %> # As its own block
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:
select(:user, :role, Role.all.map {|r| [r.name, r.id], {:include_blank => true}})
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.
Resources:
ActionView::Helpers::FormHelper
ActionView::Helpers::FormTagHelper
ActionView::Helpers::FormOptionsHelper
ActionView::Helpers::ActiveRecordHelper
ActionView::Helpers::PrototypeHelper
Next week, I will tackle the naming discrepancies between javascript_include_tag and stylesheet_link_tag – arrgh!