Computers, Open-source, Personal, Ruby, Software, Thoughts, Web

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:

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!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s